Add role_id as an alias name source for AWS and change the defaults
This commit is contained in:
parent
4363453017
commit
47accf8086
|
@ -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]
|
||||
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -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 {
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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"])
|
||||
}
|
||||
|
||||
|
|
|
@ -589,31 +589,6 @@ 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: identityAlias,
|
||||
},
|
||||
},
|
||||
}, 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
|
||||
|
@ -634,6 +609,33 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
|
|||
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
|
||||
}
|
||||
|
||||
identityAlias := ""
|
||||
|
||||
switch identityConfigEntry.EC2Alias {
|
||||
case identityAliasRoleID:
|
||||
identityAlias = roleEntry.RoleID
|
||||
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: identityAlias,
|
||||
},
|
||||
},
|
||||
}, 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.
|
||||
|
@ -1193,33 +1195,6 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
|
|||
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]
|
||||
identityAlias := ""
|
||||
switch identityConfigEntry.IAMAlias {
|
||||
case identityAliasIAMUniqueID:
|
||||
identityAlias = callerUniqueId
|
||||
case identityAliasIAMFullArn:
|
||||
identityAlias = callerID.Arn
|
||||
}
|
||||
|
||||
// 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: identityAlias,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
entity, err := parseIamArn(callerID.Arn)
|
||||
if err != nil {
|
||||
return logical.ErrorResponse(fmt.Sprintf("error parsing arn %q: %v", callerID.Arn, err)), nil
|
||||
|
@ -1242,6 +1217,35 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
|
|||
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
|
||||
}
|
||||
|
||||
// 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]
|
||||
identityAlias := ""
|
||||
switch identityConfigEntry.IAMAlias {
|
||||
case identityAliasRoleID:
|
||||
identityAlias = roleEntry.RoleID
|
||||
case identityAliasIAMUniqueID:
|
||||
identityAlias = callerUniqueId
|
||||
case identityAliasIAMFullArn:
|
||||
identityAlias = callerID.Arn
|
||||
}
|
||||
|
||||
// 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: identityAlias,
|
||||
},
|
||||
},
|
||||
}, 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 {
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
currentRoleStorageVersion = 2
|
||||
currentRoleStorageVersion = 3
|
||||
)
|
||||
|
||||
func pathRole(b *backend) *framework.Path {
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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"`
|
||||
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 |
|
||||
| :------- | :--------------------------- | :--------------------- |
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
Loading…
Reference in New Issue