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)
100 package stscreds
101 102 import (
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.
123 func 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
132 const ProviderName = "AssumeRoleProvider"
133 134 // AssumeRoleAPIClient is a client capable of the STS AssumeRole operation.
135 type 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.
146 var 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.
158 type AssumeRoleProvider struct {
159 options AssumeRoleOptions
160 }
161 162 // AssumeRoleOptions is the configurable options for AssumeRoleProvider
163 type 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 // The chain of providers that was used to create this provider
252 // These values are for reporting purposes and are not meant to be set up directly
253 CredentialSources []aws.CredentialSource
254 }
255 256 // NewAssumeRoleProvider constructs and returns a credentials provider that
257 // will retrieve credentials by assuming a IAM role using STS.
258 func NewAssumeRoleProvider(client AssumeRoleAPIClient, roleARN string, optFns ...func(*AssumeRoleOptions)) *AssumeRoleProvider {
259 o := AssumeRoleOptions{
260 Client: client,
261 RoleARN: roleARN,
262 }
263 264 for _, fn := range optFns {
265 fn(&o)
266 }
267 268 return &AssumeRoleProvider{
269 options: o,
270 }
271 }
272 273 // Retrieve generates a new set of temporary credentials using STS.
274 func (p *AssumeRoleProvider) Retrieve(ctx context.Context) (aws.Credentials, error) {
275 // Apply defaults where parameters are not set.
276 if len(p.options.RoleSessionName) == 0 {
277 // Try to work out a role name that will hopefully end up unique.
278 p.options.RoleSessionName = fmt.Sprintf("aws-go-sdk-%d", time.Now().UTC().UnixNano())
279 }
280 if p.options.Duration == 0 {
281 // Expire as often as AWS permits.
282 p.options.Duration = DefaultDuration
283 }
284 input := &sts.AssumeRoleInput{
285 DurationSeconds: aws.Int32(int32(p.options.Duration / time.Second)),
286 PolicyArns: p.options.PolicyARNs,
287 RoleArn: aws.String(p.options.RoleARN),
288 RoleSessionName: aws.String(p.options.RoleSessionName),
289 ExternalId: p.options.ExternalID,
290 SourceIdentity: p.options.SourceIdentity,
291 Tags: p.options.Tags,
292 TransitiveTagKeys: p.options.TransitiveTagKeys,
293 }
294 if p.options.Policy != nil {
295 input.Policy = p.options.Policy
296 }
297 if p.options.SerialNumber != nil {
298 if p.options.TokenProvider != nil {
299 input.SerialNumber = p.options.SerialNumber
300 code, err := p.options.TokenProvider()
301 if err != nil {
302 return aws.Credentials{}, err
303 }
304 input.TokenCode = aws.String(code)
305 } else {
306 return aws.Credentials{}, fmt.Errorf("assume role with MFA enabled, but TokenProvider is not set")
307 }
308 }
309 310 resp, err := p.options.Client.AssumeRole(ctx, input)
311 if err != nil {
312 return aws.Credentials{Source: ProviderName}, err
313 }
314 315 var accountID string
316 if resp.AssumedRoleUser != nil {
317 accountID = getAccountID(resp.AssumedRoleUser)
318 }
319 320 return aws.Credentials{
321 AccessKeyID: *resp.Credentials.AccessKeyId,
322 SecretAccessKey: *resp.Credentials.SecretAccessKey,
323 SessionToken: *resp.Credentials.SessionToken,
324 Source: ProviderName,
325 326 CanExpire: true,
327 Expires: *resp.Credentials.Expiration,
328 AccountID: accountID,
329 }, nil
330 }
331 332 // ProviderSources returns the credential chain that was used to construct this provider
333 func (p *AssumeRoleProvider) ProviderSources() []aws.CredentialSource {
334 if p.options.CredentialSources == nil {
335 return []aws.CredentialSource{aws.CredentialSourceSTSAssumeRole}
336 } // If no source has been set, assume this is used directly which means just call to assume role
337 return append(p.options.CredentialSources, aws.CredentialSourceSTSAssumeRole)
338 }
339