...
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
13
14 const ProviderName = "SSOProvider"
15
16
17
18 type GetRoleCredentialsAPIClient interface {
19 GetRoleCredentials(context.Context, *sso.GetRoleCredentialsInput, ...func(*sso.Options)) (
20 *sso.GetRoleCredentialsOutput, error,
21 )
22 }
23
24
25 type Options struct {
26
27
28 Client GetRoleCredentialsAPIClient
29
30
31 AccountID string
32
33
34 RoleName string
35
36
37
38 StartURL string
39
40
41
42
43
44
45
46
47 CachedTokenFilepath string
48
49
50
51 SSOTokenProvider *SSOTokenProvider
52 }
53
54
55
56 type Provider struct {
57 options Options
58
59 cachedTokenFilepath string
60 }
61
62
63
64
65 func 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
84
85
86
87
88 func (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 }, nil
133 }
134
135
136
137
138 type InvalidTokenError struct {
139 Err error
140 }
141
142 func (i *InvalidTokenError) Unwrap() error {
143 return i.Err
144 }
145
146 func (i *InvalidTokenError) Error() string {
147 const msg = "the SSO session has expired or is invalid"
148 if i.Err == nil {
149 return msg
150 }
151 return msg + ": " + i.Err.Error()
152 }
153
View as plain text