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.NewCredentialsCache(creds)
30//
31// // Create service client value configured for credentials
32// // from assumed role.
33// svc := s3.NewFromConfig(cfg)
34//
35// # Assume Role with custom MFA Token provider
36//
37// To assume an IAM role with a MFA token you can either specify a custom MFA
38// token provider or use the SDK's built in StdinTokenProvider that will prompt
39// the user for a token code each time the credentials need to to be refreshed.
40// Specifying a custom token provider allows you to control where the token
41// code is retrieved from, and how it is refreshed.
42//
43// With a custom token provider, the provider is responsible for refreshing the
44// token code when called.
45//
46// cfg, err := config.LoadDefaultConfig(context.TODO())
47// if err != nil {
48// panic(err)
49// }
50//
51// staticTokenProvider := func() (string, error) {
52// return someTokenCode, nil
53// }
54//
55// // Create the credentials from AssumeRoleProvider to assume the role
56// // referenced by the "myRoleARN" ARN using the MFA token code provided.
57// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) {
58// o.SerialNumber = aws.String("myTokenSerialNumber")
59// o.TokenProvider = staticTokenProvider
60// })
61//
62// cfg.Credentials = aws.NewCredentialsCache(creds)
63//
64// // Create service client value configured for credentials
65// // from assumed role.
66// svc := s3.NewFromConfig(cfg)
67//
68// # Assume Role with MFA Token Provider
69//
70// To assume an IAM role with MFA for longer running tasks where the credentials
71// may need to be refreshed setting the TokenProvider field of AssumeRoleProvider
72// will allow the credential provider to prompt for new MFA token code when the
73// role's credentials need to be refreshed.
74//
75// The StdinTokenProvider function is available to prompt on stdin to retrieve
76// the MFA token code from the user. You can also implement custom prompts by
77// satisfying the TokenProvider function signature.
78//
79// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
80// have undesirable results as the StdinTokenProvider will not be synchronized. A
81// single Credentials with an AssumeRoleProvider can be shared safely.
82//
83// cfg, err := config.LoadDefaultConfig(context.TODO())
84// if err != nil {
85// panic(err)
86// }
87//
88// // Create the credentials from AssumeRoleProvider to assume the role
89// // referenced by the "myRoleARN" ARN using the MFA token code provided.
90// creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), "myRoleArn", func(o *stscreds.AssumeRoleOptions) {
91// o.SerialNumber = aws.String("myTokenSerialNumber")
92// o.TokenProvider = stscreds.StdinTokenProvider
93// })
94//
95// cfg.Credentials = aws.NewCredentialsCache(creds)
96//
97// // Create service client value configured for credentials
98// // from assumed role.
99// svc := s3.NewFromConfig(cfg)
100package stscreds
101
102import (
103 "context"
104 "fmt"
105 "time"
106
107 "github.com/aws/aws-sdk-go-v2/aws"
108 "github.com/aws/aws-sdk-go-v2/service/sts"
109 "github.com/aws/aws-sdk-go-v2/service/sts/types"
110)
111
112// StdinTokenProvider will prompt on stdout and read from stdin for a string value.
113// An error is returned if reading from stdin fails.
114//
115// Use this function go read MFA tokens from stdin. The function makes no attempt
116// to make atomic prompts from stdin across multiple gorouties.
117//
118// Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will
119// have undesirable results as the StdinTokenProvider will not be synchronized. A
120// single Credentials with an AssumeRoleProvider can be shared safely
121//
122// Will wait forever until something is provided on the stdin.
123func StdinTokenProvider() (string, error) {
124 var v string
125 fmt.Printf("Assume Role MFA token code: ")
126 _, err := fmt.Scanln(&v)
127
128 return v, err
129}
130
131// ProviderName provides a name of AssumeRole provider
132const ProviderName = "AssumeRoleProvider"
133
134// AssumeRoleAPIClient is a client capable of the STS AssumeRole operation.
135type AssumeRoleAPIClient interface {
136 AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error)
137}
138
139// DefaultDuration is the default amount of time in minutes that the
140// credentials will be valid for. This value is only used by AssumeRoleProvider
141// for specifying the default expiry duration of an assume role.
142//
143// Other providers such as WebIdentityRoleProvider do not use this value, and
144// instead rely on STS API's default parameter handing to assign a default
145// value.
146var DefaultDuration = time.Duration(15) * time.Minute
147
148// AssumeRoleProvider retrieves temporary credentials from the STS service, and
149// keeps track of their expiration time.
150//
151// This credential provider will be used by the SDKs default credential change
152// when shared configuration is enabled, and the shared config or shared credentials
153// file configure assume role. See Session docs for how to do this.
154//
155// AssumeRoleProvider does not provide any synchronization and it is not safe
156// to share this value across multiple Credentials, Sessions, or service clients
157// without also sharing the same Credentials instance.
158type AssumeRoleProvider struct {
159 options AssumeRoleOptions
160}
161
162// AssumeRoleOptions is the configurable options for AssumeRoleProvider
163type AssumeRoleOptions struct {
164 // Client implementation of the AssumeRole operation. Required
165 Client AssumeRoleAPIClient
166
167 // IAM Role ARN to be assumed. Required
168 RoleARN string
169
170 // Session name, if you wish to uniquely identify this session.
171 RoleSessionName string
172
173 // Expiry duration of the STS credentials. Defaults to 15 minutes if not set.
174 Duration time.Duration
175
176 // Optional ExternalID to pass along, defaults to nil if not set.
177 ExternalID *string
178
179 // The policy plain text must be 2048 bytes or shorter. However, an internal
180 // conversion compresses it into a packed binary format with a separate limit.
181 // The PackedPolicySize response element indicates by percentage how close to
182 // the upper size limit the policy is, with 100% equaling the maximum allowed
183 // size.
184 Policy *string
185
186 // The ARNs of IAM managed policies you want to use as managed session policies.
187 // The policies must exist in the same account as the role.
188 //
189 // This parameter is optional. You can provide up to 10 managed policy ARNs.
190 // However, the plain text that you use for both inline and managed session
191 // policies can't exceed 2,048 characters.
192 //
193 // An AWS conversion compresses the passed session policies and session tags
194 // into a packed binary format that has a separate limit. Your request can fail
195 // for this limit even if your plain text meets the other requirements. The
196 // PackedPolicySize response element indicates by percentage how close the policies
197 // and tags for your request are to the upper size limit.
198 //
199 // Passing policies to this operation returns new temporary credentials. The
200 // resulting session's permissions are the intersection of the role's identity-based
201 // policy and the session policies. You can use the role's temporary credentials
202 // in subsequent AWS API calls to access resources in the account that owns
203 // the role. You cannot use session policies to grant more permissions than
204 // those allowed by the identity-based policy of the role that is being assumed.
205 // For more information, see Session Policies (https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html#policies_session)
206 // in the IAM User Guide.
207 PolicyARNs []types.PolicyDescriptorType
208
209 // The identification number of the MFA device that is associated with the user
210 // who is making the AssumeRole call. Specify this value if the trust policy
211 // of the role being assumed includes a condition that requires MFA authentication.
212 // The value is either the serial number for a hardware device (such as GAHT12345678)
213 // or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user).
214 SerialNumber *string
215
216 // The source identity specified by the principal that is calling the AssumeRole
217 // operation. You can require users to specify a source identity when they assume a
218 // role. You do this by using the sts:SourceIdentity condition key in a role trust
219 // policy. You can use source identity information in CloudTrail logs to determine
220 // who took actions with a role. You can use the aws:SourceIdentity condition key
221 // to further control access to Amazon Web Services resources based on the value of
222 // source identity. For more information about using source identity, see Monitor
223 // and control actions taken with assumed roles
224 // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_control-access_monitor.html)
225 // in the IAM User Guide.
226 SourceIdentity *string
227
228 // Async method of providing MFA token code for assuming an IAM role with MFA.
229 // The value returned by the function will be used as the TokenCode in the Retrieve
230 // call. See StdinTokenProvider for a provider that prompts and reads from stdin.
231 //
232 // This token provider will be called when ever the assumed role's
233 // credentials need to be refreshed when SerialNumber is set.
234 TokenProvider func() (string, error)
235
236 // A list of session tags that you want to pass. Each session tag consists of a key
237 // name and an associated value. For more information about session tags, see
238 // Tagging STS Sessions
239 // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html) in the
240 // IAM User Guide. This parameter is optional. You can pass up to 50 session tags.
241 Tags []types.Tag
242
243 // A list of keys for session tags that you want to set as transitive. If you set a
244 // tag key as transitive, the corresponding key and value passes to subsequent
245 // sessions in a role chain. For more information, see Chaining Roles with Session
246 // Tags
247 // (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_role-chaining)
248 // in the IAM User Guide. This parameter is optional.
249 TransitiveTagKeys []string
250}
251
252// NewAssumeRoleProvider constructs and returns a credentials provider that
253// will retrieve credentials by assuming a IAM role using STS.
254func NewAssumeRoleProvider(client AssumeRoleAPIClient, roleARN string, optFns ...func(*AssumeRoleOptions)) *AssumeRoleProvider {
255 o := AssumeRoleOptions{
256 Client: client,
257 RoleARN: roleARN,
258 }
259
260 for _, fn := range optFns {
261 fn(&o)
262 }
263
264 return &AssumeRoleProvider{
265 options: o,
266 }
267}
268
269// Retrieve generates a new set of temporary credentials using STS.
270func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
271 // Apply defaults where parameters are not set.
272 if len(p.options.RoleSessionName) == 0 {
273 // Try to work out a role name that will hopefully end up unique.
274 p.options.RoleSessionName = fmt.Sprintf("aws-go-sdk-%d", time.Now().UTC().UnixNano())
275 }
276 if p.options.Duration == 0 {
277 // Expire as often as AWS permits.
278 p.options.Duration = DefaultDuration
279 }
280 input := &sts.AssumeRoleInput{
281 DurationSeconds: aws.Int32(int32(p.options.Duration / time.Second)),
282 PolicyArns: p.options.PolicyARNs,
283 RoleArn: aws.String(p.options.RoleARN),
284 RoleSessionName: aws.String(p.options.RoleSessionName),
285 ExternalId: p.options.ExternalID,
286 SourceIdentity: p.options.SourceIdentity,
287 Tags: p.options.Tags,
288 TransitiveTagKeys: p.options.TransitiveTagKeys,
289 }
290 if p.options.Policy != nil {
291 input.Policy = p.options.Policy
292 }
293 if p.options.SerialNumber != nil {
294 if p.options.TokenProvider != nil {
295 input.SerialNumber = p.options.SerialNumber
296 code, err := p.options.TokenProvider()
297 if err != nil {
298 return aws.Credentials{}, err
299 }
300 input.TokenCode = aws.String(code)
301 } else {
302 return aws.Credentials{}, fmt.Errorf("assume role with MFA enabled, but TokenProvider is not set")
303 }
304 }
305
306 resp, err := p.options.Client.AssumeRole(ctx, input)
307 if err != nil {
308 return aws.Credentials{Source: ProviderName}, err
309 }
310
311 var accountID string
312 if resp.AssumedRoleUser != nil {
313 accountID = getAccountID(resp.AssumedRoleUser)
314 }
315
316 return aws.Credentials{
317 AccessKeyID: *resp.Credentials.AccessKeyId,
318 SecretAccessKey: *resp.Credentials.SecretAccessKey,
319 SessionToken: *resp.Credentials.SessionToken,
320 Source: ProviderName,
321
322 CanExpire: true,
323 Expires: *resp.Credentials.Expiration,
324 AccountID: accountID,
325 }, nil
326}