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:
Vishal Nayak 2019-01-09 18:28:29 -05:00 committed by GitHub
parent ba2d6bd498
commit 77978055fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 57 deletions

View File

@ -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

View File

@ -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"])
}
}

View File

@ -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]

View File

@ -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