275 lines
8.1 KiB
Go
275 lines
8.1 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package awsauth
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
// awsStsEntry is used to store details of an STS role for assumption
|
|
type awsStsEntry struct {
|
|
StsRole string `json:"sts_role"`
|
|
}
|
|
|
|
func (b *backend) pathListSts() *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "config/sts/?",
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixAWS,
|
|
OperationSuffix: "sts-role-relationships",
|
|
},
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.ListOperation: &framework.PathOperation{
|
|
Callback: b.pathStsList,
|
|
},
|
|
},
|
|
|
|
HelpSynopsis: pathListStsHelpSyn,
|
|
HelpDescription: pathListStsHelpDesc,
|
|
}
|
|
}
|
|
|
|
func (b *backend) pathConfigSts() *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "config/sts/" + framework.GenericNameRegex("account_id"),
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixAWS,
|
|
OperationSuffix: "sts-role",
|
|
},
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"account_id": {
|
|
Type: framework.TypeString,
|
|
Description: `AWS account ID to be associated with STS role. If set,
|
|
Vault will use assumed credentials to verify any login attempts from EC2
|
|
instances in this account.`,
|
|
},
|
|
"sts_role": {
|
|
Type: framework.TypeString,
|
|
Description: `AWS ARN for STS role to be assumed when interacting with the account specified.
|
|
The Vault server must have permissions to assume this role.`,
|
|
},
|
|
},
|
|
|
|
ExistenceCheck: b.pathConfigStsExistenceCheck,
|
|
|
|
Operations: map[logical.Operation]framework.OperationHandler{
|
|
logical.CreateOperation: &framework.PathOperation{
|
|
Callback: b.pathConfigStsCreateUpdate,
|
|
},
|
|
logical.UpdateOperation: &framework.PathOperation{
|
|
Callback: b.pathConfigStsCreateUpdate,
|
|
},
|
|
logical.ReadOperation: &framework.PathOperation{
|
|
Callback: b.pathConfigStsRead,
|
|
},
|
|
logical.DeleteOperation: &framework.PathOperation{
|
|
Callback: b.pathConfigStsDelete,
|
|
},
|
|
},
|
|
|
|
HelpSynopsis: pathConfigStsSyn,
|
|
HelpDescription: pathConfigStsDesc,
|
|
}
|
|
}
|
|
|
|
// Establishes dichotomy of request operation between CreateOperation and UpdateOperation.
|
|
// Returning 'true' forces an UpdateOperation, CreateOperation otherwise.
|
|
func (b *backend) pathConfigStsExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
|
|
accountID := data.Get("account_id").(string)
|
|
if accountID == "" {
|
|
return false, fmt.Errorf("missing account_id")
|
|
}
|
|
|
|
entry, err := b.lockedAwsStsEntry(ctx, req.Storage, accountID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return entry != nil, nil
|
|
}
|
|
|
|
// pathStsList is used to list all the AWS STS role configurations
|
|
func (b *backend) pathStsList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
b.configMutex.RLock()
|
|
defer b.configMutex.RUnlock()
|
|
sts, err := req.Storage.List(ctx, "config/sts/")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return logical.ListResponse(sts), nil
|
|
}
|
|
|
|
// nonLockedSetAwsStsEntry creates or updates an STS role association with the given accountID
|
|
// This method does not acquire the write lock before creating or updating. If locking is
|
|
// desired, use lockedSetAwsStsEntry instead
|
|
func (b *backend) nonLockedSetAwsStsEntry(ctx context.Context, s logical.Storage, accountID string, stsEntry *awsStsEntry) error {
|
|
if accountID == "" {
|
|
return fmt.Errorf("missing AWS account ID")
|
|
}
|
|
|
|
if stsEntry == nil {
|
|
return fmt.Errorf("missing AWS STS Role ARN")
|
|
}
|
|
|
|
entry, err := logical.StorageEntryJSON("config/sts/"+accountID, stsEntry)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if entry == nil {
|
|
return fmt.Errorf("failed to create storage entry for AWS STS configuration")
|
|
}
|
|
|
|
return s.Put(ctx, entry)
|
|
}
|
|
|
|
// lockedSetAwsStsEntry creates or updates an STS role association with the given accountID
|
|
// This method acquires the write lock before creating or updating the STS entry.
|
|
func (b *backend) lockedSetAwsStsEntry(ctx context.Context, s logical.Storage, accountID string, stsEntry *awsStsEntry) error {
|
|
if accountID == "" {
|
|
return fmt.Errorf("missing AWS account ID")
|
|
}
|
|
|
|
if stsEntry == nil {
|
|
return fmt.Errorf("missing sts entry")
|
|
}
|
|
|
|
b.configMutex.Lock()
|
|
defer b.configMutex.Unlock()
|
|
|
|
return b.nonLockedSetAwsStsEntry(ctx, s, accountID, stsEntry)
|
|
}
|
|
|
|
// nonLockedAwsStsEntry returns the STS role associated with the given accountID.
|
|
// This method does not acquire the read lock before returning information. If locking is
|
|
// desired, use lockedAwsStsEntry instead
|
|
func (b *backend) nonLockedAwsStsEntry(ctx context.Context, s logical.Storage, accountID string) (*awsStsEntry, error) {
|
|
entry, err := s.Get(ctx, "config/sts/"+accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if entry == nil {
|
|
return nil, nil
|
|
}
|
|
var stsEntry awsStsEntry
|
|
if err := entry.DecodeJSON(&stsEntry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &stsEntry, nil
|
|
}
|
|
|
|
// lockedAwsStsEntry returns the STS role associated with the given accountID.
|
|
// This method acquires the read lock before returning the association.
|
|
func (b *backend) lockedAwsStsEntry(ctx context.Context, s logical.Storage, accountID string) (*awsStsEntry, error) {
|
|
b.configMutex.RLock()
|
|
defer b.configMutex.RUnlock()
|
|
|
|
return b.nonLockedAwsStsEntry(ctx, s, accountID)
|
|
}
|
|
|
|
// pathConfigStsRead is used to return information about an STS role/AWS accountID association
|
|
func (b *backend) pathConfigStsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
accountID := data.Get("account_id").(string)
|
|
if accountID == "" {
|
|
return logical.ErrorResponse("missing account id"), nil
|
|
}
|
|
|
|
stsEntry, err := b.lockedAwsStsEntry(ctx, req.Storage, accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if stsEntry == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
return &logical.Response{
|
|
Data: map[string]interface{}{
|
|
"sts_role": stsEntry.StsRole,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// pathConfigStsCreateUpdate is used to associate an STS role with a given AWS accountID
|
|
func (b *backend) pathConfigStsCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
accountID := data.Get("account_id").(string)
|
|
if accountID == "" {
|
|
return logical.ErrorResponse("missing AWS account ID"), nil
|
|
}
|
|
|
|
b.configMutex.Lock()
|
|
defer b.configMutex.Unlock()
|
|
|
|
// Check if an STS role is already registered
|
|
stsEntry, err := b.nonLockedAwsStsEntry(ctx, req.Storage, accountID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if stsEntry == nil {
|
|
stsEntry = &awsStsEntry{}
|
|
}
|
|
|
|
// Check that an STS role has actually been provided
|
|
stsRole, ok := data.GetOk("sts_role")
|
|
if ok {
|
|
stsEntry.StsRole = stsRole.(string)
|
|
} else if req.Operation == logical.CreateOperation {
|
|
return logical.ErrorResponse("missing sts role"), nil
|
|
}
|
|
|
|
if stsEntry.StsRole == "" {
|
|
return logical.ErrorResponse("sts role cannot be empty"), nil
|
|
}
|
|
|
|
// save the provided STS role
|
|
if err := b.nonLockedSetAwsStsEntry(ctx, req.Storage, accountID, stsEntry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// pathConfigStsDelete is used to delete a previously configured STS configuration
|
|
func (b *backend) pathConfigStsDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
|
b.configMutex.Lock()
|
|
defer b.configMutex.Unlock()
|
|
|
|
accountID := data.Get("account_id").(string)
|
|
if accountID == "" {
|
|
return logical.ErrorResponse("missing account id"), nil
|
|
}
|
|
|
|
return nil, req.Storage.Delete(ctx, "config/sts/"+accountID)
|
|
}
|
|
|
|
const pathConfigStsSyn = `
|
|
Specify STS roles to be assumed for certain AWS accounts.
|
|
`
|
|
|
|
const pathConfigStsDesc = `
|
|
Allows the explicit association of STS roles to satellite AWS accounts (i.e. those
|
|
which are not the account in which the Vault server is running.) Login attempts from
|
|
EC2 instances running in these accounts will be verified using credentials obtained
|
|
by assumption of these STS roles.
|
|
|
|
The environment in which the Vault server resides must have access to assume the
|
|
given STS roles.
|
|
`
|
|
|
|
const pathListStsHelpSyn = `
|
|
List all the AWS account/STS role relationships registered with Vault.
|
|
`
|
|
|
|
const pathListStsHelpDesc = `
|
|
AWS accounts will be listed by account ID, along with their respective role names.
|
|
`
|