mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
Add option to configure ec2_alias values (#5846)
* Add option to configure ec2_alias values * Doc updates * Fix overwriting of previous config value * s/configEntry/config * Fix formatting * Address review feedback * Address review feedback
This commit is contained in:
parent
ba2d6bd498
commit
77978055fe
@ -13,11 +13,16 @@ func pathConfigIdentity(b *backend) *framework.Path {
|
||||
return &framework.Path{
|
||||
Pattern: "config/identity$",
|
||||
Fields: map[string]*framework.FieldSchema{
|
||||
"iam_alias": &framework.FieldSchema{
|
||||
"iam_alias": {
|
||||
Type: framework.TypeString,
|
||||
Default: identityAliasIAMUniqueID,
|
||||
Description: fmt.Sprintf("Configure how the AWS auth method generates entity aliases when using IAM auth. Valid values are %q and %q", identityAliasIAMUniqueID, identityAliasIAMFullArn),
|
||||
},
|
||||
"ec2_alias": {
|
||||
Type: framework.TypeString,
|
||||
Default: identityAliasEC2InstanceID,
|
||||
Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q and %q", identityAliasEC2InstanceID, identityAliasEC2ImageID),
|
||||
},
|
||||
},
|
||||
|
||||
Callbacks: map[logical.Operation]framework.OperationFunc{
|
||||
@ -30,27 +35,54 @@ func pathConfigIdentity(b *backend) *framework.Path {
|
||||
}
|
||||
}
|
||||
|
||||
func pathConfigIdentityRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
entry, err := req.Storage.Get(ctx, "config/identity")
|
||||
func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfig, error) {
|
||||
entryRaw, err := s.Get(ctx, "config/identity")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if entry == nil {
|
||||
return nil, nil
|
||||
|
||||
var entry identityConfig
|
||||
if entryRaw == nil {
|
||||
entry.IAMAlias = identityAliasIAMUniqueID
|
||||
entry.EC2Alias = identityAliasEC2InstanceID
|
||||
return &entry, nil
|
||||
}
|
||||
var result identityConfig
|
||||
if err := entry.DecodeJSON(&result); err != nil {
|
||||
|
||||
err = entryRaw.DecodeJSON(&entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if entry.IAMAlias == "" {
|
||||
entry.IAMAlias = identityAliasIAMUniqueID
|
||||
}
|
||||
|
||||
if entry.EC2Alias == "" {
|
||||
entry.EC2Alias = identityAliasEC2InstanceID
|
||||
}
|
||||
|
||||
return &entry, nil
|
||||
}
|
||||
|
||||
func pathConfigIdentityRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
config, err := identityConfigEntry(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"iam_alias": result.IAMAlias,
|
||||
"iam_alias": config.IAMAlias,
|
||||
"ec2_alias": config.EC2Alias,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
var configEntry identityConfig
|
||||
config, err := identityConfigEntry(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamAliasRaw, ok := data.GetOk("iam_alias")
|
||||
if ok {
|
||||
@ -59,24 +91,41 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f
|
||||
if !strutil.StrListContains(allowedIAMAliasValues, iamAlias) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("iam_alias of %q not in set of allowed values: %v", iamAlias, allowedIAMAliasValues)), nil
|
||||
}
|
||||
configEntry.IAMAlias = iamAlias
|
||||
entry, err := logical.StorageEntryJSON("config/identity", configEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := req.Storage.Put(ctx, entry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.IAMAlias = iamAlias
|
||||
}
|
||||
|
||||
ec2AliasRaw, ok := data.GetOk("ec2_alias")
|
||||
if ok {
|
||||
ec2Alias := ec2AliasRaw.(string)
|
||||
allowedEC2AliasValues := []string{identityAliasEC2InstanceID, identityAliasEC2ImageID}
|
||||
if !strutil.StrListContains(allowedEC2AliasValues, ec2Alias) {
|
||||
return logical.ErrorResponse(fmt.Sprintf("ec2_alias of %q not in set of allowed values: %v", ec2Alias, allowedEC2AliasValues)), nil
|
||||
}
|
||||
config.EC2Alias = ec2Alias
|
||||
}
|
||||
|
||||
entry, err := logical.StorageEntryJSON("config/identity", config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = req.Storage.Put(ctx, entry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type identityConfig struct {
|
||||
IAMAlias string `json:"iam_alias"`
|
||||
EC2Alias string `json:"ec2_alias"`
|
||||
}
|
||||
|
||||
const identityAliasIAMUniqueID = "unique_id"
|
||||
const identityAliasIAMFullArn = "full_arn"
|
||||
const identityAliasEC2InstanceID = "instance_id"
|
||||
const identityAliasEC2ImageID = "image_id"
|
||||
|
||||
const pathConfigIdentityHelpSyn = `
|
||||
Configure the way the AWS auth method interacts with the identity store
|
||||
|
||||
@ -22,22 +22,23 @@ func TestBackend_pathConfigIdentity(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if default values are returned before setting the configuration
|
||||
resp, err := b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "config/identity",
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp != nil {
|
||||
if resp.IsError() {
|
||||
t.Fatalf("failed to read identity config entry")
|
||||
} else if resp.Data["iam_alias"] != nil && resp.Data["iam_alias"] != "" {
|
||||
t.Fatalf("returned alias is non-empty: %q", resp.Data["alias"])
|
||||
}
|
||||
if resp.Data["iam_alias"] == nil || resp.Data["iam_alias"] != identityAliasIAMUniqueID {
|
||||
t.Fatalf("bad: iam_alias; expected: %q, actual: %q", identityAliasIAMUniqueID, resp.Data["iam_alias"])
|
||||
}
|
||||
if resp.Data["ec2_alias"] == nil || resp.Data["ec2_alias"] != identityAliasEC2InstanceID {
|
||||
t.Fatalf("bad: ec2_alias; expected: %q, actual: %q", identityAliasIAMUniqueID, resp.Data["ec2_alias"])
|
||||
}
|
||||
|
||||
// Invalid value for iam_alias
|
||||
data := map[string]interface{}{
|
||||
"iam_alias": "invalid",
|
||||
}
|
||||
@ -58,7 +59,9 @@ func TestBackend_pathConfigIdentity(t *testing.T) {
|
||||
t.Fatalf("received non-error response from invalid config/identity request: %#v", resp)
|
||||
}
|
||||
|
||||
// Valid value for iam_alias but invalid value for ec2_alias
|
||||
data["iam_alias"] = identityAliasIAMFullArn
|
||||
data["ec2_alias"] = "invalid"
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/identity",
|
||||
@ -68,23 +71,94 @@ func TestBackend_pathConfigIdentity(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp != nil && resp.IsError() {
|
||||
t.Fatalf("received error response from valid config/identity request: %#v", resp)
|
||||
if resp == nil {
|
||||
t.Fatalf("nil response from invalid config/identity request")
|
||||
}
|
||||
if !resp.IsError() {
|
||||
t.Fatalf("received non-error response from invalid config/identity request: %#v", resp)
|
||||
}
|
||||
|
||||
// Valid value for both iam_alias and ec2_alias
|
||||
data["ec2_alias"] = identityAliasEC2ImageID
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/identity",
|
||||
Data: data,
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
// Check if both values are stored properly
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "config/identity",
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatalf("nil response received from config/identity when data expected")
|
||||
} else if resp.IsError() {
|
||||
t.Fatalf("error response received from reading config/identity: %#v", resp)
|
||||
} else if resp.Data["iam_alias"] != identityAliasIAMFullArn {
|
||||
t.Fatalf("bad: expected response with iam_alias value of %q; got %#v", identityAliasIAMFullArn, resp)
|
||||
if resp.Data["iam_alias"] != identityAliasIAMFullArn {
|
||||
t.Fatalf("bad: expected response with iam_alias value of %q; got %#v", identityAliasIAMFullArn, resp.Data["iam_alias"])
|
||||
}
|
||||
if resp.Data["ec2_alias"] != identityAliasEC2ImageID {
|
||||
t.Fatalf("bad: expected response with ec2_alias value of %q; got %#v", identityAliasEC2ImageID, resp.Data["ec2_alias"])
|
||||
}
|
||||
|
||||
// Modify one field and ensure that the other one is unchanged
|
||||
data["ec2_alias"] = identityAliasEC2InstanceID
|
||||
delete(data, "iam_alias")
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/identity",
|
||||
Data: data,
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "config/identity",
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["iam_alias"] != identityAliasIAMFullArn {
|
||||
t.Fatalf("bad: expected response with iam_alias value of %q; got %#v", identityAliasIAMFullArn, resp.Data["iam_alias"])
|
||||
}
|
||||
if resp.Data["ec2_alias"] != identityAliasEC2InstanceID {
|
||||
t.Fatalf("bad: expected response with ec2_alias value of %q; got %#v", identityAliasEC2ImageID, resp.Data["ec2_alias"])
|
||||
}
|
||||
|
||||
// Update both iam_alias and ec2_alias
|
||||
data["iam_alias"] = identityAliasIAMUniqueID
|
||||
data["ec2_alias"] = identityAliasEC2InstanceID
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.UpdateOperation,
|
||||
Path: "config/identity",
|
||||
Data: data,
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
|
||||
// Check if updates were stored properly
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Operation: logical.ReadOperation,
|
||||
Path: "config/identity",
|
||||
Storage: storage,
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
|
||||
}
|
||||
if resp.Data["iam_alias"] != identityAliasIAMUniqueID {
|
||||
t.Fatalf("bad: expected response with iam_alias value of %q; got %#v", identityAliasIAMFullArn, resp.Data["iam_alias"])
|
||||
}
|
||||
if resp.Data["ec2_alias"] != identityAliasEC2InstanceID {
|
||||
t.Fatalf("bad: expected response with ec2_alias value of %q; got %#v", identityAliasEC2ImageID, resp.Data["ec2_alias"])
|
||||
}
|
||||
}
|
||||
|
||||
@ -589,12 +589,26 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
|
||||
}
|
||||
}
|
||||
|
||||
identityConfigEntry, err := identityConfigEntry(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identityAlias := ""
|
||||
|
||||
switch identityConfigEntry.EC2Alias {
|
||||
case identityAliasEC2InstanceID:
|
||||
identityAlias = identityDocParsed.InstanceID
|
||||
case identityAliasEC2ImageID:
|
||||
identityAlias = identityDocParsed.AmiID
|
||||
}
|
||||
|
||||
// If we're just looking up for MFA, return the Alias info
|
||||
if req.Operation == logical.AliasLookaheadOperation {
|
||||
return &logical.Response{
|
||||
Auth: &logical.Auth{
|
||||
Alias: &logical.Alias{
|
||||
Name: identityDocParsed.InstanceID,
|
||||
Name: identityAlias,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
@ -814,7 +828,7 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
|
||||
MaxTTL: shortestMaxTTL,
|
||||
},
|
||||
Alias: &logical.Alias{
|
||||
Name: identityDocParsed.InstanceID,
|
||||
Name: identityAlias,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1114,19 +1128,6 @@ func (b *backend) pathLoginRenewEc2(ctx context.Context, req *logical.Request, d
|
||||
}
|
||||
|
||||
func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
|
||||
identityConfigEntryRaw, err := req.Storage.Get(ctx, "config/identity")
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf("failed to retrieve identity config: {{err}}", err)
|
||||
}
|
||||
var identityConfigEntry identityConfig
|
||||
if identityConfigEntryRaw == nil {
|
||||
identityConfigEntry.IAMAlias = identityAliasIAMUniqueID
|
||||
} else {
|
||||
if err = identityConfigEntryRaw.DecodeJSON(&identityConfigEntry); err != nil {
|
||||
return nil, errwrap.Wrapf("failed to parse stored config/identity: {{err}}", err)
|
||||
}
|
||||
}
|
||||
|
||||
method := data.Get("iam_http_request_method").(string)
|
||||
if method == "" {
|
||||
return logical.ErrorResponse("missing iam_http_request_method"), nil
|
||||
@ -1191,6 +1192,12 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error making upstream request: %v", err)), nil
|
||||
}
|
||||
|
||||
identityConfigEntry, err := identityConfigEntry(ctx, req.Storage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// This could either be a "userID:SessionID" (in the case of an assumed role) or just a "userID"
|
||||
// (in the case of an IAM user).
|
||||
callerUniqueId := strings.Split(callerID.UserId, ":")[0]
|
||||
|
||||
@ -135,8 +135,7 @@ $ curl \
|
||||
## Configure Identity Integration
|
||||
|
||||
This configures the way that Vault interacts with the
|
||||
[Identity](/docs/secrets/identity/index.html) store. This currently only
|
||||
configures how identity aliases are generated when using the `iam` auth method.
|
||||
[Identity](/docs/secrets/identity/index.html) store.
|
||||
|
||||
| Method | Path | Produces |
|
||||
| :------- | :--------------------------- | :--------------------- |
|
||||
@ -144,18 +143,25 @@ configures how identity aliases are generated when using the `iam` auth method.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `iam_alias` `(string: "unique_id")` - How to generate the Identity alias when
|
||||
- `iam_alias` `(string: "unique_id")` - How to generate the identity alias when
|
||||
using the `iam` auth method. Valid choices are `unique_id` and `full_arn`.
|
||||
When `unique_id` is selected, the [IAM Unique ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids)
|
||||
of the IAM principal (either the user or role) is used as the Identity alias.
|
||||
When `full_arn` is selected, the ARN returned by the `sts:GetCallerIdentity`
|
||||
call is used as the alias. This is either
|
||||
When `unique_id` is selected, the [IAM Unique
|
||||
ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids)
|
||||
of the IAM principal (either the user or role) is used as the identity alias
|
||||
name. When `full_arn` is selected, the ARN returned by the
|
||||
`sts:GetCallerIdentity` call is used as the alias name. This is either
|
||||
`arn:aws:iam::<account_id>:user/<optional_path/><user_name>` or
|
||||
`arn:aws:sts::<account_id>:assumed-role/<role_name_without_path>/<role_session_name>`.
|
||||
**Note**: if you select `full_arn` and then delete and recreate the IAM role,
|
||||
Vault won't be aware and any identity aliases set up for the role name will
|
||||
still be valid.
|
||||
|
||||
- `ec2_alias (string: "instance_id")` - Configures how to generate the identity alias when
|
||||
using the `ec2` auth method. Valid choices are `instance_id` and `image_id`.
|
||||
When `instance_id` is selected, the instance identifier is used as the
|
||||
identity alias name. When `image_id` is selected, AMI ID of the instance is
|
||||
used as the identity alias name.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user