202 lines
5.4 KiB
Go
202 lines
5.4 KiB
Go
package aws
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/fatih/structs"
|
|
"github.com/hashicorp/vault/logical"
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
)
|
|
|
|
func pathConfigClient(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "config/client",
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"access_key": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "Access key with permission to query instance metadata.",
|
|
},
|
|
|
|
"secret_key": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "Secret key with permission to query instance metadata.",
|
|
},
|
|
|
|
"region": &framework.FieldSchema{
|
|
Type: framework.TypeString,
|
|
Description: "Region for API calls. Defaults to the value of the AWS_REGION env var. Required.",
|
|
},
|
|
},
|
|
|
|
ExistenceCheck: b.pathConfigClientExistenceCheck,
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
logical.CreateOperation: b.pathConfigClientCreateUpdate,
|
|
logical.DeleteOperation: b.pathConfigClientDelete,
|
|
logical.ReadOperation: b.pathConfigClientRead,
|
|
logical.UpdateOperation: b.pathConfigClientCreateUpdate,
|
|
},
|
|
|
|
HelpSynopsis: pathConfigClientHelpSyn,
|
|
HelpDescription: pathConfigClientHelpDesc,
|
|
}
|
|
}
|
|
|
|
// Establishes dichotomy of request operation between CreateOperation and UpdateOperation.
|
|
// Returning 'true' forces an UpdateOperation, CreateOperation otherwise.
|
|
func (b *backend) pathConfigClientExistenceCheck(
|
|
req *logical.Request, data *framework.FieldData) (bool, error) {
|
|
b.configMutex.RLock()
|
|
defer b.configMutex.RUnlock()
|
|
|
|
entry, err := clientConfigEntry(req.Storage)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return entry != nil, nil
|
|
}
|
|
|
|
// Fetch the client configuration required to access the AWS API.
|
|
func clientConfigEntry(s logical.Storage) (*clientConfig, error) {
|
|
entry, err := s.Get("config/client")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if entry == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var result clientConfig
|
|
if err := entry.DecodeJSON(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
return &result, nil
|
|
}
|
|
|
|
func (b *backend) pathConfigClientRead(
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
b.configMutex.RLock()
|
|
defer b.configMutex.RUnlock()
|
|
|
|
clientConfig, err := clientConfigEntry(req.Storage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if clientConfig == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return &logical.Response{
|
|
Data: structs.New(clientConfig).Map(),
|
|
}, nil
|
|
}
|
|
|
|
func (b *backend) pathConfigClientDelete(
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
b.configMutex.Lock()
|
|
|
|
err := req.Storage.Delete("config/client")
|
|
if err != nil {
|
|
b.configMutex.Unlock()
|
|
return nil, err
|
|
}
|
|
|
|
b.configMutex.Unlock()
|
|
|
|
_, err = b.clientEC2(req.Storage, true)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating client with updated credentials: %s", err)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// pathConfigClientCreateUpdate is used to register the 'aws_secret_key' and 'aws_access_key'
|
|
// that can be used to interact with AWS EC2 API.
|
|
func (b *backend) pathConfigClientCreateUpdate(
|
|
req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
b.configMutex.Lock()
|
|
defer b.configMutex.Unlock()
|
|
|
|
configEntry, err := clientConfigEntry(req.Storage)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if configEntry == nil {
|
|
configEntry = &clientConfig{}
|
|
}
|
|
|
|
regionStr, ok := data.GetOk("region")
|
|
if ok {
|
|
configEntry.Region = regionStr.(string)
|
|
} else if req.Operation == logical.CreateOperation {
|
|
configEntry.Region = data.Get("region").(string)
|
|
}
|
|
|
|
changedCreds := false
|
|
|
|
accessKeyStr, ok := data.GetOk("access_key")
|
|
if ok {
|
|
if configEntry.AccessKey != accessKeyStr.(string) {
|
|
changedCreds = true
|
|
}
|
|
configEntry.AccessKey = accessKeyStr.(string)
|
|
} else if req.Operation == logical.CreateOperation {
|
|
// Use the default
|
|
configEntry.AccessKey = data.Get("access_key").(string)
|
|
}
|
|
|
|
secretKeyStr, ok := data.GetOk("secret_key")
|
|
if ok {
|
|
if configEntry.SecretKey != secretKeyStr.(string) {
|
|
changedCreds = true
|
|
}
|
|
configEntry.SecretKey = secretKeyStr.(string)
|
|
} else if req.Operation == logical.CreateOperation {
|
|
configEntry.SecretKey = data.Get("secret_key").(string)
|
|
}
|
|
|
|
entry, err := logical.StorageEntryJSON("config/client", configEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := req.Storage.Put(entry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if changedCreds {
|
|
// We have to be careful here to re-lock as we have a deferred unlock
|
|
// queued up and unlocking an unlocked mutex leads to a panic
|
|
b.configMutex.Unlock()
|
|
_, err = b.clientEC2(req.Storage, true)
|
|
b.configMutex.Lock()
|
|
if err != nil {
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
"error creating client with updated credentials: %s", err),
|
|
), nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// Struct to hold 'aws_access_key' and 'aws_secret_key' that are required to
|
|
// interact with the AWS EC2 API.
|
|
type clientConfig struct {
|
|
AccessKey string `json:"access_key" structs:"access_key" mapstructure:"access_key"`
|
|
SecretKey string `json:"secret_key" structs:"secret_key" mapstructure:"secret_key"`
|
|
Region string `json:"region" structs:"region" mapstructure:"region"`
|
|
}
|
|
|
|
const pathConfigClientHelpSyn = `
|
|
Configure the client credentials that are used to query instance details from AWS EC2 API.
|
|
`
|
|
|
|
const pathConfigClientHelpDesc = `
|
|
AWS auth backend makes API calls to retrieve EC2 instance metadata.
|
|
The aws_secret_key and aws_access_key registered with Vault should have the
|
|
permissions to make these API calls.
|
|
`
|