assume_role_provider.go

  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}