code.dwrz.net

Go monorepo.
Log | Files | Refs

sso_credentials_provider.go (4050B)


      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 
     50 // Provider is an AWS credential provider that retrieves temporary AWS
     51 // credentials by exchanging an SSO login token.
     52 type Provider struct {
     53 	options Options
     54 
     55 	cachedTokenFilepath string
     56 }
     57 
     58 // New returns a new AWS Single Sign-On (AWS SSO) credential provider. The
     59 // provided client is expected to be configured for the AWS Region where the
     60 // AWS SSO user portal is located.
     61 func New(client GetRoleCredentialsAPIClient, accountID, roleName, startURL string, optFns ...func(options *Options)) *Provider {
     62 	options := Options{
     63 		Client:    client,
     64 		AccountID: accountID,
     65 		RoleName:  roleName,
     66 		StartURL:  startURL,
     67 	}
     68 
     69 	for _, fn := range optFns {
     70 		fn(&options)
     71 	}
     72 
     73 	return &Provider{
     74 		options:             options,
     75 		cachedTokenFilepath: options.CachedTokenFilepath,
     76 	}
     77 }
     78 
     79 // Retrieve retrieves temporary AWS credentials from the configured Amazon
     80 // Single Sign-On (AWS SSO) user portal by exchanging the accessToken present
     81 // in ~/.aws/sso/cache.
     82 func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
     83 	if p.cachedTokenFilepath == "" {
     84 		cachedTokenFilepath, err := StandardCachedTokenFilepath(p.options.StartURL)
     85 		if err != nil {
     86 			return aws.Credentials{}, &InvalidTokenError{Err: err}
     87 		}
     88 		p.cachedTokenFilepath = cachedTokenFilepath
     89 	}
     90 
     91 	tokenFile, err := loadCachedToken(p.cachedTokenFilepath)
     92 	if err != nil {
     93 		return aws.Credentials{}, &InvalidTokenError{Err: err}
     94 	}
     95 
     96 	if tokenFile.ExpiresAt == nil || sdk.NowTime().After(time.Time(*tokenFile.ExpiresAt)) {
     97 		return aws.Credentials{}, &InvalidTokenError{}
     98 	}
     99 
    100 	output, err := p.options.Client.GetRoleCredentials(ctx, &sso.GetRoleCredentialsInput{
    101 		AccessToken: &tokenFile.AccessToken,
    102 		AccountId:   &p.options.AccountID,
    103 		RoleName:    &p.options.RoleName,
    104 	})
    105 	if err != nil {
    106 		return aws.Credentials{}, err
    107 	}
    108 
    109 	return aws.Credentials{
    110 		AccessKeyID:     aws.ToString(output.RoleCredentials.AccessKeyId),
    111 		SecretAccessKey: aws.ToString(output.RoleCredentials.SecretAccessKey),
    112 		SessionToken:    aws.ToString(output.RoleCredentials.SessionToken),
    113 		CanExpire:       true,
    114 		Expires:         time.Unix(0, output.RoleCredentials.Expiration*int64(time.Millisecond)).UTC(),
    115 		Source:          ProviderName,
    116 	}, nil
    117 }
    118 
    119 // InvalidTokenError is the error type that is returned if loaded token has
    120 // expired or is otherwise invalid. To refresh the SSO session run AWS SSO
    121 // login with the corresponding profile.
    122 type InvalidTokenError struct {
    123 	Err error
    124 }
    125 
    126 func (i *InvalidTokenError) Unwrap() error {
    127 	return i.Err
    128 }
    129 
    130 func (i *InvalidTokenError) Error() string {
    131 	const msg = "the SSO session has expired or is invalid"
    132 	if i.Err == nil {
    133 		return msg
    134 	}
    135 	return msg + ": " + i.Err.Error()
    136 }