Skip to content

Commit 3c8dc0e

Browse files
AWS Web Identity auth support
Signed-off-by: mateusz-ciesielski <mateusz.j.ciesielski@intel.com>
1 parent 3e0dffa commit 3c8dc0e

File tree

19 files changed

+8390
-0
lines changed

19 files changed

+8390
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package stscreds
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"strconv"
7+
"time"
8+
9+
"github.com/IBM/ibm-cos-sdk-go/aws"
10+
"github.com/IBM/ibm-cos-sdk-go/aws/awserr"
11+
"github.com/IBM/ibm-cos-sdk-go/aws/client"
12+
"github.com/IBM/ibm-cos-sdk-go/aws/credentials"
13+
"github.com/IBM/ibm-cos-sdk-go/service/sts"
14+
"github.com/IBM/ibm-cos-sdk-go/service/sts/stsiface"
15+
)
16+
17+
const (
18+
// ErrCodeWebIdentity will be used as an error code when constructing
19+
// a new error to be returned during session creation or retrieval.
20+
ErrCodeWebIdentity = "WebIdentityErr"
21+
22+
// WebIdentityProviderName is the web identity provider name
23+
WebIdentityProviderName = "WebIdentityCredentials"
24+
)
25+
26+
// now is used to return a time.Time object representing
27+
// the current time. This can be used to easily test and
28+
// compare test values.
29+
var now = time.Now
30+
31+
// TokenFetcher should return WebIdentity token bytes or an error
32+
type TokenFetcher interface {
33+
FetchToken(credentials.Context) ([]byte, error)
34+
}
35+
36+
// FetchTokenPath is a path to a WebIdentity token file
37+
type FetchTokenPath string
38+
39+
// FetchToken returns a token by reading from the filesystem
40+
func (f FetchTokenPath) FetchToken(ctx credentials.Context) ([]byte, error) {
41+
data, err := ioutil.ReadFile(string(f))
42+
if err != nil {
43+
errMsg := fmt.Sprintf("unable to read file at %s", f)
44+
return nil, awserr.New(ErrCodeWebIdentity, errMsg, err)
45+
}
46+
return data, nil
47+
}
48+
49+
// WebIdentityRoleProvider is used to retrieve credentials using
50+
// an OIDC token.
51+
type WebIdentityRoleProvider struct {
52+
credentials.Expiry
53+
54+
// The policy ARNs to use with the web identity assumed role.
55+
PolicyArns []*sts.PolicyDescriptorType
56+
57+
// Duration the STS credentials will be valid for. Truncated to seconds.
58+
// If unset, the assumed role will use AssumeRoleWithWebIdentity's default
59+
// expiry duration. See
60+
// https://docs.aws.amazon.com/sdk-for-go/api/service/sts/#STS.AssumeRoleWithWebIdentity
61+
// for more information.
62+
Duration time.Duration
63+
64+
// The amount of time the credentials will be refreshed before they expire.
65+
// This is useful refresh credentials before they expire to reduce risk of
66+
// using credentials as they expire. If unset, will default to no expiry
67+
// window.
68+
ExpiryWindow time.Duration
69+
70+
client stsiface.STSAPI
71+
72+
tokenFetcher TokenFetcher
73+
roleARN string
74+
roleSessionName string
75+
}
76+
77+
// NewWebIdentityCredentials will return a new set of credentials with a given
78+
// configuration, role arn, and token file path.
79+
//
80+
// Deprecated: Use NewWebIdentityRoleProviderWithOptions for flexible
81+
// functional options, and wrap with credentials.NewCredentials helper.
82+
func NewWebIdentityCredentials(c client.ConfigProvider, roleARN, roleSessionName, path string) *credentials.Credentials {
83+
svc := sts.New(c)
84+
p := NewWebIdentityRoleProvider(svc, roleARN, roleSessionName, path)
85+
return credentials.NewCredentials(p)
86+
}
87+
88+
// NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the
89+
// provided stsiface.STSAPI
90+
//
91+
// Deprecated: Use NewWebIdentityRoleProviderWithOptions for flexible
92+
// functional options.
93+
func NewWebIdentityRoleProvider(svc stsiface.STSAPI, roleARN, roleSessionName, path string) *WebIdentityRoleProvider {
94+
return NewWebIdentityRoleProviderWithOptions(svc, roleARN, roleSessionName, FetchTokenPath(path))
95+
}
96+
97+
// NewWebIdentityRoleProviderWithToken will return a new WebIdentityRoleProvider with the
98+
// provided stsiface.STSAPI and a TokenFetcher
99+
//
100+
// Deprecated: Use NewWebIdentityRoleProviderWithOptions for flexible
101+
// functional options.
102+
func NewWebIdentityRoleProviderWithToken(svc stsiface.STSAPI, roleARN, roleSessionName string, tokenFetcher TokenFetcher) *WebIdentityRoleProvider {
103+
return NewWebIdentityRoleProviderWithOptions(svc, roleARN, roleSessionName, tokenFetcher)
104+
}
105+
106+
// NewWebIdentityRoleProviderWithOptions will return an initialize
107+
// WebIdentityRoleProvider with the provided stsiface.STSAPI, role ARN, and a
108+
// TokenFetcher. Additional options can be provided as functional options.
109+
//
110+
// TokenFetcher is the implementation that will retrieve the JWT token from to
111+
// assume the role with. Use the provided FetchTokenPath implementation to
112+
// retrieve the JWT token using a file system path.
113+
func NewWebIdentityRoleProviderWithOptions(svc stsiface.STSAPI, roleARN, roleSessionName string, tokenFetcher TokenFetcher, optFns ...func(*WebIdentityRoleProvider)) *WebIdentityRoleProvider {
114+
p := WebIdentityRoleProvider{
115+
client: svc,
116+
tokenFetcher: tokenFetcher,
117+
roleARN: roleARN,
118+
roleSessionName: roleSessionName,
119+
}
120+
121+
for _, fn := range optFns {
122+
fn(&p)
123+
}
124+
125+
return &p
126+
}
127+
128+
// Retrieve will attempt to assume a role from a token which is located at
129+
// 'WebIdentityTokenFilePath' specified destination and if that is empty an
130+
// error will be returned.
131+
func (p *WebIdentityRoleProvider) Retrieve() (credentials.Value, error) {
132+
return p.RetrieveWithContext(aws.BackgroundContext())
133+
}
134+
135+
// RetrieveWithContext will attempt to assume a role from a token which is
136+
// located at 'WebIdentityTokenFilePath' specified destination and if that is
137+
// empty an error will be returned.
138+
func (p *WebIdentityRoleProvider) RetrieveWithContext(ctx credentials.Context) (credentials.Value, error) {
139+
b, err := p.tokenFetcher.FetchToken(ctx)
140+
if err != nil {
141+
return credentials.Value{}, awserr.New(ErrCodeWebIdentity, "failed fetching WebIdentity token: ", err)
142+
}
143+
144+
sessionName := p.roleSessionName
145+
if len(sessionName) == 0 {
146+
// session name is used to uniquely identify a session. This simply
147+
// uses unix time in nanoseconds to uniquely identify sessions.
148+
sessionName = strconv.FormatInt(now().UnixNano(), 10)
149+
}
150+
151+
var duration *int64
152+
if p.Duration != 0 {
153+
duration = aws.Int64(int64(p.Duration / time.Second))
154+
}
155+
156+
req, resp := p.client.AssumeRoleWithWebIdentityRequest(&sts.AssumeRoleWithWebIdentityInput{
157+
PolicyArns: p.PolicyArns,
158+
RoleArn: &p.roleARN,
159+
RoleSessionName: &sessionName,
160+
WebIdentityToken: aws.String(string(b)),
161+
DurationSeconds: duration,
162+
})
163+
164+
req.SetContext(ctx)
165+
166+
// InvalidIdentityToken error is a temporary error that can occur
167+
// when assuming an Role with a JWT web identity token.
168+
req.RetryErrorCodes = append(req.RetryErrorCodes, sts.ErrCodeInvalidIdentityTokenException)
169+
if err := req.Send(); err != nil {
170+
return credentials.Value{}, awserr.New(ErrCodeWebIdentity, "failed to retrieve credentials", err)
171+
}
172+
173+
p.SetExpiration(aws.TimeValue(resp.Credentials.Expiration), p.ExpiryWindow)
174+
175+
value := credentials.Value{
176+
AccessKeyID: aws.StringValue(resp.Credentials.AccessKeyId),
177+
SecretAccessKey: aws.StringValue(resp.Credentials.SecretAccessKey),
178+
SessionToken: aws.StringValue(resp.Credentials.SessionToken),
179+
ProviderName: WebIdentityProviderName,
180+
}
181+
return value, nil
182+
}

0 commit comments

Comments
 (0)