sso_credentials_provider.go

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