web_identity_provider.go (5324B)
1 package stscreds 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "strconv" 8 "time" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 "github.com/aws/aws-sdk-go-v2/aws/retry" 12 "github.com/aws/aws-sdk-go-v2/internal/sdk" 13 "github.com/aws/aws-sdk-go-v2/service/sts" 14 "github.com/aws/aws-sdk-go-v2/service/sts/types" 15 ) 16 17 var invalidIdentityTokenExceptionCode = (&types.InvalidIdentityTokenException{}).ErrorCode() 18 19 const ( 20 // WebIdentityProviderName is the web identity provider name 21 WebIdentityProviderName = "WebIdentityCredentials" 22 ) 23 24 // AssumeRoleWithWebIdentityAPIClient is a client capable of the STS AssumeRoleWithWebIdentity operation. 25 type AssumeRoleWithWebIdentityAPIClient interface { 26 AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) 27 } 28 29 // WebIdentityRoleProvider is used to retrieve credentials using 30 // an OIDC token. 31 type WebIdentityRoleProvider struct { 32 options WebIdentityRoleOptions 33 } 34 35 // WebIdentityRoleOptions is a structure of configurable options for WebIdentityRoleProvider 36 type WebIdentityRoleOptions struct { 37 // Client implementation of the AssumeRoleWithWebIdentity operation. Required 38 Client AssumeRoleWithWebIdentityAPIClient 39 40 // JWT Token Provider. Required 41 TokenRetriever IdentityTokenRetriever 42 43 // IAM Role ARN to assume. Required 44 RoleARN string 45 46 // Session name, if you wish to uniquely identify this session. 47 RoleSessionName string 48 49 // Expiry duration of the STS credentials. STS will assign a default expiry 50 // duration if this value is unset. This is different from the Duration 51 // option of AssumeRoleProvider, which automatically assigns 15 minutes if 52 // Duration is unset. 53 // 54 // See the STS AssumeRoleWithWebIdentity API reference guide for more 55 // information on defaults. 56 // https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html 57 Duration time.Duration 58 59 // An IAM policy in JSON format that you want to use as an inline session policy. 60 Policy *string 61 62 // The Amazon Resource Names (ARNs) of the IAM managed policies that you 63 // want to use as managed session policies. The policies must exist in the 64 // same account as the role. 65 PolicyARNs []types.PolicyDescriptorType 66 } 67 68 // IdentityTokenRetriever is an interface for retrieving a JWT 69 type IdentityTokenRetriever interface { 70 GetIdentityToken() ([]byte, error) 71 } 72 73 // IdentityTokenFile is for retrieving an identity token from the given file name 74 type IdentityTokenFile string 75 76 // GetIdentityToken retrieves the JWT token from the file and returns the contents as a []byte 77 func (j IdentityTokenFile) GetIdentityToken() ([]byte, error) { 78 b, err := ioutil.ReadFile(string(j)) 79 if err != nil { 80 return nil, fmt.Errorf("unable to read file at %s: %v", string(j), err) 81 } 82 83 return b, nil 84 } 85 86 // NewWebIdentityRoleProvider will return a new WebIdentityRoleProvider with the 87 // provided stsiface.ClientAPI 88 func NewWebIdentityRoleProvider(client AssumeRoleWithWebIdentityAPIClient, roleARN string, tokenRetriever IdentityTokenRetriever, optFns ...func(*WebIdentityRoleOptions)) *WebIdentityRoleProvider { 89 o := WebIdentityRoleOptions{ 90 Client: client, 91 RoleARN: roleARN, 92 TokenRetriever: tokenRetriever, 93 } 94 95 for _, fn := range optFns { 96 fn(&o) 97 } 98 99 return &WebIdentityRoleProvider{options: o} 100 } 101 102 // Retrieve will attempt to assume a role from a token which is located at 103 // 'WebIdentityTokenFilePath' specified destination and if that is empty an 104 // error will be returned. 105 func (p *WebIdentityRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { 106 b, err := p.options.TokenRetriever.GetIdentityToken() 107 if err != nil { 108 return aws.Credentials{}, fmt.Errorf("failed to retrieve jwt from provide source, %w", err) 109 } 110 111 sessionName := p.options.RoleSessionName 112 if len(sessionName) == 0 { 113 // session name is used to uniquely identify a session. This simply 114 // uses unix time in nanoseconds to uniquely identify sessions. 115 sessionName = strconv.FormatInt(sdk.NowTime().UnixNano(), 10) 116 } 117 input := &sts.AssumeRoleWithWebIdentityInput{ 118 PolicyArns: p.options.PolicyARNs, 119 RoleArn: &p.options.RoleARN, 120 RoleSessionName: &sessionName, 121 WebIdentityToken: aws.String(string(b)), 122 } 123 if p.options.Duration != 0 { 124 // If set use the value, otherwise STS will assign a default expiration duration. 125 input.DurationSeconds = aws.Int32(int32(p.options.Duration / time.Second)) 126 } 127 if p.options.Policy != nil { 128 input.Policy = p.options.Policy 129 } 130 131 resp, err := p.options.Client.AssumeRoleWithWebIdentity(ctx, input, func(options *sts.Options) { 132 options.Retryer = retry.AddWithErrorCodes(options.Retryer, invalidIdentityTokenExceptionCode) 133 }) 134 if err != nil { 135 return aws.Credentials{}, fmt.Errorf("failed to retrieve credentials, %w", err) 136 } 137 138 // InvalidIdentityToken error is a temporary error that can occur 139 // when assuming an Role with a JWT web identity token. 140 141 value := aws.Credentials{ 142 AccessKeyID: aws.ToString(resp.Credentials.AccessKeyId), 143 SecretAccessKey: aws.ToString(resp.Credentials.SecretAccessKey), 144 SessionToken: aws.ToString(resp.Credentials.SessionToken), 145 Source: WebIdentityProviderName, 146 CanExpire: true, 147 Expires: *resp.Credentials.Expiration, 148 } 149 return value, nil 150 }