5e6f8904d8
* Add AWS Secret Engine Root Credential Rotation This allows the AWS Secret Engine to rotate its credentials used to access AWS. This will only work when the AWS Secret Engine has been provided explicit IAM credentials via the config/root endpoint, and further, when the IAM credentials provided are the only access key on the IAM user associated wtih the access key (because AWS allows a maximum of 2 access keys per user). Fixes #4385 * Add test for AWS root credential rotation Also fix a typo in the root credential rotation code * Add docs for AWS root rotation * Add locks around reading and writing config/root And wire the backend up in a bunch of places so the config can get the lock * Respond to PR feedback * Fix casing in error messages * Fix merge errors * Fix locking bugs
138 lines
3.1 KiB
Go
138 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),
|
|
pathConfigRotateRoot(&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 and client configs
|
|
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 := nonCachedClientIAM(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 := nonCachedClientSTS(ctx, s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.stsClient = stsClient
|
|
|
|
return b.stsClient, nil
|
|
}
|