]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | /* |
2 | Package stscreds are credential Providers to retrieve STS AWS credentials. | |
3 | ||
4 | STS provides multiple ways to retrieve credentials which can be used when making | |
5 | future AWS service API operation calls. | |
6 | ||
7 | The SDK will ensure that per instance of credentials.Credentials all requests | |
8 | to refresh the credentials will be synchronized. But, the SDK is unable to | |
9 | ensure synchronous usage of the AssumeRoleProvider if the value is shared | |
10 | between multiple Credentials, Sessions or service clients. | |
11 | ||
12 | Assume Role | |
13 | ||
14 | To assume an IAM role using STS with the SDK you can create a new Credentials | |
15 | with the SDKs's stscreds package. | |
16 | ||
17 | // Initial credentials loaded from SDK's default credential chain. Such as | |
18 | // the environment, shared credentials (~/.aws/credentials), or EC2 Instance | |
19 | // Role. These credentials will be used to to make the STS Assume Role API. | |
20 | sess := session.Must(session.NewSession()) | |
21 | ||
22 | // Create the credentials from AssumeRoleProvider to assume the role | |
23 | // referenced by the "myRoleARN" ARN. | |
24 | creds := stscreds.NewCredentials(sess, "myRoleArn") | |
25 | ||
26 | // Create service client value configured for credentials | |
27 | // from assumed role. | |
28 | svc := s3.New(sess, &aws.Config{Credentials: creds}) | |
29 | ||
30 | Assume Role with static MFA Token | |
31 | ||
32 | To assume an IAM role with a MFA token you can either specify a MFA token code | |
33 | directly or provide a function to prompt the user each time the credentials | |
34 | need to refresh the role's credentials. Specifying the TokenCode should be used | |
35 | for short lived operations that will not need to be refreshed, and when you do | |
36 | not want to have direct control over the user provides their MFA token. | |
37 | ||
38 | With TokenCode the AssumeRoleProvider will be not be able to refresh the role's | |
39 | credentials. | |
40 | ||
41 | // Create the credentials from AssumeRoleProvider to assume the role | |
42 | // referenced by the "myRoleARN" ARN using the MFA token code provided. | |
43 | creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) { | |
44 | p.SerialNumber = aws.String("myTokenSerialNumber") | |
45 | p.TokenCode = aws.String("00000000") | |
46 | }) | |
47 | ||
48 | // Create service client value configured for credentials | |
49 | // from assumed role. | |
50 | svc := s3.New(sess, &aws.Config{Credentials: creds}) | |
51 | ||
52 | Assume Role with MFA Token Provider | |
53 | ||
54 | To assume an IAM role with MFA for longer running tasks where the credentials | |
55 | may need to be refreshed setting the TokenProvider field of AssumeRoleProvider | |
56 | will allow the credential provider to prompt for new MFA token code when the | |
57 | role's credentials need to be refreshed. | |
58 | ||
59 | The StdinTokenProvider function is available to prompt on stdin to retrieve | |
60 | the MFA token code from the user. You can also implement custom prompts by | |
61 | satisfing the TokenProvider function signature. | |
62 | ||
63 | Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will | |
64 | have undesirable results as the StdinTokenProvider will not be synchronized. A | |
65 | single Credentials with an AssumeRoleProvider can be shared safely. | |
66 | ||
67 | // Create the credentials from AssumeRoleProvider to assume the role | |
68 | // referenced by the "myRoleARN" ARN. Prompting for MFA token from stdin. | |
69 | creds := stscreds.NewCredentials(sess, "myRoleArn", func(p *stscreds.AssumeRoleProvider) { | |
70 | p.SerialNumber = aws.String("myTokenSerialNumber") | |
71 | p.TokenProvider = stscreds.StdinTokenProvider | |
72 | }) | |
73 | ||
74 | // Create service client value configured for credentials | |
75 | // from assumed role. | |
76 | svc := s3.New(sess, &aws.Config{Credentials: creds}) | |
77 | ||
78 | */ | |
79 | package stscreds | |
80 | ||
81 | import ( | |
82 | "fmt" | |
107c1cdb | 83 | "os" |
bae9f6d2 JC |
84 | "time" |
85 | ||
86 | "github.com/aws/aws-sdk-go/aws" | |
87 | "github.com/aws/aws-sdk-go/aws/awserr" | |
88 | "github.com/aws/aws-sdk-go/aws/client" | |
89 | "github.com/aws/aws-sdk-go/aws/credentials" | |
107c1cdb | 90 | "github.com/aws/aws-sdk-go/internal/sdkrand" |
bae9f6d2 JC |
91 | "github.com/aws/aws-sdk-go/service/sts" |
92 | ) | |
93 | ||
107c1cdb | 94 | // StdinTokenProvider will prompt on stderr and read from stdin for a string value. |
bae9f6d2 JC |
95 | // An error is returned if reading from stdin fails. |
96 | // | |
97 | // Use this function go read MFA tokens from stdin. The function makes no attempt | |
98 | // to make atomic prompts from stdin across multiple gorouties. | |
99 | // | |
100 | // Using StdinTokenProvider with multiple AssumeRoleProviders, or Credentials will | |
101 | // have undesirable results as the StdinTokenProvider will not be synchronized. A | |
102 | // single Credentials with an AssumeRoleProvider can be shared safely | |
103 | // | |
104 | // Will wait forever until something is provided on the stdin. | |
105 | func StdinTokenProvider() (string, error) { | |
106 | var v string | |
107c1cdb | 107 | fmt.Fprintf(os.Stderr, "Assume Role MFA token code: ") |
bae9f6d2 JC |
108 | _, err := fmt.Scanln(&v) |
109 | ||
110 | return v, err | |
111 | } | |
112 | ||
113 | // ProviderName provides a name of AssumeRole provider | |
114 | const ProviderName = "AssumeRoleProvider" | |
115 | ||
116 | // AssumeRoler represents the minimal subset of the STS client API used by this provider. | |
117 | type AssumeRoler interface { | |
118 | AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) | |
119 | } | |
120 | ||
121 | // DefaultDuration is the default amount of time in minutes that the credentials | |
122 | // will be valid for. | |
123 | var DefaultDuration = time.Duration(15) * time.Minute | |
124 | ||
125 | // AssumeRoleProvider retrieves temporary credentials from the STS service, and | |
126 | // keeps track of their expiration time. | |
127 | // | |
128 | // This credential provider will be used by the SDKs default credential change | |
129 | // when shared configuration is enabled, and the shared config or shared credentials | |
130 | // file configure assume role. See Session docs for how to do this. | |
131 | // | |
132 | // AssumeRoleProvider does not provide any synchronization and it is not safe | |
133 | // to share this value across multiple Credentials, Sessions, or service clients | |
134 | // without also sharing the same Credentials instance. | |
135 | type AssumeRoleProvider struct { | |
136 | credentials.Expiry | |
137 | ||
138 | // STS client to make assume role request with. | |
139 | Client AssumeRoler | |
140 | ||
141 | // Role to be assumed. | |
142 | RoleARN string | |
143 | ||
144 | // Session name, if you wish to reuse the credentials elsewhere. | |
145 | RoleSessionName string | |
146 | ||
147 | // Expiry duration of the STS credentials. Defaults to 15 minutes if not set. | |
148 | Duration time.Duration | |
149 | ||
150 | // Optional ExternalID to pass along, defaults to nil if not set. | |
151 | ExternalID *string | |
152 | ||
153 | // The policy plain text must be 2048 bytes or shorter. However, an internal | |
154 | // conversion compresses it into a packed binary format with a separate limit. | |
155 | // The PackedPolicySize response element indicates by percentage how close to | |
156 | // the upper size limit the policy is, with 100% equaling the maximum allowed | |
157 | // size. | |
158 | Policy *string | |
159 | ||
160 | // The identification number of the MFA device that is associated with the user | |
161 | // who is making the AssumeRole call. Specify this value if the trust policy | |
162 | // of the role being assumed includes a condition that requires MFA authentication. | |
163 | // The value is either the serial number for a hardware device (such as GAHT12345678) | |
164 | // or an Amazon Resource Name (ARN) for a virtual device (such as arn:aws:iam::123456789012:mfa/user). | |
165 | SerialNumber *string | |
166 | ||
167 | // The value provided by the MFA device, if the trust policy of the role being | |
168 | // assumed requires MFA (that is, if the policy includes a condition that tests | |
169 | // for MFA). If the role being assumed requires MFA and if the TokenCode value | |
170 | // is missing or expired, the AssumeRole call returns an "access denied" error. | |
171 | // | |
172 | // If SerialNumber is set and neither TokenCode nor TokenProvider are also | |
173 | // set an error will be returned. | |
174 | TokenCode *string | |
175 | ||
176 | // Async method of providing MFA token code for assuming an IAM role with MFA. | |
177 | // The value returned by the function will be used as the TokenCode in the Retrieve | |
178 | // call. See StdinTokenProvider for a provider that prompts and reads from stdin. | |
179 | // | |
180 | // This token provider will be called when ever the assumed role's | |
181 | // credentials need to be refreshed when SerialNumber is also set and | |
182 | // TokenCode is not set. | |
183 | // | |
184 | // If both TokenCode and TokenProvider is set, TokenProvider will be used and | |
185 | // TokenCode is ignored. | |
186 | TokenProvider func() (string, error) | |
187 | ||
188 | // ExpiryWindow will allow the credentials to trigger refreshing prior to | |
189 | // the credentials actually expiring. This is beneficial so race conditions | |
190 | // with expiring credentials do not cause request to fail unexpectedly | |
191 | // due to ExpiredTokenException exceptions. | |
192 | // | |
193 | // So a ExpiryWindow of 10s would cause calls to IsExpired() to return true | |
194 | // 10 seconds before the credentials are actually expired. | |
195 | // | |
196 | // If ExpiryWindow is 0 or less it will be ignored. | |
197 | ExpiryWindow time.Duration | |
107c1cdb ND |
198 | |
199 | // MaxJitterFrac reduces the effective Duration of each credential requested | |
200 | // by a random percentage between 0 and MaxJitterFraction. MaxJitterFrac must | |
201 | // have a value between 0 and 1. Any other value may lead to expected behavior. | |
202 | // With a MaxJitterFrac value of 0, default) will no jitter will be used. | |
203 | // | |
204 | // For example, with a Duration of 30m and a MaxJitterFrac of 0.1, the | |
205 | // AssumeRole call will be made with an arbitrary Duration between 27m and | |
206 | // 30m. | |
207 | // | |
208 | // MaxJitterFrac should not be negative. | |
209 | MaxJitterFrac float64 | |
bae9f6d2 JC |
210 | } |
211 | ||
212 | // NewCredentials returns a pointer to a new Credentials object wrapping the | |
213 | // AssumeRoleProvider. The credentials will expire every 15 minutes and the | |
214 | // role will be named after a nanosecond timestamp of this operation. | |
215 | // | |
216 | // Takes a Config provider to create the STS client. The ConfigProvider is | |
217 | // satisfied by the session.Session type. | |
218 | // | |
219 | // It is safe to share the returned Credentials with multiple Sessions and | |
220 | // service clients. All access to the credentials and refreshing them | |
221 | // will be synchronized. | |
222 | func NewCredentials(c client.ConfigProvider, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials { | |
223 | p := &AssumeRoleProvider{ | |
224 | Client: sts.New(c), | |
225 | RoleARN: roleARN, | |
226 | Duration: DefaultDuration, | |
227 | } | |
228 | ||
229 | for _, option := range options { | |
230 | option(p) | |
231 | } | |
232 | ||
233 | return credentials.NewCredentials(p) | |
234 | } | |
235 | ||
236 | // NewCredentialsWithClient returns a pointer to a new Credentials object wrapping the | |
237 | // AssumeRoleProvider. The credentials will expire every 15 minutes and the | |
238 | // role will be named after a nanosecond timestamp of this operation. | |
239 | // | |
240 | // Takes an AssumeRoler which can be satisfied by the STS client. | |
241 | // | |
242 | // It is safe to share the returned Credentials with multiple Sessions and | |
243 | // service clients. All access to the credentials and refreshing them | |
244 | // will be synchronized. | |
245 | func NewCredentialsWithClient(svc AssumeRoler, roleARN string, options ...func(*AssumeRoleProvider)) *credentials.Credentials { | |
246 | p := &AssumeRoleProvider{ | |
247 | Client: svc, | |
248 | RoleARN: roleARN, | |
249 | Duration: DefaultDuration, | |
250 | } | |
251 | ||
252 | for _, option := range options { | |
253 | option(p) | |
254 | } | |
255 | ||
256 | return credentials.NewCredentials(p) | |
257 | } | |
258 | ||
259 | // Retrieve generates a new set of temporary credentials using STS. | |
260 | func (p *AssumeRoleProvider) Retrieve() (credentials.Value, error) { | |
261 | ||
262 | // Apply defaults where parameters are not set. | |
263 | if p.RoleSessionName == "" { | |
264 | // Try to work out a role name that will hopefully end up unique. | |
265 | p.RoleSessionName = fmt.Sprintf("%d", time.Now().UTC().UnixNano()) | |
266 | } | |
267 | if p.Duration == 0 { | |
268 | // Expire as often as AWS permits. | |
269 | p.Duration = DefaultDuration | |
270 | } | |
107c1cdb | 271 | jitter := time.Duration(sdkrand.SeededRand.Float64() * p.MaxJitterFrac * float64(p.Duration)) |
bae9f6d2 | 272 | input := &sts.AssumeRoleInput{ |
107c1cdb | 273 | DurationSeconds: aws.Int64(int64((p.Duration - jitter) / time.Second)), |
bae9f6d2 JC |
274 | RoleArn: aws.String(p.RoleARN), |
275 | RoleSessionName: aws.String(p.RoleSessionName), | |
276 | ExternalId: p.ExternalID, | |
277 | } | |
278 | if p.Policy != nil { | |
279 | input.Policy = p.Policy | |
280 | } | |
281 | if p.SerialNumber != nil { | |
282 | if p.TokenCode != nil { | |
283 | input.SerialNumber = p.SerialNumber | |
284 | input.TokenCode = p.TokenCode | |
285 | } else if p.TokenProvider != nil { | |
286 | input.SerialNumber = p.SerialNumber | |
287 | code, err := p.TokenProvider() | |
288 | if err != nil { | |
289 | return credentials.Value{ProviderName: ProviderName}, err | |
290 | } | |
291 | input.TokenCode = aws.String(code) | |
292 | } else { | |
293 | return credentials.Value{ProviderName: ProviderName}, | |
294 | awserr.New("AssumeRoleTokenNotAvailable", | |
295 | "assume role with MFA enabled, but neither TokenCode nor TokenProvider are set", nil) | |
296 | } | |
297 | } | |
298 | ||
299 | roleOutput, err := p.Client.AssumeRole(input) | |
300 | if err != nil { | |
301 | return credentials.Value{ProviderName: ProviderName}, err | |
302 | } | |
303 | ||
304 | // We will proactively generate new credentials before they expire. | |
305 | p.SetExpiration(*roleOutput.Credentials.Expiration, p.ExpiryWindow) | |
306 | ||
307 | return credentials.Value{ | |
308 | AccessKeyID: *roleOutput.Credentials.AccessKeyId, | |
309 | SecretAccessKey: *roleOutput.Credentials.SecretAccessKey, | |
310 | SessionToken: *roleOutput.Credentials.SessionToken, | |
311 | ProviderName: ProviderName, | |
312 | }, nil | |
313 | } |