]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | // Package credentials provides credential retrieval and management |
2 | // | |
3 | // The Credentials is the primary method of getting access to and managing | |
4 | // credentials Values. Using dependency injection retrieval of the credential | |
5 | // values is handled by a object which satisfies the Provider interface. | |
6 | // | |
7 | // By default the Credentials.Get() will cache the successful result of a | |
8 | // Provider's Retrieve() until Provider.IsExpired() returns true. At which | |
9 | // point Credentials will call Provider's Retrieve() to get new credential Value. | |
10 | // | |
11 | // The Provider is responsible for determining when credentials Value have expired. | |
12 | // It is also important to note that Credentials will always call Retrieve the | |
13 | // first time Credentials.Get() is called. | |
14 | // | |
15 | // Example of using the environment variable credentials. | |
16 | // | |
17 | // creds := credentials.NewEnvCredentials() | |
18 | // | |
19 | // // Retrieve the credentials value | |
20 | // credValue, err := creds.Get() | |
21 | // if err != nil { | |
22 | // // handle error | |
23 | // } | |
24 | // | |
25 | // Example of forcing credentials to expire and be refreshed on the next Get(). | |
26 | // This may be helpful to proactively expire credentials and refresh them sooner | |
27 | // than they would naturally expire on their own. | |
28 | // | |
29 | // creds := credentials.NewCredentials(&ec2rolecreds.EC2RoleProvider{}) | |
30 | // creds.Expire() | |
31 | // credsValue, err := creds.Get() | |
32 | // // New credentials will be retrieved instead of from cache. | |
33 | // | |
34 | // | |
35 | // Custom Provider | |
36 | // | |
37 | // Each Provider built into this package also provides a helper method to generate | |
38 | // a Credentials pointer setup with the provider. To use a custom Provider just | |
39 | // create a type which satisfies the Provider interface and pass it to the | |
40 | // NewCredentials method. | |
41 | // | |
42 | // type MyProvider struct{} | |
43 | // func (m *MyProvider) Retrieve() (Value, error) {...} | |
44 | // func (m *MyProvider) IsExpired() bool {...} | |
45 | // | |
46 | // creds := credentials.NewCredentials(&MyProvider{}) | |
47 | // credValue, err := creds.Get() | |
48 | // | |
49 | package credentials | |
50 | ||
51 | import ( | |
107c1cdb ND |
52 | "fmt" |
53 | "github.com/aws/aws-sdk-go/aws/awserr" | |
bae9f6d2 JC |
54 | "sync" |
55 | "time" | |
56 | ) | |
57 | ||
58 | // AnonymousCredentials is an empty Credential object that can be used as | |
59 | // dummy placeholder credentials for requests that do not need signed. | |
60 | // | |
61 | // This Credentials can be used to configure a service to not sign requests | |
62 | // when making service API calls. For example, when accessing public | |
63 | // s3 buckets. | |
64 | // | |
65 | // svc := s3.New(session.Must(session.NewSession(&aws.Config{ | |
66 | // Credentials: credentials.AnonymousCredentials, | |
67 | // }))) | |
68 | // // Access public S3 buckets. | |
bae9f6d2 JC |
69 | var AnonymousCredentials = NewStaticCredentials("", "", "") |
70 | ||
71 | // A Value is the AWS credentials value for individual credential fields. | |
72 | type Value struct { | |
73 | // AWS Access key ID | |
74 | AccessKeyID string | |
75 | ||
76 | // AWS Secret Access Key | |
77 | SecretAccessKey string | |
78 | ||
79 | // AWS Session Token | |
80 | SessionToken string | |
81 | ||
82 | // Provider used to get credentials | |
83 | ProviderName string | |
84 | } | |
85 | ||
86 | // A Provider is the interface for any component which will provide credentials | |
87 | // Value. A provider is required to manage its own Expired state, and what to | |
88 | // be expired means. | |
89 | // | |
90 | // The Provider should not need to implement its own mutexes, because | |
91 | // that will be managed by Credentials. | |
92 | type Provider interface { | |
93 | // Retrieve returns nil if it successfully retrieved the value. | |
94 | // Error is returned if the value were not obtainable, or empty. | |
95 | Retrieve() (Value, error) | |
96 | ||
97 | // IsExpired returns if the credentials are no longer valid, and need | |
98 | // to be retrieved. | |
99 | IsExpired() bool | |
100 | } | |
101 | ||
107c1cdb ND |
102 | // An Expirer is an interface that Providers can implement to expose the expiration |
103 | // time, if known. If the Provider cannot accurately provide this info, | |
104 | // it should not implement this interface. | |
105 | type Expirer interface { | |
106 | // The time at which the credentials are no longer valid | |
107 | ExpiresAt() time.Time | |
108 | } | |
109 | ||
bae9f6d2 JC |
110 | // An ErrorProvider is a stub credentials provider that always returns an error |
111 | // this is used by the SDK when construction a known provider is not possible | |
112 | // due to an error. | |
113 | type ErrorProvider struct { | |
114 | // The error to be returned from Retrieve | |
115 | Err error | |
116 | ||
117 | // The provider name to set on the Retrieved returned Value | |
118 | ProviderName string | |
119 | } | |
120 | ||
121 | // Retrieve will always return the error that the ErrorProvider was created with. | |
122 | func (p ErrorProvider) Retrieve() (Value, error) { | |
123 | return Value{ProviderName: p.ProviderName}, p.Err | |
124 | } | |
125 | ||
126 | // IsExpired will always return not expired. | |
127 | func (p ErrorProvider) IsExpired() bool { | |
128 | return false | |
129 | } | |
130 | ||
131 | // A Expiry provides shared expiration logic to be used by credentials | |
132 | // providers to implement expiry functionality. | |
133 | // | |
134 | // The best method to use this struct is as an anonymous field within the | |
135 | // provider's struct. | |
136 | // | |
137 | // Example: | |
138 | // type EC2RoleProvider struct { | |
139 | // Expiry | |
140 | // ... | |
141 | // } | |
142 | type Expiry struct { | |
143 | // The date/time when to expire on | |
144 | expiration time.Time | |
145 | ||
146 | // If set will be used by IsExpired to determine the current time. | |
147 | // Defaults to time.Now if CurrentTime is not set. Available for testing | |
148 | // to be able to mock out the current time. | |
149 | CurrentTime func() time.Time | |
150 | } | |
151 | ||
152 | // SetExpiration sets the expiration IsExpired will check when called. | |
153 | // | |
154 | // If window is greater than 0 the expiration time will be reduced by the | |
155 | // window value. | |
156 | // | |
157 | // Using a window is helpful to trigger credentials to expire sooner than | |
158 | // the expiration time given to ensure no requests are made with expired | |
159 | // tokens. | |
160 | func (e *Expiry) SetExpiration(expiration time.Time, window time.Duration) { | |
161 | e.expiration = expiration | |
162 | if window > 0 { | |
163 | e.expiration = e.expiration.Add(-window) | |
164 | } | |
165 | } | |
166 | ||
167 | // IsExpired returns if the credentials are expired. | |
168 | func (e *Expiry) IsExpired() bool { | |
107c1cdb ND |
169 | curTime := e.CurrentTime |
170 | if curTime == nil { | |
171 | curTime = time.Now | |
bae9f6d2 | 172 | } |
107c1cdb | 173 | return e.expiration.Before(curTime()) |
bae9f6d2 JC |
174 | } |
175 | ||
107c1cdb ND |
176 | // ExpiresAt returns the expiration time of the credential |
177 | func (e *Expiry) ExpiresAt() time.Time { | |
178 | return e.expiration | |
179 | } | |
180 | ||
181 | // A Credentials provides concurrency safe retrieval of AWS credentials Value. | |
bae9f6d2 JC |
182 | // Credentials will cache the credentials value until they expire. Once the value |
183 | // expires the next Get will attempt to retrieve valid credentials. | |
184 | // | |
185 | // Credentials is safe to use across multiple goroutines and will manage the | |
186 | // synchronous state so the Providers do not need to implement their own | |
187 | // synchronization. | |
188 | // | |
189 | // The first Credentials.Get() will always call Provider.Retrieve() to get the | |
190 | // first instance of the credentials Value. All calls to Get() after that | |
191 | // will return the cached credentials Value until IsExpired() returns true. | |
192 | type Credentials struct { | |
193 | creds Value | |
194 | forceRefresh bool | |
15c0b25d AP |
195 | |
196 | m sync.RWMutex | |
bae9f6d2 JC |
197 | |
198 | provider Provider | |
199 | } | |
200 | ||
201 | // NewCredentials returns a pointer to a new Credentials with the provider set. | |
202 | func NewCredentials(provider Provider) *Credentials { | |
203 | return &Credentials{ | |
204 | provider: provider, | |
205 | forceRefresh: true, | |
206 | } | |
207 | } | |
208 | ||
209 | // Get returns the credentials value, or error if the credentials Value failed | |
210 | // to be retrieved. | |
211 | // | |
212 | // Will return the cached credentials Value if it has not expired. If the | |
213 | // credentials Value has expired the Provider's Retrieve() will be called | |
214 | // to refresh the credentials. | |
215 | // | |
216 | // If Credentials.Expire() was called the credentials Value will be force | |
217 | // expired, and the next call to Get() will cause them to be refreshed. | |
218 | func (c *Credentials) Get() (Value, error) { | |
15c0b25d AP |
219 | // Check the cached credentials first with just the read lock. |
220 | c.m.RLock() | |
221 | if !c.isExpired() { | |
222 | creds := c.creds | |
223 | c.m.RUnlock() | |
224 | return creds, nil | |
225 | } | |
226 | c.m.RUnlock() | |
227 | ||
228 | // Credentials are expired need to retrieve the credentials taking the full | |
229 | // lock. | |
bae9f6d2 JC |
230 | c.m.Lock() |
231 | defer c.m.Unlock() | |
232 | ||
233 | if c.isExpired() { | |
234 | creds, err := c.provider.Retrieve() | |
235 | if err != nil { | |
236 | return Value{}, err | |
237 | } | |
238 | c.creds = creds | |
239 | c.forceRefresh = false | |
240 | } | |
241 | ||
242 | return c.creds, nil | |
243 | } | |
244 | ||
245 | // Expire expires the credentials and forces them to be retrieved on the | |
246 | // next call to Get(). | |
247 | // | |
248 | // This will override the Provider's expired state, and force Credentials | |
249 | // to call the Provider's Retrieve(). | |
250 | func (c *Credentials) Expire() { | |
251 | c.m.Lock() | |
252 | defer c.m.Unlock() | |
253 | ||
254 | c.forceRefresh = true | |
255 | } | |
256 | ||
257 | // IsExpired returns if the credentials are no longer valid, and need | |
258 | // to be retrieved. | |
259 | // | |
260 | // If the Credentials were forced to be expired with Expire() this will | |
261 | // reflect that override. | |
262 | func (c *Credentials) IsExpired() bool { | |
15c0b25d AP |
263 | c.m.RLock() |
264 | defer c.m.RUnlock() | |
bae9f6d2 JC |
265 | |
266 | return c.isExpired() | |
267 | } | |
268 | ||
269 | // isExpired helper method wrapping the definition of expired credentials. | |
270 | func (c *Credentials) isExpired() bool { | |
271 | return c.forceRefresh || c.provider.IsExpired() | |
272 | } | |
107c1cdb ND |
273 | |
274 | // ExpiresAt provides access to the functionality of the Expirer interface of | |
275 | // the underlying Provider, if it supports that interface. Otherwise, it returns | |
276 | // an error. | |
277 | func (c *Credentials) ExpiresAt() (time.Time, error) { | |
278 | c.m.RLock() | |
279 | defer c.m.RUnlock() | |
280 | ||
281 | expirer, ok := c.provider.(Expirer) | |
282 | if !ok { | |
283 | return time.Time{}, awserr.New("ProviderNotExpirer", | |
284 | fmt.Sprintf("provider %s does not support ExpiresAt()", c.creds.ProviderName), | |
285 | nil) | |
286 | } | |
287 | if c.forceRefresh { | |
288 | // set expiration time to the distant past | |
289 | return time.Time{}, nil | |
290 | } | |
291 | return expirer.ExpiresAt(), nil | |
292 | } |