assume_role_provider.go raw

   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