vault/builtin/logical/aws/backend.go
Clint f39bafbf9c
Translate AWS Rate limiting errors to 502 errors (#5270)
* Initial implemntation of returning 529 for rate limits

- bump aws iam and sts packages to v1.14.31 to get mocking interface
- promote the iam and sts clients to the aws backend struct, for mocking in tests
- this also promotes some functions to methods on the Backend struct, so
  that we can use the injected client

Generating creds requires reading config/root for credentials to contact
IAM. Here we make pathConfigRoot a method on aws/backend so we can clear
the clients on successful update of config/root path. Adds a mutex to
safely clear the clients

* refactor locking and unlocking into methods on *backend

* refactor/simply the locking

* check client after grabbing lock
2018-09-18 15:26:06 -05:00

137 lines
3.1 KiB
Go

package aws
import (
"context"
"strings"
"sync"
"time"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"github.com/aws/aws-sdk-go/service/sts/stsiface"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend, error) {
b := Backend()
if err := b.Setup(ctx, conf); err != nil {
return nil, err
}
return b, nil
}
func Backend() *backend {
var b backend
b.Backend = &framework.Backend{
Help: strings.TrimSpace(backendHelp),
PathsSpecial: &logical.Paths{
LocalStorage: []string{
framework.WALPrefix,
},
SealWrapStorage: []string{
"config/root",
},
},
Paths: []*framework.Path{
pathConfigRoot(&b),
pathConfigLease(&b),
pathRoles(&b),
pathListRoles(&b),
pathUser(&b),
},
Secrets: []*framework.Secret{
secretAccessKeys(&b),
},
WALRollback: b.walRollback,
WALRollbackMinAge: 5 * time.Minute,
BackendType: logical.TypeLogical,
}
return &b
}
type backend struct {
*framework.Backend
// Mutex to protect access to reading and writing policies
roleMutex sync.RWMutex
// Mutex to protect access to iam/sts clients
clientMutex sync.RWMutex
// iamClient and stsClient hold configured iam and sts clients for reuse, and
// to enable mocking with AWS iface for tests
iamClient iamiface.IAMAPI
stsClient stsiface.STSAPI
}
const backendHelp = `
The AWS backend dynamically generates AWS access keys for a set of
IAM policies. The AWS access keys have a configurable lease set and
are automatically revoked at the end of the lease.
After mounting this backend, credentials to generate IAM keys must
be configured with the "root" path and policies must be written using
the "roles/" endpoints before any access keys can be generated.
`
// clientIAM returns the configured IAM client. If nil, it constructs a new one
// and returns it, setting it the internal variable
func (b *backend) clientIAM(ctx context.Context, s logical.Storage) (iamiface.IAMAPI, error) {
b.clientMutex.RLock()
if b.iamClient != nil {
b.clientMutex.RUnlock()
return b.iamClient, nil
}
// Upgrade the lock for writing
b.clientMutex.RUnlock()
b.clientMutex.Lock()
defer b.clientMutex.Unlock()
// check client again, in the event that a client was being created while we
// waited for Lock()
if b.iamClient != nil {
return b.iamClient, nil
}
iamClient, err := clientIAM(ctx, s)
if err != nil {
return nil, err
}
b.iamClient = iamClient
return b.iamClient, nil
}
func (b *backend) clientSTS(ctx context.Context, s logical.Storage) (stsiface.STSAPI, error) {
b.clientMutex.RLock()
if b.stsClient != nil {
b.clientMutex.RUnlock()
return b.stsClient, nil
}
// Upgrade the lock for writing
b.clientMutex.RUnlock()
b.clientMutex.Lock()
defer b.clientMutex.Unlock()
// check client again, in the event that a client was being created while we
// waited for Lock()
if b.stsClient != nil {
return b.stsClient, nil
}
stsClient, err := clientSTS(ctx, s)
if err != nil {
return nil, err
}
b.stsClient = stsClient
return b.stsClient, nil
}