Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add role_id as an alias name source for AWS and change the defaults #6133

Merged
merged 1 commit into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

CHANGES:

* New AWS authentication plugin mounts will default to using the generated
role ID as the Identity alias name. This applies to both EC2 and IAM auth.
Existing mounts will not be affected.
* The default policy now allows a token to look up its associated identity
entity either by name or by id [GH-6105]

Expand Down
26 changes: 11 additions & 15 deletions builtin/credential/aws/path_config_identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ func pathConfigIdentity(b *backend) *framework.Path {
"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),
Description: fmt.Sprintf("Configure how the AWS auth method generates entity aliases when using IAM auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasIAMUniqueID, identityAliasIAMFullArn, identityAliasRoleID),
},
"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),
Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults ot %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID),
},
},

Expand All @@ -42,23 +42,18 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi
}

var entry identityConfig
if entryRaw == nil {
entry.IAMAlias = identityAliasIAMUniqueID
entry.EC2Alias = identityAliasEC2InstanceID
return &entry, nil
}

err = entryRaw.DecodeJSON(&entry)
if err != nil {
return nil, err
if entryRaw != nil {
if err := entryRaw.DecodeJSON(&entry); err != nil {
return nil, err
}
}

if entry.IAMAlias == "" {
entry.IAMAlias = identityAliasIAMUniqueID
entry.IAMAlias = identityAliasRoleID
}

if entry.EC2Alias == "" {
entry.EC2Alias = identityAliasEC2InstanceID
entry.EC2Alias = identityAliasRoleID
}

return &entry, nil
Expand Down Expand Up @@ -87,7 +82,7 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f
iamAliasRaw, ok := data.GetOk("iam_alias")
if ok {
iamAlias := iamAliasRaw.(string)
allowedIAMAliasValues := []string{identityAliasIAMUniqueID, identityAliasIAMFullArn}
allowedIAMAliasValues := []string{identityAliasRoleID, identityAliasIAMUniqueID, identityAliasIAMFullArn}
if !strutil.StrListContains(allowedIAMAliasValues, iamAlias) {
return logical.ErrorResponse(fmt.Sprintf("iam_alias of %q not in set of allowed values: %v", iamAlias, allowedIAMAliasValues)), nil
}
Expand All @@ -97,7 +92,7 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f
ec2AliasRaw, ok := data.GetOk("ec2_alias")
if ok {
ec2Alias := ec2AliasRaw.(string)
allowedEC2AliasValues := []string{identityAliasEC2InstanceID, identityAliasEC2ImageID}
allowedEC2AliasValues := []string{identityAliasRoleID, 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
}
Expand Down Expand Up @@ -126,6 +121,7 @@ const identityAliasIAMUniqueID = "unique_id"
const identityAliasIAMFullArn = "full_arn"
const identityAliasEC2InstanceID = "instance_id"
const identityAliasEC2ImageID = "image_id"
const identityAliasRoleID = "role_id"

const pathConfigIdentityHelpSyn = `
Configure the way the AWS auth method interacts with the identity store
Expand Down
4 changes: 2 additions & 2 deletions builtin/credential/aws/path_config_identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ func TestBackend_pathConfigIdentity(t *testing.T) {
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: err: %v\nresp: %#v", err, resp)
}
if resp.Data["iam_alias"] == nil || resp.Data["iam_alias"] != identityAliasIAMUniqueID {
if resp.Data["iam_alias"] == nil || resp.Data["iam_alias"] != identityAliasRoleID {
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 {
if resp.Data["ec2_alias"] == nil || resp.Data["ec2_alias"] != identityAliasRoleID {
t.Fatalf("bad: ec2_alias; expected: %q, actual: %q", identityAliasIAMUniqueID, resp.Data["ec2_alias"])
}

Expand Down
88 changes: 46 additions & 42 deletions builtin/credential/aws/path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,26 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
}
}

roleName := data.Get("role").(string)

// If roleName is not supplied, a role in the name of the instance's AMI ID will be looked for
if roleName == "" {
roleName = identityDocParsed.AmiID
}

// Get the entry for the role used by the instance
roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("entry for role %q not found", roleName)), nil
}

if roleEntry.AuthType != ec2AuthType {
return logical.ErrorResponse(fmt.Sprintf("auth method ec2 not allowed for role %s", roleName)), nil
}

identityConfigEntry, err := identityConfigEntry(ctx, req.Storage)
if err != nil {
return nil, err
Expand All @@ -597,6 +617,8 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
identityAlias := ""

switch identityConfigEntry.EC2Alias {
case identityAliasRoleID:
identityAlias = roleEntry.RoleID
case identityAliasEC2InstanceID:
identityAlias = identityDocParsed.InstanceID
case identityAliasEC2ImageID:
Expand All @@ -614,26 +636,6 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
}, nil
}

roleName := data.Get("role").(string)

// If roleName is not supplied, a role in the name of the instance's AMI ID will be looked for
if roleName == "" {
roleName = identityDocParsed.AmiID
}

// Get the entry for the role used by the instance
roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("entry for role %q not found", roleName)), nil
}

if roleEntry.AuthType != ec2AuthType {
return logical.ErrorResponse(fmt.Sprintf("auth method ec2 not allowed for role %s", roleName)), nil
}

// Validate the instance ID by making a call to AWS EC2 DescribeInstances API
// and fetching the instance description. Validation succeeds only if the
// instance is in 'running' state.
Expand Down Expand Up @@ -1193,6 +1195,28 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
return logical.ErrorResponse(fmt.Sprintf("error making upstream request: %v", err)), nil
}

entity, err := parseIamArn(callerID.Arn)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("error parsing arn %q: %v", callerID.Arn, err)), nil
}

roleName := data.Get("role").(string)
if roleName == "" {
roleName = entity.FriendlyName
}

roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("entry for role %s not found", roleName)), nil
}

if roleEntry.AuthType != iamAuthType {
return logical.ErrorResponse(fmt.Sprintf("auth method iam not allowed for role %s", roleName)), nil
}

identityConfigEntry, err := identityConfigEntry(ctx, req.Storage)
if err != nil {
return nil, err
Expand All @@ -1203,6 +1227,8 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
callerUniqueId := strings.Split(callerID.UserId, ":")[0]
identityAlias := ""
switch identityConfigEntry.IAMAlias {
case identityAliasRoleID:
identityAlias = roleEntry.RoleID
case identityAliasIAMUniqueID:
identityAlias = callerUniqueId
case identityAliasIAMFullArn:
Expand All @@ -1220,28 +1246,6 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
}, nil
}

entity, err := parseIamArn(callerID.Arn)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("error parsing arn %q: %v", callerID.Arn, err)), nil
}

roleName := data.Get("role").(string)
if roleName == "" {
roleName = entity.FriendlyName
}

roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName)
if err != nil {
return nil, err
}
if roleEntry == nil {
return logical.ErrorResponse(fmt.Sprintf("entry for role %s not found", roleName)), nil
}

if roleEntry.AuthType != iamAuthType {
return logical.ErrorResponse(fmt.Sprintf("auth method iam not allowed for role %s", roleName)), nil
}

// The role creation should ensure that either we're inferring this is an EC2 instance
// or that we're binding an ARN
if len(roleEntry.BoundIamPrincipalARNs) > 0 {
Expand Down
26 changes: 21 additions & 5 deletions builtin/credential/aws/path_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
)

var (
currentRoleStorageVersion = 2
currentRoleStorageVersion = 3
)

func pathRole(b *backend) *framework.Path {
Expand Down Expand Up @@ -391,8 +391,8 @@ func (b *backend) upgradeRoleEntry(ctx context.Context, s logical.Storage, roleE
roleEntry.BoundVpcIDs = []string{roleEntry.BoundVpcID}
roleEntry.BoundVpcID = ""
}
roleEntry.Version = 1
fallthrough

case 1:
// Make BoundIamRoleARNs and BoundIamInstanceProfileARNs explicitly prefix-matched
for i, arn := range roleEntry.BoundIamRoleARNs {
Expand All @@ -401,15 +401,24 @@ func (b *backend) upgradeRoleEntry(ctx context.Context, s logical.Storage, roleE
for i, arn := range roleEntry.BoundIamInstanceProfileARNs {
roleEntry.BoundIamInstanceProfileARNs[i] = fmt.Sprintf("%s*", arn)
}
roleEntry.Version = 2
fallthrough

case 2:
roleID, err := uuid.GenerateUUID()
if err != nil {
return false, err
}
roleEntry.RoleID = roleID
fallthrough

case currentRoleStorageVersion:
roleEntry.Version = currentRoleStorageVersion

default:
return false, fmt.Errorf("unrecognized role version: %q", roleEntry.Version)
}

return upgraded, nil

}

// nonLockedAWSRole returns the properties set on the given role. This method
Expand Down Expand Up @@ -494,7 +503,12 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
return nil, err
}
if roleEntry == nil {
roleID, err := uuid.GenerateUUID()
if err != nil {
return nil, err
}
roleEntry = &awsRoleEntry{
RoleID: roleID,
Version: currentRoleStorageVersion,
}
} else {
Expand Down Expand Up @@ -807,7 +821,8 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request

// Struct to hold the information associated with a Vault role
type awsRoleEntry struct {
AuthType string `json:"auth_type" `
RoleID string `json:"role_id"`
AuthType string `json:"auth_type"`
BoundAmiIDs []string `json:"bound_ami_id_list"`
BoundAccountIDs []string `json:"bound_account_id_list"`
BoundEc2InstanceIDs []string `json:"bound_ec2_instance_id_list"`
Expand Down Expand Up @@ -858,6 +873,7 @@ func (r *awsRoleEntry) ToResponseData() map[string]interface{} {
"inferred_entity_type": r.InferredEntityType,
"inferred_aws_region": r.InferredAWSRegion,
"resolve_aws_unique_ids": r.ResolveAWSUniqueIDs,
"role_id": r.RoleID,
"role_tag": r.RoleTag,
"allow_instance_migration": r.AllowInstanceMigration,
"ttl": r.TTL / time.Second,
Expand Down
20 changes: 15 additions & 5 deletions builtin/credential/aws/path_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/go-test/deep"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
Expand Down Expand Up @@ -620,8 +621,13 @@ func TestAwsEc2_RoleCrud(t *testing.T) {
"period": time.Duration(60),
}

if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: role data: expected: %#v\n actual: %#v", expected, resp.Data)
if resp.Data["role_id"] == nil {
t.Fatal("role_id not found in repsonse")
}
expected["role_id"] = resp.Data["role_id"]

if diff := deep.Equal(expected, resp.Data); diff != nil {
t.Fatal(diff)
}

roleData["bound_vpc_id"] = "newvpcid"
Expand Down Expand Up @@ -711,7 +717,7 @@ func TestAwsEc2_RoleDurationSeconds(t *testing.T) {
}
}

func TestRoleEntryUpgradeV1(t *testing.T) {
func TestRoleEntryUpgradeV(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
Expand Down Expand Up @@ -743,8 +749,12 @@ func TestRoleEntryUpgradeV1(t *testing.T) {
if !upgraded {
t.Fatalf("expected to upgrade role entry %#v but got no upgrade", roleEntryToUpgrade)
}
if !reflect.DeepEqual(*roleEntryToUpgrade, *expected) {
t.Fatalf("bad: expected upgraded role of %#v, got %#v instead", expected, roleEntryToUpgrade)
if roleEntryToUpgrade.RoleID == "" {
t.Fatal("expected role ID to be populated")
}
expected.RoleID = roleEntryToUpgrade.RoleID
if diff := deep.Equal(*roleEntryToUpgrade, *expected); diff != nil {
t.Fatal(diff)
}
}

Expand Down
21 changes: 12 additions & 9 deletions website/source/api/auth/aws/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ $ curl \
## Configure Identity Integration

This configures the way that Vault interacts with the
[Identity](/docs/secrets/identity/index.html) store.
[Identity](/docs/secrets/identity/index.html) store. The default (as of Vault
1.0.3) is `role_id` for both values.

| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
Expand All @@ -144,8 +145,9 @@ This configures the way that Vault interacts with the
### Parameters

- `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
using the `iam` auth method. Valid choices are `role_id`, `unique_id`, and
`full_arn` When `role_id` is selected, the randomly generated ID of the role
is used. 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
Expand All @@ -156,17 +158,18 @@ This configures the way that Vault interacts with the
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.
- `ec2_alias (string: "instance_id")` - Configures how to generate the identity
alias when using the `ec2` auth method. Valid choices are `role_id`,
`instance_id`, and `image_id`. When `role_id` is selected, the randomly
generated ID of the role is used. 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
{
"iam_alias": "full_arn"
"iam_alias": "unique_id"
}
```

Expand Down