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 }