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