src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

sso_credentials_provider.go (5152B)


      1 package ssocreds
      2 
      3 import (
      4 	"context"
      5 	"time"
      6 
      7 	"github.com/aws/aws-sdk-go-v2/aws"
      8 	"github.com/aws/aws-sdk-go-v2/internal/sdk"
      9 	"github.com/aws/aws-sdk-go-v2/service/sso"
     10 )
     11 
     12 // ProviderName is the name of the provider used to specify the source of
     13 // credentials.
     14 const ProviderName = "SSOProvider"
     15 
     16 // GetRoleCredentialsAPIClient is a API client that implements the
     17 // GetRoleCredentials operation.
     18 type GetRoleCredentialsAPIClient interface {
     19 	GetRoleCredentials(context.Context, *sso.GetRoleCredentialsInput, ...func(*sso.Options)) (
     20 		*sso.GetRoleCredentialsOutput, error,
     21 	)
     22 }
     23 
     24 // Options is the Provider options structure.
     25 type Options struct {
     26 	// The Client which is configured for the AWS Region where the AWS SSO user
     27 	// portal is located.
     28 	Client GetRoleCredentialsAPIClient
     29 
     30 	// The AWS account that is assigned to the user.
     31 	AccountID string
     32 
     33 	// The role name that is assigned to the user.
     34 	RoleName string
     35 
     36 	// The URL that points to the organization's AWS Single Sign-On (AWS SSO)
     37 	// user portal.
     38 	StartURL string
     39 
     40 	// The filepath the cached token will be retrieved from. If unset Provider will
     41 	// use the startURL to determine the filepath at.
     42 	//
     43 	//    ~/.aws/sso/cache/<sha1-hex-encoded-startURL>.json
     44 	//
     45 	// If custom cached token filepath is used, the Provider's startUrl
     46 	// parameter will be ignored.
     47 	CachedTokenFilepath string
     48 
     49 	// Used by the SSOCredentialProvider if a token configuration
     50 	// profile is used in the shared config
     51 	SSOTokenProvider *SSOTokenProvider
     52 
     53 	// The chain of providers that was used to create this provider.
     54 	// These values are for reporting purposes and are not meant to be set up directly
     55 	CredentialSources []aws.CredentialSource
     56 }
     57 
     58 // Provider is an AWS credential provider that retrieves temporary AWS
     59 // credentials by exchanging an SSO login token.
     60 type Provider struct {
     61 	options Options
     62 
     63 	cachedTokenFilepath string
     64 }
     65 
     66 // New returns a new AWS Single Sign-On (AWS SSO) credential provider. The
     67 // provided client is expected to be configured for the AWS Region where the
     68 // AWS SSO user portal is located.
     69 func New(client GetRoleCredentialsAPIClient, accountID, roleName, startURL string, optFns ...func(options *Options)) *Provider {
     70 	options := Options{
     71 		Client:    client,
     72 		AccountID: accountID,
     73 		RoleName:  roleName,
     74 		StartURL:  startURL,
     75 	}
     76 
     77 	for _, fn := range optFns {
     78 		fn(&options)
     79 	}
     80 
     81 	return &Provider{
     82 		options:             options,
     83 		cachedTokenFilepath: options.CachedTokenFilepath,
     84 	}
     85 }
     86 
     87 // Retrieve retrieves temporary AWS credentials from the configured Amazon
     88 // Single Sign-On (AWS SSO) user portal by exchanging the accessToken present
     89 // in ~/.aws/sso/cache. However, if a token provider configuration exists
     90 // in the shared config, then we ought to use the token provider rather then
     91 // direct access on the cached token.
     92 func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
     93 	var accessToken *string
     94 	if p.options.SSOTokenProvider != nil {
     95 		token, err := p.options.SSOTokenProvider.RetrieveBearerToken(ctx)
     96 		if err != nil {
     97 			return aws.Credentials{}, err
     98 		}
     99 		accessToken = &token.Value
    100 	} else {
    101 		if p.cachedTokenFilepath == "" {
    102 			cachedTokenFilepath, err := StandardCachedTokenFilepath(p.options.StartURL)
    103 			if err != nil {
    104 				return aws.Credentials{}, &InvalidTokenError{Err: err}
    105 			}
    106 			p.cachedTokenFilepath = cachedTokenFilepath
    107 		}
    108 
    109 		tokenFile, err := loadCachedToken(p.cachedTokenFilepath)
    110 		if err != nil {
    111 			return aws.Credentials{}, &InvalidTokenError{Err: err}
    112 		}
    113 
    114 		if tokenFile.ExpiresAt == nil || sdk.NowTime().After(time.Time(*tokenFile.ExpiresAt)) {
    115 			return aws.Credentials{}, &InvalidTokenError{}
    116 		}
    117 		accessToken = &tokenFile.AccessToken
    118 	}
    119 
    120 	output, err := p.options.Client.GetRoleCredentials(ctx, &sso.GetRoleCredentialsInput{
    121 		AccessToken: accessToken,
    122 		AccountId:   &p.options.AccountID,
    123 		RoleName:    &p.options.RoleName,
    124 	})
    125 	if err != nil {
    126 		return aws.Credentials{}, err
    127 	}
    128 
    129 	return aws.Credentials{
    130 		AccessKeyID:     aws.ToString(output.RoleCredentials.AccessKeyId),
    131 		SecretAccessKey: aws.ToString(output.RoleCredentials.SecretAccessKey),
    132 		SessionToken:    aws.ToString(output.RoleCredentials.SessionToken),
    133 		CanExpire:       true,
    134 		Expires:         time.Unix(0, output.RoleCredentials.Expiration*int64(time.Millisecond)).UTC(),
    135 		Source:          ProviderName,
    136 		AccountID:       p.options.AccountID,
    137 	}, nil
    138 }
    139 
    140 // ProviderSources returns the credential chain that was used to construct this provider
    141 func (p *Provider) ProviderSources() []aws.CredentialSource {
    142 	if p.options.CredentialSources == nil {
    143 		return []aws.CredentialSource{aws.CredentialSourceSSO}
    144 	}
    145 	return p.options.CredentialSources
    146 }
    147 
    148 // InvalidTokenError is the error type that is returned if loaded token has
    149 // expired or is otherwise invalid. To refresh the SSO session run AWS SSO
    150 // login with the corresponding profile.
    151 type InvalidTokenError struct {
    152 	Err error
    153 }
    154 
    155 func (i *InvalidTokenError) Unwrap() error {
    156 	return i.Err
    157 }
    158 
    159 func (i *InvalidTokenError) Error() string {
    160 	const msg = "the SSO session has expired or is invalid"
    161 	if i.Err == nil {
    162 		return msg
    163 	}
    164 	return msg + ": " + i.Err.Error()
    165 }