auth/aws: Allow lists in binds (#3907)

* auth/aws: Allow lists in binds

In the aws auth method, allow a number of binds to take in lists
instead of a single string value. The intended semantic is that, for
each bind type set, clients must match at least one of each of the bind
types set in order to authenticate.
This commit is contained in:
Joel Thompson
2018-03-02 11:09:14 -05:00
committed by Jeff Mitchell
parent 4d419aa420
commit 8a115c73d9
6 changed files with 392 additions and 237 deletions

View File

@@ -1084,7 +1084,7 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing.
"auth_type": "ec2",
"policies": "root",
"max_ttl": "120s",
"bound_ami_id": "wrong_ami_id",
"bound_ami_id": []string{"wrong_ami_id", "wrong_ami_id2"},
"bound_account_id": accountID,
"bound_iam_role_arn": iamARN,
}
@@ -1108,10 +1108,10 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing.
t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err)
}
// Place the correct AMI ID, but make the AccountID wrong
// Place the correct AMI ID in one of the values, but make the AccountID wrong
roleReq.Operation = logical.UpdateOperation
data["bound_ami_id"] = amiID
data["bound_account_id"] = "wrong-account-id"
data["bound_ami_id"] = []string{"wrong_ami_id_1", amiID, "wrong_ami_id_2"}
data["bound_account_id"] = []string{"wrong-account-id", "wrong-account-id-2"}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
@@ -1123,9 +1123,9 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing.
t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err)
}
// Place the correct AccountID, but make the wrong IAMRoleARN
data["bound_account_id"] = accountID
data["bound_iam_role_arn"] = "wrong_iam_role_arn"
// Place the correct AccountID in one of the values, but make the wrong IAMRoleARN
data["bound_account_id"] = []string{"wrong-account-id-1", accountID, "wrong-account-id-2"}
data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn", "wrong_iam_role_arn_2"}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
@@ -1137,8 +1137,8 @@ func TestBackendAcc_LoginWithInstanceIdentityDocAndWhitelistIdentity(t *testing.
t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err)
}
// place the correct IAM role ARN
data["bound_iam_role_arn"] = iamARN
// place a correct IAM role ARN
data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn_1", iamARN, "wrong_iam_role_arn_2"}
resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
@@ -1456,7 +1456,7 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) {
// configuring the valid role we'll be able to login to
roleData := map[string]interface{}{
"bound_iam_principal_arn": entity.canonicalArn(),
"bound_iam_principal_arn": []string{entity.canonicalArn(), "arn:aws:iam::123456789012:role/FakeRoleArn1*"}, // Fake ARN MUST be wildcard terminated because we're resolving unique IDs, and the wildcard termination prevents unique ID resolution
"policies": "root",
"auth_type": iamAuthType,
}
@@ -1489,16 +1489,19 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) {
}
fakeArn := "arn:aws:iam::123456789012:role/somePath/FakeRole"
fakeArn2 := "arn:aws:iam::123456789012:role/somePath/FakeRole2"
fakeArnResolverCount := 0
fakeArnResolver := func(ctx context.Context, s logical.Storage, arn string) (string, error) {
if arn == fakeArn {
return fmt.Sprintf("FakeUniqueIdFor%s", fakeArn), nil
if strings.HasPrefix(arn, fakeArn) {
fakeArnResolverCount++
return fmt.Sprintf("FakeUniqueIdFor%s%d", arn, fakeArnResolverCount), nil
}
return b.resolveArnToRealUniqueId(context.Background(), s, arn)
}
b.resolveArnToUniqueIDFunc = fakeArnResolver
// now we're creating the invalid role we won't be able to login to
roleData["bound_iam_principal_arn"] = fakeArn
roleData["bound_iam_principal_arn"] = []string{fakeArn, fakeArn2}
roleRequest.Path = "role/" + testInvalidRoleName
resp, err = b.HandleRequest(context.Background(), roleRequest)
if err != nil || (resp != nil && resp.IsError()) {
@@ -1630,11 +1633,11 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) {
wildcardRoleName := "valid_wildcard"
wildcardEntity := *entity
wildcardEntity.FriendlyName = "*"
roleData["bound_iam_principal_arn"] = wildcardEntity.canonicalArn()
roleData["bound_iam_principal_arn"] = []string{wildcardEntity.canonicalArn(), "arn:aws:iam::123456789012:role/DoesNotExist/Vault_Fake_Role*"}
roleRequest.Path = "role/" + wildcardRoleName
resp, err = b.HandleRequest(context.Background(), roleRequest)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("bad: failed to create wildcard role: resp:%#v\nerr:%v", resp, err)
t.Fatalf("bad: failed to create wildcard roles: resp:%#v\nerr:%v", resp, err)
}
loginData["role"] = wildcardRoleName

View File

@@ -386,7 +386,7 @@ func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context,
// Verify that the AccountID of the instance trying to login matches the
// AccountID specified as a constraint on role
if roleEntry.BoundAccountID != "" && identityDoc.AccountID != roleEntry.BoundAccountID {
if len(roleEntry.BoundAccountIDs) > 0 && !strutil.StrListContains(roleEntry.BoundAccountIDs, identityDoc.AccountID) {
return fmt.Errorf("account ID %q does not belong to role %q", identityDoc.AccountID, roleName), nil
}
@@ -399,31 +399,31 @@ func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context,
// already calling the API to validate the Instance ID anyway, so it shouldn't
// matter. The benefit is that we have the exact same code whether auth_type
// is ec2 or iam.
if roleEntry.BoundAmiID != "" {
if len(roleEntry.BoundAmiIDs) > 0 {
if instance.ImageId == nil {
return nil, fmt.Errorf("AMI ID in the instance description is nil")
}
if roleEntry.BoundAmiID != *instance.ImageId {
if !strutil.StrListContains(roleEntry.BoundAmiIDs, *instance.ImageId) {
return fmt.Errorf("AMI ID %q does not belong to role %q", instance.ImageId, roleName), nil
}
}
// Validate the SubnetID if corresponding bound was set on the role
if roleEntry.BoundSubnetID != "" {
if len(roleEntry.BoundSubnetIDs) > 0 {
if instance.SubnetId == nil {
return nil, fmt.Errorf("subnet ID in the instance description is nil")
}
if roleEntry.BoundSubnetID != *instance.SubnetId {
if !strutil.StrListContains(roleEntry.BoundSubnetIDs, *instance.SubnetId) {
return fmt.Errorf("subnet ID %q does not satisfy the constraint on role %q", *instance.SubnetId, roleName), nil
}
}
// Validate the VpcID if corresponding bound was set on the role
if roleEntry.BoundVpcID != "" {
if len(roleEntry.BoundVpcIDs) > 0 {
if instance.VpcId == nil {
return nil, fmt.Errorf("VPC ID in the instance description is nil")
}
if roleEntry.BoundVpcID != *instance.VpcId {
if !strutil.StrListContains(roleEntry.BoundVpcIDs, *instance.VpcId) {
return fmt.Errorf("VPC ID %q does not satisfy the constraint on role %q", *instance.VpcId, roleName), nil
}
}
@@ -431,7 +431,7 @@ func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context,
// Check if the IAM instance profile ARN of the instance trying to
// login, matches the IAM instance profile ARN specified as a constraint
// on the role
if roleEntry.BoundIamInstanceProfileARN != "" {
if len(roleEntry.BoundIamInstanceProfileARNs) > 0 {
if instance.IamInstanceProfile == nil {
return nil, fmt.Errorf("IAM instance profile in the instance description is nil")
}
@@ -439,14 +439,21 @@ func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context,
return nil, fmt.Errorf("IAM instance profile ARN in the instance description is nil")
}
iamInstanceProfileARN := *instance.IamInstanceProfile.Arn
if !strings.HasPrefix(iamInstanceProfileARN, roleEntry.BoundIamInstanceProfileARN) {
matchesInstanceProfile := false
for _, boundInstanceProfileARN := range roleEntry.BoundIamInstanceProfileARNs {
if strings.HasPrefix(iamInstanceProfileARN, boundInstanceProfileARN) {
matchesInstanceProfile = true
break
}
}
if !matchesInstanceProfile {
return fmt.Errorf("IAM instance profile ARN %q does not satisfy the constraint role %q", iamInstanceProfileARN, roleName), nil
}
}
// Check if the IAM role ARN of the instance trying to login, matches
// the IAM role ARN specified as a constraint on the role.
if roleEntry.BoundIamRoleARN != "" {
if len(roleEntry.BoundIamRoleARNs) > 0 {
if instance.IamInstanceProfile == nil {
return nil, fmt.Errorf("IAM instance profile in the instance description is nil")
}
@@ -484,7 +491,14 @@ func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context,
return nil, fmt.Errorf("IAM role ARN could not be fetched")
}
if !strings.HasPrefix(iamRoleARN, roleEntry.BoundIamRoleARN) {
matchesInstanceRoleARN := false
for _, boundIamRoleARN := range roleEntry.BoundIamRoleARNs {
if strings.HasPrefix(iamRoleARN, boundIamRoleARN) {
matchesInstanceRoleARN = true
break
}
}
if !matchesInstanceRoleARN {
return fmt.Errorf("IAM role ARN %q does not satisfy the constraint role %q", iamRoleARN, roleName), nil
}
}
@@ -588,7 +602,7 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
// Verify that the `Region` of the instance trying to login matches the
// `Region` specified as a constraint on role
if roleEntry.BoundRegion != "" && identityDocParsed.Region != roleEntry.BoundRegion {
if len(roleEntry.BoundRegions) > 0 && !strutil.StrListContains(roleEntry.BoundRegions, identityDocParsed.Region) {
return logical.ErrorResponse(fmt.Sprintf("Region %q does not satisfy the constraint on role %q", identityDocParsed.Region, roleName)), nil
}
@@ -939,17 +953,20 @@ func (b *backend) pathLoginRenewIam(ctx context.Context, req *logical.Request, d
// read the role directly to know what the bind is. It's a relatively small amount of leakage, in
// some fairly corner cases, and in the most likely error case (role has been changed to a new ARN),
// the error message is identical.
if roleEntry.BoundIamPrincipalARN != "" {
if len(roleEntry.BoundIamPrincipalARNs) > 0 {
// We might not get here if all bindings were on the inferred entity, which we've already validated
// above
// As with logins, there are three ways to pass this check:
// 1: clientUserId is in roleEntry.BoundIamPrincipalIDs (entries in roleEntry.BoundIamPrincipalIDs
// implies that roleEntry.ResolveAWSUniqueIDs is true)
// 2: roleEntry.ResolveAWSUniqueIDs is false and canonical_arn is in roleEntry.BoundIamPrincipalARNs
// 3: Full ARN matches one of the wildcard globs in roleEntry.BoundIamPrincipalARNs
clientUserId, ok := req.Auth.Metadata["client_user_id"]
if ok && roleEntry.BoundIamPrincipalID != "" {
// Resolving unique IDs is enabled and the auth metadata contains the unique ID, so checking the
// unique ID is authoritative at this stage
if roleEntry.BoundIamPrincipalID != clientUserId {
return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn)
}
} else if strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
switch {
case ok && strutil.StrListContains(roleEntry.BoundIamPrincipalIDs, clientUserId): // check 1 passed
case !roleEntry.ResolveAWSUniqueIDs && strutil.StrListContains(roleEntry.BoundIamPrincipalARNs, canonicalArn): // check 2 passed
default:
// check 3 is a bit more complex, so we do it last
fullArn := b.getCachedUserId(clientUserId)
if fullArn == "" {
entity, err := parseIamArn(canonicalArn)
@@ -967,11 +984,16 @@ func (b *backend) pathLoginRenewIam(ctx context.Context, req *logical.Request, d
b.setCachedUserId(clientUserId, fullArn)
}
}
if !strutil.GlobbedStringsMatch(roleEntry.BoundIamPrincipalARN, fullArn) {
matchedWildcardBind := false
for _, principalARN := range roleEntry.BoundIamPrincipalARNs {
if strings.HasSuffix(principalARN, "*") && strutil.GlobbedStringsMatch(principalARN, fullArn) {
matchedWildcardBind = true
break
}
}
if !matchedWildcardBind {
return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn)
}
} else if roleEntry.BoundIamPrincipalARN != canonicalArn {
return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn)
}
}
@@ -1189,15 +1211,19 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
// The role creation should ensure that either we're inferring this is an EC2 instance
// or that we're binding an ARN
// The only way BoundIamPrincipalID could get set is if BoundIamPrincipalARN was also set and
// resolving to internal IDs was turned on, which can't be turned off. So, there should be no
// way for this to be set and not match BoundIamPrincipalARN
if roleEntry.BoundIamPrincipalID != "" {
if callerUniqueId != roleEntry.BoundIamPrincipalID {
return logical.ErrorResponse(fmt.Sprintf("expected IAM %s %s to resolve to unique AWS ID %q but got %q instead", entity.Type, entity.FriendlyName, roleEntry.BoundIamPrincipalID, callerUniqueId)), nil
}
} else if roleEntry.BoundIamPrincipalARN != "" {
if strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
if len(roleEntry.BoundIamPrincipalARNs) > 0 {
// As with renews, there are three ways to pass this check:
// 1: callerUniqueId is in roleEntry.BoundIamPrincipalIDs (entries in roleEntry.BoundIamPrincipalIDs
// implies that roleEntry.ResolveAWSUniqueIDs is true)
// 2: roleEntry.ResolveAWSUniqueIDs is false and entity.canonicalArn() is in roleEntry.BoundIamPrincipalARNs
// 3: Full ARN matches one of the wildcard globs in roleEntry.BoundIamPrincipalARNs
// Need to be able to handle pathological configurations such as roleEntry.BoundIamPrincipalARNs looking something like:
// arn:aw:iam::123456789012:{user/UserName,user/path/*,role/RoleName,role/path/*}
switch {
case strutil.StrListContains(roleEntry.BoundIamPrincipalIDs, callerUniqueId): // check 1 passed
case !roleEntry.ResolveAWSUniqueIDs && strutil.StrListContains(roleEntry.BoundIamPrincipalARNs, entity.canonicalArn()): // check 2 passed
default:
// evaluate check 3
fullArn := b.getCachedUserId(callerUniqueId)
if fullArn == "" {
fullArn, err = b.fullArn(ctx, entity, req.Storage)
@@ -1209,13 +1235,16 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
}
b.setCachedUserId(callerUniqueId, fullArn)
}
if !strutil.GlobbedStringsMatch(roleEntry.BoundIamPrincipalARN, fullArn) {
// Note: Intentionally giving the exact same error message as a few lines below. Otherwise, we might leak information
// about whether the bound IAM principal ARN is a wildcard or not, and what that wildcard is.
matchedWildcardBind := false
for _, principalARN := range roleEntry.BoundIamPrincipalARNs {
if strings.HasSuffix(principalARN, "*") && strutil.GlobbedStringsMatch(principalARN, fullArn) {
matchedWildcardBind = true
break
}
}
if !matchedWildcardBind {
return logical.ErrorResponse(fmt.Sprintf("IAM Principal %q does not belong to the role %q", callerID.Arn, roleName)), nil
}
} else if roleEntry.BoundIamPrincipalARN != entity.canonicalArn() {
return logical.ErrorResponse(fmt.Sprintf("IAM Principal %q does not belong to the role %q", callerID.Arn, roleName)), nil
}
}

View File

@@ -7,11 +7,16 @@ import (
"time"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/helper/consts"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/logical/framework"
)
var (
currentRoleStorageVersion = 1
)
func pathRole(b *backend) *framework.Path {
return &framework.Path{
Pattern: "role/" + framework.GenericNameRegex("role"),
@@ -26,32 +31,33 @@ func pathRole(b *backend) *framework.Path {
iam or ec2 and cannot be changed after role creation.`,
},
"bound_ami_id": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `If set, defines a constraint on the EC2 instances that they should be
using the AMI ID specified by this parameter. This is only applicable when auth_type is ec2
or inferred_entity_type is ec2_instance.`,
using one of the AMI IDs specified by this parameter. This is only applicable
when auth_type is ec2 or inferred_entity_type is ec2_instance.`,
},
"bound_account_id": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `If set, defines a constraint on the EC2 instances that the account ID
in its identity document to match the one specified by this parameter. This is only
applicable when auth_type is ec2 or inferred_entity_type is ec2_instance.`,
in its identity document to match one of the IDs specified by this parameter.
This is only applicable when auth_type is ec2 or inferred_entity_type is
ec2_instance.`,
},
"bound_iam_principal_arn": {
Type: framework.TypeString,
Description: `ARN of the IAM principal to bind to this role. Only applicable when
Type: framework.TypeCommaStringSlice,
Description: `ARN of the IAM principals to bind to this role. Only applicable when
auth_type is iam.`,
},
"bound_region": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `If set, defines a constraint on the EC2 instances that the region in
its identity document to match the one specified by this parameter. This is only
its identity document match one of the regions specified by this parameter. This is only
applicable when auth_type is ec2.`,
},
"bound_iam_role_arn": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `If set, defines a constraint on the authenticating EC2 instance
that it must match the IAM role ARN specified by this parameter.
that it must match one of the IAM role ARNs specified by this parameter.
The value is prefix-matched (as though it were a glob ending in
'*'). The configured IAM user or EC2 instance role must be allowed
to execute the 'iam:GetInstanceProfile' action if this is specified. This is
@@ -59,10 +65,10 @@ only applicable when auth_type is ec2 or inferred_entity_type is
ec2_instance.`,
},
"bound_iam_instance_profile_arn": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `If set, defines a constraint on the EC2 instances to be associated
with an IAM instance profile ARN which has a prefix that matches
the value specified by this parameter. The value is prefix-matched
one of the values specified by this parameter. The value is prefix-matched
(as though it were a glob ending in '*'). This is only applicable when
auth_type is ec2 or inferred_entity_type is ec2_instance.`,
},
@@ -93,18 +99,19 @@ fail.`,
inferred_entity_type is set, the region to assume the inferred entity exists in.`,
},
"bound_vpc_id": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `
If set, defines a constraint on the EC2 instance to be associated with the VPC
ID that matches the value specified by this parameter. This is only applicable
when auth_type is ec2 or inferred_entity_type is ec2_instance.`,
If set, defines a constraint on the EC2 instance to be associated with a VPC
ID that matches one of the value specified by this parameter. This is only
applicable when auth_type is ec2 or inferred_entity_type is ec2_instance.`,
},
"bound_subnet_id": {
Type: framework.TypeString,
Type: framework.TypeCommaStringSlice,
Description: `
If set, defines a constraint on the EC2 instance to be associated with the
subnet ID that matches the value specified by this parameter. This is only
applicable when auth_type is ec2 or inferred_entity_type is ec2_instance.`,
subnet ID that matches one of the values specified by this parameter. This is
only applicable when auth_type is ec2 or inferred_entity_type is
ec2_instance.`,
},
"role_tag": {
Type: framework.TypeString,
@@ -232,7 +239,7 @@ func (b *backend) lockedAWSRole(ctx context.Context, s logical.Storage, roleName
if err != nil {
return nil, fmt.Errorf("error upgrading roleEntry: %v", err)
}
if needUpgrade {
if needUpgrade && (b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary)) {
b.roleMutex.Lock()
defer b.roleMutex.Unlock()
// Now that we have a R/W lock, we need to re-read the role entry in case it was
@@ -307,35 +314,93 @@ func (b *backend) upgradeRoleEntry(ctx context.Context, s logical.Storage, roleE
return false, fmt.Errorf("received nil roleEntry")
}
var upgraded bool
// Check if the value held by role ARN field is actually an instance profile ARN
if roleEntry.BoundIamRoleARN != "" && strings.Contains(roleEntry.BoundIamRoleARN, ":instance-profile/") {
// If yes, move it to the correct field
roleEntry.BoundIamInstanceProfileARN = roleEntry.BoundIamRoleARN
switch roleEntry.Version {
case 0:
// Check if the value held by role ARN field is actually an instance profile ARN
if roleEntry.BoundIamRoleARN != "" && strings.Contains(roleEntry.BoundIamRoleARN, ":instance-profile/") {
// If yes, move it to the correct field
roleEntry.BoundIamInstanceProfileARN = roleEntry.BoundIamRoleARN
// Reset the old field
roleEntry.BoundIamRoleARN = ""
// Reset the old field
roleEntry.BoundIamRoleARN = ""
upgraded = true
}
// Check if there was no pre-existing AuthType set (from older versions)
if roleEntry.AuthType == "" {
// then default to the original behavior of ec2
roleEntry.AuthType = ec2AuthType
upgraded = true
}
if roleEntry.AuthType == iamAuthType &&
roleEntry.ResolveAWSUniqueIDs &&
roleEntry.BoundIamPrincipalARN != "" &&
roleEntry.BoundIamPrincipalID == "" &&
!strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
principalId, err := b.resolveArnToUniqueIDFunc(ctx, s, roleEntry.BoundIamPrincipalARN)
if err != nil {
return false, err
upgraded = true
}
roleEntry.BoundIamPrincipalID = principalId
upgraded = true
// Check if there was no pre-existing AuthType set (from older versions)
if roleEntry.AuthType == "" {
// then default to the original behavior of ec2
roleEntry.AuthType = ec2AuthType
upgraded = true
}
// Check if we need to resolve the unique ID on the role
if roleEntry.AuthType == iamAuthType &&
roleEntry.ResolveAWSUniqueIDs &&
roleEntry.BoundIamPrincipalARN != "" &&
roleEntry.BoundIamPrincipalID == "" &&
!strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
principalId, err := b.resolveArnToUniqueIDFunc(ctx, s, roleEntry.BoundIamPrincipalARN)
if err != nil {
return false, err
}
roleEntry.BoundIamPrincipalID = principalId
// Not setting roleEntry.BoundIamPrincipalARN to "" here so that clients can see the original
// ARN that the role was bound to
upgraded = true
}
// Check if we need to convert individual string values to lists
if roleEntry.BoundAmiID != "" {
roleEntry.BoundAmiIDs = []string{roleEntry.BoundAmiID}
roleEntry.BoundAmiID = ""
upgraded = true
}
if roleEntry.BoundAccountID != "" {
roleEntry.BoundAccountIDs = []string{roleEntry.BoundAccountID}
roleEntry.BoundAccountID = ""
upgraded = true
}
if roleEntry.BoundIamPrincipalARN != "" {
roleEntry.BoundIamPrincipalARNs = []string{roleEntry.BoundIamPrincipalARN}
roleEntry.BoundIamPrincipalARN = ""
upgraded = true
}
if roleEntry.BoundIamPrincipalID != "" {
roleEntry.BoundIamPrincipalIDs = []string{roleEntry.BoundIamPrincipalID}
roleEntry.BoundIamPrincipalID = ""
upgraded = true
}
if roleEntry.BoundIamRoleARN != "" {
roleEntry.BoundIamRoleARNs = []string{roleEntry.BoundIamRoleARN}
roleEntry.BoundIamRoleARN = ""
upgraded = true
}
if roleEntry.BoundIamInstanceProfileARN != "" {
roleEntry.BoundIamInstanceProfileARNs = []string{roleEntry.BoundIamInstanceProfileARN}
roleEntry.BoundIamInstanceProfileARN = ""
upgraded = true
}
if roleEntry.BoundRegion != "" {
roleEntry.BoundRegions = []string{roleEntry.BoundRegion}
roleEntry.BoundRegion = ""
upgraded = true
}
if roleEntry.BoundSubnetID != "" {
roleEntry.BoundSubnetIDs = []string{roleEntry.BoundSubnetID}
roleEntry.BoundSubnetID = ""
upgraded = true
}
if roleEntry.BoundVpcID != "" {
roleEntry.BoundVpcIDs = []string{roleEntry.BoundVpcID}
roleEntry.BoundVpcID = ""
upgraded = true
}
roleEntry.Version = 1
fallthrough
case currentRoleStorageVersion:
default:
return false, fmt.Errorf("unrecognized role version: %q", roleEntry.Version)
}
return upgraded, nil
@@ -405,28 +470,7 @@ func (b *backend) pathRoleRead(ctx context.Context, req *logical.Request, data *
}
return &logical.Response{
Data: map[string]interface{}{
"auth_type": roleEntry.AuthType,
"bound_ami_id": roleEntry.BoundAmiID,
"bound_account_id": roleEntry.BoundAccountID,
"bound_iam_principal_arn": roleEntry.BoundIamPrincipalARN,
"bound_iam_principal_id": roleEntry.BoundIamPrincipalID,
"bound_iam_role_arn": roleEntry.BoundIamRoleARN,
"bound_iam_instance_profile_arn": roleEntry.BoundIamInstanceProfileARN,
"bound_region": roleEntry.BoundRegion,
"bound_subnet_id": roleEntry.BoundSubnetID,
"bound_vpc_id": roleEntry.BoundVpcID,
"inferred_entity_type": roleEntry.InferredEntityType,
"inferred_aws_region": roleEntry.InferredAWSRegion,
"resolve_aws_unique_ids": roleEntry.ResolveAWSUniqueIDs,
"role_tag": roleEntry.RoleTag,
"allow_instance_migration": roleEntry.AllowInstanceMigration,
"ttl": roleEntry.TTL / time.Second,
"max_ttl": roleEntry.MaxTTL / time.Second,
"policies": roleEntry.Policies,
"disallow_reauthentication": roleEntry.DisallowReauthentication,
"period": roleEntry.Period / time.Second,
},
Data: roleEntry.ToResponseData(),
}, nil
}
@@ -445,7 +489,9 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
return nil, err
}
if roleEntry == nil {
roleEntry = &awsRoleEntry{}
roleEntry = &awsRoleEntry{
Version: currentRoleStorageVersion,
}
} else {
needUpdate, err := b.upgradeRoleEntry(ctx, req.Storage, roleEntry)
if err != nil {
@@ -462,23 +508,23 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
// Fetch and set the bound parameters. There can't be default values
// for these.
if boundAmiIDRaw, ok := data.GetOk("bound_ami_id"); ok {
roleEntry.BoundAmiID = boundAmiIDRaw.(string)
roleEntry.BoundAmiIDs = boundAmiIDRaw.([]string)
}
if boundAccountIDRaw, ok := data.GetOk("bound_account_id"); ok {
roleEntry.BoundAccountID = boundAccountIDRaw.(string)
roleEntry.BoundAccountIDs = boundAccountIDRaw.([]string)
}
if boundRegionRaw, ok := data.GetOk("bound_region"); ok {
roleEntry.BoundRegion = boundRegionRaw.(string)
roleEntry.BoundRegions = boundRegionRaw.([]string)
}
if boundVpcIDRaw, ok := data.GetOk("bound_vpc_id"); ok {
roleEntry.BoundVpcID = boundVpcIDRaw.(string)
roleEntry.BoundVpcIDs = boundVpcIDRaw.([]string)
}
if boundSubnetIDRaw, ok := data.GetOk("bound_subnet_id"); ok {
roleEntry.BoundSubnetID = boundSubnetIDRaw.(string)
roleEntry.BoundSubnetIDs = boundSubnetIDRaw.([]string)
}
if resolveAWSUniqueIDsRaw, ok := data.GetOk("resolve_aws_unique_ids"); ok {
@@ -495,37 +541,29 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
}
if boundIamRoleARNRaw, ok := data.GetOk("bound_iam_role_arn"); ok {
roleEntry.BoundIamRoleARN = boundIamRoleARNRaw.(string)
roleEntry.BoundIamRoleARNs = boundIamRoleARNRaw.([]string)
}
if boundIamInstanceProfileARNRaw, ok := data.GetOk("bound_iam_instance_profile_arn"); ok {
roleEntry.BoundIamInstanceProfileARN = boundIamInstanceProfileARNRaw.(string)
roleEntry.BoundIamInstanceProfileARNs = boundIamInstanceProfileARNRaw.([]string)
}
if boundIamPrincipalARNRaw, ok := data.GetOk("bound_iam_principal_arn"); ok {
principalARN := boundIamPrincipalARNRaw.(string)
roleEntry.BoundIamPrincipalARN = principalARN
// Explicitly not checking to see if the user has changed the ARN under us
// This allows the user to sumbit an update with the same ARN to force Vault
// to re-resolve the ARN to the unique ID, in case an entity was deleted and
// recreated
if roleEntry.ResolveAWSUniqueIDs && roleEntry.BoundIamPrincipalARN != "" && !strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
principalID, err := b.resolveArnToUniqueIDFunc(ctx, req.Storage, principalARN)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("failed updating the unique ID of ARN %#v: %#v", principalARN, err)), nil
principalARNs := boundIamPrincipalARNRaw.([]string)
roleEntry.BoundIamPrincipalARNs = principalARNs
roleEntry.BoundIamPrincipalIDs = []string{}
}
if roleEntry.ResolveAWSUniqueIDs && len(roleEntry.BoundIamPrincipalIDs) == 0 {
// we might be turning on resolution on this role, so ensure we update the IDs
for _, principalARN := range roleEntry.BoundIamPrincipalARNs {
if !strings.HasSuffix(principalARN, "*") {
principalID, err := b.resolveArnToUniqueIDFunc(ctx, req.Storage, principalARN)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("unable to resolve ARN %#v to internal ID: %#v", principalARN, err)), nil
}
roleEntry.BoundIamPrincipalIDs = append(roleEntry.BoundIamPrincipalIDs, principalID)
}
roleEntry.BoundIamPrincipalID = principalID
} else {
// Need to handle the case where we're switching from a non-wildcard principal to a wildcard principal
roleEntry.BoundIamPrincipalID = ""
}
} else if roleEntry.ResolveAWSUniqueIDs && roleEntry.BoundIamPrincipalARN != "" && !strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") {
// we're turning on resolution on this role, so ensure we update it
principalID, err := b.resolveArnToUniqueIDFunc(ctx, req.Storage, roleEntry.BoundIamPrincipalARN)
if err != nil {
return logical.ErrorResponse(fmt.Sprintf("unable to resolve ARN %#v to internal ID: %#v", roleEntry.BoundIamPrincipalARN, err)), nil
}
roleEntry.BoundIamPrincipalID = principalID
}
if inferRoleTypeRaw, ok := data.GetOk("inferred_entity_type"); ok {
@@ -581,56 +619,56 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
numBinds := 0
if roleEntry.BoundAccountID != "" {
if len(roleEntry.BoundAccountIDs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_account_id but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
numBinds++
}
if roleEntry.BoundRegion != "" {
if len(roleEntry.BoundRegions) > 0 {
if roleEntry.AuthType != ec2AuthType {
return logical.ErrorResponse("specified bound_region but not allowing ec2 auth_type"), nil
}
numBinds++
}
if roleEntry.BoundAmiID != "" {
if len(roleEntry.BoundAmiIDs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_ami_id but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
numBinds++
}
if roleEntry.BoundIamInstanceProfileARN != "" {
if len(roleEntry.BoundIamInstanceProfileARNs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_iam_instance_profile_arn but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
numBinds++
}
if roleEntry.BoundIamRoleARN != "" {
if len(roleEntry.BoundIamRoleARNs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_iam_role_arn but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
numBinds++
}
if roleEntry.BoundIamPrincipalARN != "" {
if len(roleEntry.BoundIamPrincipalARNs) > 0 {
if roleEntry.AuthType != iamAuthType {
return logical.ErrorResponse("specified bound_iam_principal_arn but not allowing iam auth_type"), nil
}
numBinds++
}
if roleEntry.BoundVpcID != "" {
if len(roleEntry.BoundVpcIDs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_vpc_id but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
numBinds++
}
if roleEntry.BoundSubnetID != "" {
if len(roleEntry.BoundSubnetIDs) > 0 {
if !allowEc2Binds {
return logical.ErrorResponse(fmt.Sprintf("specified bound_subnet_id but not allowing ec2 auth_type or inferring %s", ec2EntityType)), nil
}
@@ -751,29 +789,82 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
return &resp, nil
}
// Struct to hold the information associated with an AMI ID in Vault.
// Struct to hold the information associated with a Vault role
type awsRoleEntry struct {
AuthType string `json:"auth_type"`
BoundAmiID string `json:"bound_ami_id"`
BoundAccountID string `json:"bound_account_id"`
BoundIamPrincipalARN string `json:"bound_iam_principal_arn"`
BoundIamPrincipalID string `json:"bound_iam_principal_id"`
BoundIamRoleARN string `json:"bound_iam_role_arn"`
BoundIamInstanceProfileARN string `json:"bound_iam_instance_profile_arn"`
BoundRegion string `json:"bound_region"`
BoundSubnetID string `json:"bound_subnet_id"`
BoundVpcID string `json:"bound_vpc_id"`
InferredEntityType string `json:"inferred_entity_type"`
InferredAWSRegion string `json:"inferred_aws_region"`
ResolveAWSUniqueIDs bool `json:"resolve_aws_unique_ids"`
RoleTag string `json:"role_tag"`
AllowInstanceMigration bool `json:"allow_instance_migration"`
TTL time.Duration `json:"ttl"`
MaxTTL time.Duration `json:"max_ttl"`
Policies []string `json:"policies"`
DisallowReauthentication bool `json:"disallow_reauthentication"`
HMACKey string `json:"hmac_key"`
Period time.Duration `json:"period"`
AuthType string `json:"auth_type" `
BoundAmiIDs []string `json:"bound_ami_id_list"`
BoundAccountIDs []string `json:"bound_account_id_list"`
BoundIamPrincipalARNs []string `json:"bound_iam_principal_arn_list"`
BoundIamPrincipalIDs []string `json:"bound_iam_principal_id_list"`
BoundIamRoleARNs []string `json:"bound_iam_role_arn_list"`
BoundIamInstanceProfileARNs []string `json:"bound_iam_instance_profile_arn_list"`
BoundRegions []string `json:"bound_region_list"`
BoundSubnetIDs []string `json:"bound_subnet_id_list"`
BoundVpcIDs []string `json:"bound_vpc_id_list"`
InferredEntityType string `json:"inferred_entity_type"`
InferredAWSRegion string `json:"inferred_aws_region"`
ResolveAWSUniqueIDs bool `json:"resolve_aws_unique_ids"`
RoleTag string `json:"role_tag"`
AllowInstanceMigration bool `json:"allow_instance_migration"`
TTL time.Duration `json:"ttl"`
MaxTTL time.Duration `json:"max_ttl"`
Policies []string `json:"policies"`
DisallowReauthentication bool `json:"disallow_reauthentication"`
HMACKey string `json:"hmac_key"`
Period time.Duration `json:"period"`
Version int `json:"version"`
// DEPRECATED -- these are the old fields before we supported lists and exist for backwards compatibility
BoundAmiID string `json:"bound_ami_id,omitempty" `
BoundAccountID string `json:"bound_account_id,omitempty"`
BoundIamPrincipalARN string `json:"bound_iam_principal_arn,omitempty"`
BoundIamPrincipalID string `json:"bound_iam_principal_id,omitempty"`
BoundIamRoleARN string `json:"bound_iam_role_arn,omitempty"`
BoundIamInstanceProfileARN string `json:"bound_iam_instance_profile_arn,omitempty"`
BoundRegion string `json:"bound_region,omitempty"`
BoundSubnetID string `json:"bound_subnet_id,omitempty"`
BoundVpcID string `json:"bound_vpc_id,omitempty"`
}
func (r *awsRoleEntry) ToResponseData() map[string]interface{} {
responseData := map[string]interface{}{
"auth_type": r.AuthType,
"bound_ami_id": r.BoundAmiIDs,
"bound_account_id": r.BoundAccountIDs,
"bound_iam_principal_arn": r.BoundIamPrincipalARNs,
"bound_iam_principal_id": r.BoundIamPrincipalIDs,
"bound_iam_role_arn": r.BoundIamRoleARNs,
"bound_iam_instance_profile_arn": r.BoundIamInstanceProfileARNs,
"bound_region": r.BoundRegions,
"bound_subnet_id": r.BoundSubnetIDs,
"bound_vpc_id": r.BoundVpcIDs,
"inferred_entity_type": r.InferredEntityType,
"inferred_aws_region": r.InferredAWSRegion,
"resolve_aws_unique_ids": r.ResolveAWSUniqueIDs,
"role_tag": r.RoleTag,
"allow_instance_migration": r.AllowInstanceMigration,
"ttl": r.TTL / time.Second,
"max_ttl": r.MaxTTL / time.Second,
"policies": r.Policies,
"disallow_reauthentication": r.DisallowReauthentication,
"period": r.Period / time.Second,
}
convertNilToEmptySlice := func(data map[string]interface{}, field string) {
if data[field] == nil || len(data[field].([]string)) == 0 {
data[field] = []string{}
}
}
convertNilToEmptySlice(responseData, "bound_ami_id")
convertNilToEmptySlice(responseData, "bound_account_id")
convertNilToEmptySlice(responseData, "bound_iam_principal_arn")
convertNilToEmptySlice(responseData, "bound_iam_principal_id")
convertNilToEmptySlice(responseData, "bound_iam_role_arn")
convertNilToEmptySlice(responseData, "bound_iam_instance_profile_arn")
convertNilToEmptySlice(responseData, "bound_region")
convertNilToEmptySlice(responseData, "bound_subnet_id")
convertNilToEmptySlice(responseData, "bound_vpc_id")
return responseData
}
const pathRoleSyn = `

View File

@@ -8,6 +8,7 @@ import (
"time"
"github.com/hashicorp/vault/helper/policyutil"
"github.com/hashicorp/vault/helper/strutil"
"github.com/hashicorp/vault/logical"
)
@@ -187,10 +188,11 @@ func Test_enableIamIDResolution(t *testing.T) {
b.resolveArnToUniqueIDFunc = resolveArnToFakeUniqueId
boundIamRoleARNs := []string{"arn:aws:iam::123456789012:role/MyRole", "arn:aws:iam::123456789012:role/path/*"}
data := map[string]interface{}{
"auth_type": iamAuthType,
"policies": "p,q",
"bound_iam_principal_arn": "arn:aws:iam::123456789012:role/MyRole",
"bound_iam_principal_arn": boundIamRoleARNs,
"resolve_aws_unique_ids": false,
}
@@ -218,7 +220,7 @@ func Test_enableIamIDResolution(t *testing.T) {
if resp == nil || resp.IsError() {
t.Fatalf("failed to read role: resp:%#v,\nerr:%#v", resp, err)
}
if resp.Data["bound_iam_principal_id"] != "" {
if resp.Data["bound_iam_principal_id"] != nil && len(resp.Data["bound_iam_principal_id"].([]string)) > 0 {
t.Fatalf("expected to get no unique ID in role, but got %q", resp.Data["bound_iam_principal_id"])
}
@@ -240,9 +242,14 @@ func Test_enableIamIDResolution(t *testing.T) {
if resp == nil || resp.IsError() {
t.Fatalf("failed to read role: resp:%#v,\nerr:%#v", resp, err)
}
if resp.Data["bound_iam_principal_id"] != "FakeUniqueId1" {
principalIDs := resp.Data["bound_iam_principal_id"].([]string)
if len(principalIDs) != 1 || principalIDs[0] != "FakeUniqueId1" {
t.Fatalf("bad: expected upgrade of role resolve principal ID to %q, but got %q instead", "FakeUniqueId1", resp.Data["bound_iam_principal_id"])
}
returnedARNs := resp.Data["bound_iam_principal_arn"].([]string)
if !strutil.EquivalentSlices(returnedARNs, boundIamRoleARNs) {
t.Fatalf("bad: expected to return bound_iam_principal_arn of %q, but got %q instead", boundIamRoleARNs, returnedARNs)
}
}
func TestBackend_pathIam(t *testing.T) {
@@ -466,7 +473,8 @@ func TestBackend_pathRoleMixedTypes(t *testing.T) {
data["auth_type"] = iamAuthType
delete(data, "bound_ami_id")
data["bound_iam_principal_arn"] = "arn:aws:iam::123456789012:role/MyRole"
boundIamPrincipalARNs := []string{"arn:aws:iam::123456789012:role/MyRole", "arn:aws:iam::123456789012:role/path/*"}
data["bound_iam_principal_arn"] = boundIamPrincipalARNs
resp, err = submitRequest("ec2_to_iam", logical.UpdateOperation)
if resp == nil || !resp.IsError() {
t.Fatalf("changed auth type on the role")
@@ -499,9 +507,14 @@ func TestBackend_pathRoleMixedTypes(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if resp.Data["bound_iam_principal_id"] != "FakeUniqueId1" {
principalIDs := resp.Data["bound_iam_principal_id"].([]string)
if len(principalIDs) != 1 || principalIDs[0] != "FakeUniqueId1" {
t.Fatalf("expected fake unique ID of FakeUniqueId1, got %q", resp.Data["bound_iam_principal_id"])
}
returnedARNs := resp.Data["bound_iam_principal_arn"].([]string)
if !strutil.EquivalentSlices(returnedARNs, boundIamPrincipalARNs) {
t.Fatalf("bad: expected to return bound_iam_principal_arn of %q, but got %q instead", boundIamPrincipalARNs, returnedARNs)
}
data["resolve_aws_unique_ids"] = false
resp, err = submitRequest("withInternalIdResolution", logical.UpdateOperation)
if err != nil {
@@ -584,15 +597,15 @@ func TestAwsEc2_RoleCrud(t *testing.T) {
expected := map[string]interface{}{
"auth_type": ec2AuthType,
"bound_ami_id": "testamiid",
"bound_account_id": "testaccountid",
"bound_region": "testregion",
"bound_iam_principal_arn": "",
"bound_iam_principal_id": "",
"bound_iam_role_arn": "arn:aws:iam::123456789012:role/MyRole",
"bound_iam_instance_profile_arn": "arn:aws:iam::123456789012:instance-profile/MyInstanceProfile",
"bound_subnet_id": "testsubnetid",
"bound_vpc_id": "testvpcid",
"bound_ami_id": []string{"testamiid"},
"bound_account_id": []string{"testaccountid"},
"bound_region": []string{"testregion"},
"bound_iam_principal_arn": []string{},
"bound_iam_principal_id": []string{},
"bound_iam_role_arn": []string{"arn:aws:iam::123456789012:role/MyRole"},
"bound_iam_instance_profile_arn": []string{"arn:aws:iam::123456789012:instance-profile/MyInstanceProfile"},
"bound_subnet_id": []string{"testsubnetid"},
"bound_vpc_id": []string{"testvpcid"},
"inferred_entity_type": "",
"inferred_aws_region": "",
"resolve_aws_unique_ids": false,
@@ -624,7 +637,7 @@ func TestAwsEc2_RoleCrud(t *testing.T) {
t.Fatalf("resp: %#v, err: %v", resp, err)
}
expected["bound_vpc_id"] = "newvpcid"
expected["bound_vpc_id"] = []string{"newvpcid"}
if !reflect.DeepEqual(expected, resp.Data) {
t.Fatalf("bad: role data: expected: %#v\n actual: %#v", expected, resp.Data)

View File

@@ -529,11 +529,13 @@ Registers a role in the method. Only those instances or principals which
are using the role registered using this endpoint, will be able to perform
the login operation. Contraints can be specified on the role, that are
applied on the instances or principals attempting to login. At least one
constraint should be specified on the role. The available constraints you
constraint must be specified on the role. The available constraints you
can choose are dependent on the `auth_type` of the role and, if the
`auth_type` is `iam`, then whether inferencing is enabled. A role will not
let you configure a constraint if it is not checked by the `auth_type` and
inferencing configuration of that role.
inferencing configuration of that role. For the constraints which accept a list
of values, the authenticating instance/principal must match any one value in the
list in order to satisfy that constraint.
| Method | Path | Produces |
| :------- | :--------------------------- | :--------------------- |
@@ -547,53 +549,64 @@ inferencing configuration of that role.
"iam" (except for legacy `aws-ec2` auth types, for which it will default to
"ec2"). Only those bindings applicable to the auth type chosen will be allowed
to be configured on the role.
- `bound_ami_id` `(string: "")` - If set, defines a constraint on the EC2
instances that they should be using the AMI ID specified by this parameter.
- `bound_ami_id` `(list: [])` - If set, defines a constraint on the EC2
instances that they should be using one of the AMI ID specified by this parameter.
This constraint is checked during ec2 auth as well as the iam auth method only
when inferring an EC2 instance.
- `bound_account_id` `(string: "")` - If set, defines a constraint on the EC2
instances that the account ID in its identity document to match the one
when inferring an EC2 instance. This is a comma-separated string or JSON
array.
- `bound_account_id` `(list: [])` - If set, defines a constraint on the EC2
instances that the account ID in its identity document to match one of the ones
specified by this parameter. This constraint is checked during ec2 auth as
well as the iam auth method only when inferring an EC2 instance.
- `bound_region` `(string: "")` - If set, defines a constraint on the EC2
instances that the region in its identity document must match the one
specified by this parameter. This constraint is only checked by the ec2 auth
well as the iam auth method only when inferring an EC2 instance. This is a
comma-separated string or JSON array.
- `bound_region` `(list: [])` - If set, defines a constraint on the EC2
instances that the region in its identity document must match one of the
regions specified by this parameter. This constraint is only checked by the ec2 auth
method as well as the iam auth method only when inferring an ec2 instance.
- `bound_vpc_id` `(string: "")` - If set, defines a constraint on the EC2
instance to be associated with the VPC ID that matches the value specified by
This is a comma-separated string or JSON array.
- `bound_vpc_id` `(list: [])` - If set, defines a constraint on the EC2
instance to be associated with a VPC ID that matches one of the values specified by
this parameter. This constraint is only checked by the ec2 auth method as well
as the iam auth method only when inferring an ec2 instance.
- `bound_subnet_id` `(string: "")` - If set, defines a constraint on the EC2
instance to be associated with the subnet ID that matches the value specified
as the iam auth method only when inferring an ec2 instance. This is a
comma-separated string or JSON array.
- `bound_subnet_id` `(list: [])` - If set, defines a constraint on the EC2
instance to be associated with a subnet ID that matches one of the values specified
by this parameter. This constraint is only checked by the ec2 auth method as
well as the iam auth method only when inferring an ec2 instance.
- `bound_iam_role_arn` `(string: "")` - If set, defines a constraint on the
authenticating EC2 instance that it must match the IAM role ARN specified by
well as the iam auth method only when inferring an ec2 instance. This is a
comma-separated string or a JSON array.
- `bound_iam_role_arn` `(list: [])` - If set, defines a constraint on the
authenticating EC2 instance that it must match one of the IAM role ARNs specified by
this parameter. The value is refix-matched (as though it were a glob ending
in `*`). The configured IAM user or EC2 instance role must be allowed to
execute the `iam:GetInstanceProfile` action if this is specified. This
constraint is checked by the ec2 auth method as well as the iam auth method
only when inferring an EC2 instance.
- `bound_iam_instance_profile_arn` `(string: "")` - If set, defines a constraint
only when inferring an EC2 instance. This is a comma-separated string or a
JSON array.
- `bound_iam_instance_profile_arn` `(list: [])` - If set, defines a constraint
on the EC2 instances to be associated with an IAM instance profile ARN which
has a prefix that matches the value specified by this parameter. The value is
has a prefix that matches one of the values specified by this parameter. The value is
prefix-matched (as though it were a glob ending in `*`). This constraint is
checked by the ec2 auth method as well as the iam auth method only when
inferring an ec2 instance.
inferring an ec2 instance. This is a comma-separated string or a JSON array.
- `role_tag` `(string: "")` - If set, enables the role tags for this role. The
value set for this field should be the 'key' of the tag on the EC2 instance.
The 'value' of the tag should be generated using `role/<role>/tag` endpoint.
Defaults to an empty string, meaning that role tags are disabled. This
constraint is valid only with the ec2 auth method and is not allowed when an
auth_type is iam.
- `bound_iam_principal_arn` `(string: "")` - Defines the IAM principal that must
be authenticated using the iam auth method. It should look like
"arn:aws:iam::123456789012:user/MyUserName" or
constraint is valid only with the ec2 auth method and is not allowed when
`auth_type` is iam.
- `bound_iam_principal_arn` `(list: [])` - Defines the list of IAM principals
that are permitted to login to the role using the iam auth method. Individual
values should look like "arn:aws:iam::123456789012:user/MyUserName" or
"arn:aws:iam::123456789012:role/MyRoleName". Wildcards are supported at the
end of the ARN, e.g., "arn:aws:iam::123456789012:\*" will match any IAM
principal in the AWS account 123456789012. This constraint is only checked by
principal in the AWS account 123456789012. When `resolve_aws_unique_ids` is
`false` and you are binding to IAM roles (as opposed to users) and you are not
using a wildcard at the end, then you must specify the ARN by ommitting any
path component; see the documentation for `resolve_aws_unique_ids` below.
This constraint is only checked by
the iam auth method. Wildcards are supported at the end of the ARN, e.g.,
"arn:aws:iam::123456789012:role/\*" will match all roles in the AWS account.
This is a comma-separated string or JSON array.
- `inferred_entity_type` `(string: "")` - When set, instructs Vault to turn on
inferencing. The only current valid value is "ec2\_instance" instructing Vault
to infer that the role comes from an EC2 instance in an IAM instance profile.
@@ -631,11 +644,13 @@ inferencing configuration of that role.
Vault still has the necessary IAM permissions to resolve the unique ID, Vault
will update the unique ID. (If it does not have the necessary permissions to
resolve the unique ID, then it will fail to update.) If this option is set to
false, then you MUST leave out the path component in bound_iam_principal_arn
for **roles** only, but not IAM users. That is, if your IAM role ARN is of the
form `arn:aws:iam::123456789012:role/some/path/to/MyRoleName`, you **must**
specify a bound_iam_principal_arn of
`arn:aws:iam::123456789012:role/MyRoleName` for authentication to work.
false, then you MUST leave out the path component in `bound_iam_principal_arn`
for **roles** that do not specify a wildcard at the end, but not IAM users or
role bindings that have a wildcard. That is, if your IAM role ARN is of the
form `arn:aws:iam::123456789012:role/some/path/to/MyRoleName`, and
`resolve_aws_unique_ids` is `false`, you **must** specify a
`bound_iam_principal_arn` of `arn:aws:iam::123456789012:role/MyRoleName` for
authentication to work.
- `ttl` `(string: "")` - The TTL period of tokens issued using this role,
provided as "1h", where hour is the largest suffix.
- `max_ttl` `(string: "")` - The maximum allowed lifetime of tokens issued using
@@ -665,7 +680,7 @@ inferencing configuration of that role.
```json
{
"bound_ami_id": "ami-fce36987",
"bound_ami_id": ["ami-fce36987"],
"role_tag": "",
"policies": [
"default",
@@ -713,7 +728,7 @@ $ curl \
```json
{
"data": {
"bound_ami_id": "ami-fce36987",
"bound_ami_id": ["ami-fce36987"],
"role_tag": "",
"policies": [
"default",

View File

@@ -115,12 +115,16 @@ method and associated with a specific authentication type that cannot be
changed once the role has been created. Roles can also be associated with
various optional restrictions, such as the set of allowed policies and max TTLs
on the generated tokens. Each role can be specified with the constraints that
are to be met during the login. For example, one such constraint that is
supported is to bind against AMI ID. A role which is bound to a specific AMI,
can only be used for login by EC2 instances that are deployed on the same AMI.
are to be met during the login. Many of these contraints accept lists of
required values. For any constraint which accepts a list of values, that
constraint will be considered satisfied if any one of the values is matched
during the login process. For example, one such constraint that is
supported is to bind against a list of AMI IDs. A role which is bound to a
specific list of AMIs can only be used for login by EC2 instances that are
deployed to one of the AMIs that the role is bound to.
The iam auth method allows you to specify a bound IAM principal ARN.
Clients authenticating to Vault must have an ARN that matches the ARN bound to
The iam auth method allows you to specify bound IAM principal ARNs.
Clients authenticating to Vault must have an ARN that matches one of the ARNs bound to
the role they are attempting to login to. The bound ARN allows specifying a
wildcard at the end of the bound ARN. For example, if the bound ARN were
`arn:aws:iam::123456789012:*` it would allow any principal in AWS account