forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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 hashicorp#4385
- Loading branch information
1 parent
f88c1b3
commit 649c4e4
Showing
2 changed files
with
115 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package aws | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/service/iam" | ||
"github.com/hashicorp/vault/logical" | ||
"github.com/hashicorp/vault/logical/framework" | ||
) | ||
|
||
func pathConfigRotateRoot() *framework.Path { | ||
return &framework.Path{ | ||
Pattern: "config/rotate-root", | ||
Callbacks: map[logical.Operation]framework.OperationFunc{ | ||
logical.UpdateOperation: pathConfigRotateRootUpdate, | ||
}, | ||
|
||
HelpSynopsis: pathConfigRotateRootHelpSyn, | ||
HelpDescription: pathConfigRotateRootHelpDesc, | ||
} | ||
} | ||
|
||
func pathConfigRotateRootUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { | ||
// TODO: Add locking around reading/writing the config path/rootConfig | ||
rawRootConfig, err := req.Storage.Get(ctx, "config/root") | ||
if err != nil { | ||
return nil, err | ||
} | ||
if rawRootConfig == nil { | ||
return logical.ErrorResponse("no configuration found for config/root"), nil | ||
} | ||
var config rootConfig | ||
if err := rawRootConfig.DecodeJSON(&config); err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error reading root configuration: %v", err)), nil | ||
} | ||
|
||
if config.AccessKey == "" || config.SecretKey == "" { | ||
return logical.ErrorResponse("cannot call config/rotate-root when either access_key or secret_key is empty"), nil | ||
} | ||
|
||
client, err := clientIAM(ctx, req.Storage) | ||
if err == nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error retrieving IAM client: %v", err)), nil | ||
} | ||
if client == nil { | ||
return logical.ErrorResponse("nil IAM client"), nil | ||
} | ||
var getUserInput iam.GetUserInput // empty input means get current user | ||
getUserRes, err := client.GetUser(&getUserInput) | ||
if err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error calling GetUser: %v", err)), nil | ||
} | ||
if getUserRes == nil { | ||
return logical.ErrorResponse("nil response from GetUser"), nil | ||
} | ||
if getUserRes.User == nil { | ||
return logical.ErrorResponse("nil user returned from GetUser"), nil | ||
} | ||
if getUserRes.User.UserName == nil { | ||
return logical.ErrorResponse("nil UserName returnjd from GetUser"), nil | ||
} | ||
|
||
createAccessKeyInput := iam.CreateAccessKeyInput{ | ||
UserName: getUserRes.User.UserName, | ||
} | ||
createAccessKeyRes, err := client.CreateAccessKey(&createAccessKeyInput) | ||
if err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error calling CreateAccessKey: %v", err)), nil | ||
} | ||
if createAccessKeyRes.AccessKey == nil { | ||
return logical.ErrorResponse("nil response from CreateAccessKey"), nil | ||
} | ||
if createAccessKeyRes.AccessKey.AccessKeyId == nil || createAccessKeyRes.AccessKey.SecretAccessKey == nil { | ||
return logical.ErrorResponse("nil AccessKeyId or SecretAccessKey returned from CreateAccessKey"), nil | ||
} | ||
|
||
oldAccessKey := config.AccessKey | ||
|
||
config.AccessKey = *createAccessKeyRes.AccessKey.AccessKeyId | ||
config.SecretKey = *createAccessKeyRes.AccessKey.SecretAccessKey | ||
|
||
newEntry, err := logical.StorageEntryJSON("config/root", config) | ||
if err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error generating new root config: %v", err)), nil | ||
} | ||
// TODO: Should we return the full error message? Are there any scenarios in which it could expose | ||
// the underlying creds? (The idea of rotate-root is that it should never expose credentials.) | ||
if err := req.Storage.Put(ctx, newEntry); err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error saving new root config: %v", err)), nil | ||
} | ||
|
||
deleteAccessKeyInput := iam.DeleteAccessKeyInput{ | ||
AccessKeyId: aws.String(oldAccessKey), | ||
UserName: getUserRes.User.UserName, | ||
} | ||
_, err = client.DeleteAccessKey(&deleteAccessKeyInput) | ||
if err != nil { | ||
return logical.ErrorResponse(fmt.Sprintf("error deleting old access key: %v", err)), nil | ||
} | ||
|
||
return nil, nil | ||
} | ||
|
||
const pathConfigRotateRootHelpSyn = ` | ||
Request to rotate the AWS credentials used by Vault | ||
` | ||
|
||
const pathConfigRotateRootHelpDesc = ` | ||
This path attempts to rotate the AWS credentials used by Vault for this mount. | ||
It is only valid if Vault has been configured to use AWS IAM credentials via the | ||
config/root endpoint. | ||
` |