1// Package stscreds are credential Providers to retrieve STS AWS credentials. 2// 3// STS provides multiple ways to retrieve credentials which can be used when making 4// future AWS service API operation calls. 5// 6// The SDK will ensure that per instance of credentials.Credentials all requests 7// to refresh the credentials will be synchronized. But, the SDK is unable to 8// ensure synchronous usage of the AssumeRoleProvider if the value is shared 9// between multiple Credentials or service clients. 10// 11// Assume Role 12// 13// To assume an IAM role using STS with the SDK you can create a new Credentials 14// with the SDKs's stscreds package. 15// 16// // Initial credentials loaded from SDK's default credential chain. Such as 17// // the environment, shared credentials (~/.aws/credentials), or EC2 Instance 18// // Role. These credentials will be used to to make the STS Assume Role API. 19// cfg, err := config.LoadDefaultConfig(context.TODO()) 20// if err != nil { 21// panic(err) 22// } 23// 24// // Create the credentials from AssumeRoleProvider to assume the role 25// // referenced by the "myRoleARN" ARN. 26// stsSvc := sts.NewFromConfig(cfg) 27// creds := stscreds.NewAssumeRoleProvider(stsSvc, "myRoleArn") 28// 29// cfg.Credentials = &aws.CredentialsCache{Provider: creds} 30// 31// // Create service client value configured for credentials 32// // from assumed role. 33// svc := s3.NewFromConfig(cfg) 34// 35// Assume Role with static MFA Token 36// 37// To assume an IAM role with a MFA token you can either specify a MFA token code 38// directly or provide a function to prompt the user each time the credentials 39// need to refresh the role's credentials. Specifying the TokenCode should be used 40// for short lived operations that will not need to be refreshed, and when you do 41// not want to have direct control over the user provides their MFA token. 42// 43// With TokenCode the AssumeRoleProvider will be not be able to refresh the role's 44// credentials. 45// 46// cfg, err := config.LoadDefaultConfig(context.TODO()) 47// if err != nil { 48// panic(err) 49// } 50// 51// // Create the credentials from AssumeRoleProvider to assume the role 52// // referenced by the "myRoleARN" ARN using the MFA token code provided. 53// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) { 54// o.SerialNumber = aws.String("myTokenSerialNumber") 55// o.TokenCode = aws.String("00000000") 56// }) 57// 58// cfg.Credentials = &aws.CredentialsCache{Provider: creds} 59// 60// // Create service client value configured for credentials 61// // from assumed role. 62// svc := s3.NewFromConfig(cfg) 63// 64// Assume Role with MFA Token Provider 65// 66// To assume an IAM role with MFA for longer running tasks where the credentials 67// may need to be refreshed setting the TokenProvider field of AssumeRoleProvider 68// will allow the credential provider to prompt for new MFA token code when the 69// role's credentials need to be refreshed. 70// 71// The StdinTokenProvider function is available to prompt on stdin to retrieve 72// the MFA token code from the user. You can also implement custom prompts by 73// satisfying the TokenProvider function signature. 74// 75// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will 76// have undesirable results as the StdinTokenProvider will not be synchronized. A 77// single Credentials with an AssumeRoleProvider can be shared safely. 78// 79// cfg, err := config.LoadDefaultConfig(context.TODO()) 80// if err != nil { 81// panic(err) 82// } 83// 84// // Create the credentials from AssumeRoleProvider to assume the role 85// // referenced by the "myRoleARN" ARN using the MFA token code provided. 86// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) { 87// o.SerialNumber = aws.String("myTokenSerialNumber") 88// o.TokenProvider = stscreds.StdinTokenProvider 89// }) 90// 91// cfg.Credentials = &aws.CredentialsCache{Provider: creds} 92// 93// // Create service client value configured for credentials 94// // from assumed role. 95// svc := s3.NewFromConfig(cfg) 96package stscreds 97 98import ( 99 "context" 100 "fmt" 101 "time" 102 103 "github.com/aws/aws-sdk-go-v2/aws" 104 "github.com/aws/aws-sdk-go-v2/service/sts" 105 "github.com/aws/aws-sdk-go-v2/service/sts/types" 106) 107 108// StdinTokenProvider will prompt on stdout and read from stdin for a string value. 109// An error is returned if reading from stdin fails. 110// 111// Use this function go read MFA tokens from stdin. The function makes no attempt 112// to make atomic prompts from stdin across multiple gorouties. 113// 114// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will 115// have undesirable results as the StdinTokenProvider will not be synchronized. A 116// single Credentials with an AssumeRoleProvider can be shared safely 117// 118// Will wait forever until something is provided on the stdin. 119func StdinTokenProvider() (string, error) { 120 var v string 121 fmt.Printf("Assume Role MFA token code: ") 122 _, err := fmt.Scanln(&v) 123 124 return v, err 125} 126 127// ProviderName provides a name of AssumeRole provider 128const ProviderName = "AssumeRoleProvider" 129 130// AssumeRoleAPIClient is a client capable of the STS AssumeRole operation. 131type AssumeRoleAPIClient interface { 132 AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) 133} 134 135// DefaultDuration is the default amount of time in minutes that the credentials 136// will be valid for. 137var DefaultDuration = time.Duration(15) * time.Minute 138 139// AssumeRoleProvider retrieves temporary credentials from the STS service, and 140// keeps track of their expiration time. 141// 142// This credential provider will be used by the SDKs default credential change 143// when shared configuration is enabled, and the shared config or shared credentials 144// file configure assume role. See Session docs for how to do this. 145// 146// AssumeRoleProvider does not provide any synchronization and it is not safe 147// to share this value across multiple Credentials, Sessions, or service clients 148// without also sharing the same Credentials instance. 149type AssumeRoleProvider struct { 150 options AssumeRoleOptions 151} 152 153// AssumeRoleOptions is the configurable options for AssumeRoleProvider 154type AssumeRoleOptions struct { 155 // Client implementation of the AssumeRole operation. Required 156 Client AssumeRoleAPIClient 157 158 // IAM Role ARN to be assumed. Required 159 RoleARN string 160 161 // Session name, if you wish to uniquely identify this session. 162 RoleSessionName string 163 164 // Expiry duration of the STS credentials. Defaults to 15 minutes if not set. 165 Duration time.Duration 166 167 // Optional ExternalID to pass along, defaults to nil if not set. 168 ExternalID *string 169 170 // The policy plain text must be 2048 bytes or shorter. However, an internal 171 // conversion compresses it into a packed binary format with a separate limit. 172 // The PackedPolicySize response element indicates by percentage how close to 173 // the upper size limit the policy is, with 100% equaling the maximum allowed 174 // size. 175 Policy *string 176 177 // The ARNs of IAM managed policies you want to use as managed session policies. 178 // The policies must exist in the same account as the role. 179 // 180 // This parameter is optional. You can provide up to 10 managed policy ARNs. 181 // However, the plain text that you use for both inline and managed session 182 // policies can't exceed 2,048 characters. 183 // 184 // An AWS conversion compresses the passed session policies and session tags 185 // into a packed binary format that has a separate limit. Your request can fail 186 // for this limit even if your plain text meets the other requirements. The 187 // PackedPolicySize response element indicates by percentage how close the policies 188 // and tags for your request are to the upper size limit. 189 // 190 // Passing policies to this operation returns new temporary credentials. The 191 // resulting session's permissions are the intersection of the role's identity-based 192 // policy and the session policies. You can use the role's temporary credentials 193 // in subsequent AWS API calls to access resources in the account that owns 194 // the role. You cannot use session policies to grant more permissions than 195 // those allowed by the identity-based policy of the role that is being assumed. 196 // For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session) 197 // in the IAM User Guide. 198 PolicyARNs []types.PolicyDescriptorType 199 200 // The identification number of the MFA device that is associated with the user 201 // who is making the AssumeRole call. Specify this value if the trust policy 202 // of the role being assumed includes a condition that requires MFA authentication. 203 // The value is either the serial number for a hardware device (such as GAHT12345678) 204 // or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user). 205 SerialNumber *string 206 207 // Async method of providing MFA token code for assuming an IAM role with MFA. 208 // The value returned by the function will be used as the TokenCode in the Retrieve 209 // call. See StdinTokenProvider for a provider that prompts and reads from stdin. 210 // 211 // This token provider will be called when ever the assumed role's 212 // credentials need to be refreshed when SerialNumber is also set and 213 // TokenCode is not set. 214 // 215 // If both TokenCode and TokenProvider is set, TokenProvider will be used and 216 // TokenCode is ignored. 217 TokenProvider func() (string, error) 218} 219 220// NewAssumeRoleProvider constructs and returns a credentials provider that 221// will retrieve credentials by assuming a IAM role using STS. 222func NewAssumeRoleProvider(client AssumeRoleAPIClient, roleARN string, optFns ...func(*AssumeRoleOptions)) *AssumeRoleProvider { 223 o := AssumeRoleOptions{ 224 Client: client, 225 RoleARN: roleARN, 226 } 227 228 for _, fn := range optFns { 229 fn(&o) 230 } 231 232 return &AssumeRoleProvider{ 233 options: o, 234 } 235} 236 237// Retrieve generates a new set of temporary credentials using STS. 238func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) { 239 // Apply defaults where parameters are not set. 240 if len(p.options.RoleSessionName) == 0 { 241 // Try to work out a role name that will hopefully end up unique. 242 p.options.RoleSessionName = fmt.Sprintf("aws-go-sdk-%d", time.Now().UTC().UnixNano()) 243 } 244 if p.options.Duration == 0 { 245 // Expire as often as AWS permits. 246 p.options.Duration = DefaultDuration 247 } 248 input := &sts.AssumeRoleInput{ 249 DurationSeconds: aws.Int32(int32(p.options.Duration / time.Second)), 250 PolicyArns: p.options.PolicyARNs, 251 RoleArn: aws.String(p.options.RoleARN), 252 RoleSessionName: aws.String(p.options.RoleSessionName), 253 ExternalId: p.options.ExternalID, 254 } 255 if p.options.Policy != nil { 256 input.Policy = p.options.Policy 257 } 258 if p.options.SerialNumber != nil { 259 if p.options.TokenProvider != nil { 260 input.SerialNumber = p.options.SerialNumber 261 code, err := p.options.TokenProvider() 262 if err != nil { 263 return aws.Credentials{}, err 264 } 265 input.TokenCode = aws.String(code) 266 } else { 267 return aws.Credentials{}, fmt.Errorf("assume role with MFA enabled, but neither TokenCode nor TokenProvider are set") 268 } 269 } 270 271 resp, err := p.options.Client.AssumeRole(ctx, input) 272 if err != nil { 273 return aws.Credentials{Source: ProviderName}, err 274 } 275 276 return aws.Credentials{ 277 AccessKeyID: *resp.Credentials.AccessKeyId, 278 SecretAccessKey: *resp.Credentials.SecretAccessKey, 279 SessionToken: *resp.Credentials.SessionToken, 280 Source: ProviderName, 281 282 CanExpire: true, 283 Expires: *resp.Credentials.Expiration, 284 }, nil 285} 286