sso_credentials_provider.go 4.5 KB

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