code.dwrz.net

Go monorepo.
Log | Files | Refs

assume_role_provider.go (13457B)


      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 
    252 // NewAssumeRoleProvider constructs and returns a credentials provider that
    253 // will retrieve credentials by assuming a IAM role using STS.
    254 func 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.
    270 func (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 	return aws.Credentials{
    312 		AccessKeyID:     *resp.Credentials.AccessKeyId,
    313 		SecretAccessKey: *resp.Credentials.SecretAccessKey,
    314 		SessionToken:    *resp.Credentials.SessionToken,
    315 		Source:          ProviderName,
    316 
    317 		CanExpire: true,
    318 		Expires:   *resp.Credentials.Expiration,
    319 	}, nil
    320 }