From caf90f58d8832aa0ee18525423744c4426e7250f Mon Sep 17 00:00:00 2001 From: Joel Thompson Date: Wed, 30 Aug 2017 17:51:48 -0400 Subject: [PATCH] auth/aws: Allow wildcard in bound_iam_principal_id (#3213) --- builtin/credential/aws/backend.go | 13 +- builtin/credential/aws/backend_test.go | 68 ++++- builtin/credential/aws/client.go | 19 ++ builtin/credential/aws/path_login.go | 97 ++++++- builtin/credential/aws/path_role.go | 10 +- website/source/api/auth/aws/index.html.md | 316 +++++++++++----------- website/source/docs/auth/aws.html.md | 69 ++++- 7 files changed, 412 insertions(+), 180 deletions(-) diff --git a/builtin/credential/aws/backend.go b/builtin/credential/aws/backend.go index 9497436b4..30feba9ca 100644 --- a/builtin/credential/aws/backend.go +++ b/builtin/credential/aws/backend.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" + "github.com/patrickmn/go-cache" ) func Factory(conf *logical.BackendConfig) (logical.Backend, error) { @@ -60,6 +61,11 @@ type backend struct { // will be flushed. The empty STS role signifies the master account IAMClientsMap map[string]map[string]*iam.IAM + // Map of AWS unique IDs to the full ARN corresponding to that unique ID + // This avoids the overhead of an AWS API hit for every login request + // using the IAM auth method when bound_iam_principal_arn contains a wildcard + iamUserIdToArnCache *cache.Cache + // AWS Account ID of the "default" AWS credentials // This cache avoids the need to call GetCallerIdentity repeatedly to learn it // We can't store this because, in certain pathological cases, it could change @@ -74,9 +80,10 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { b := &backend{ // Setting the periodic func to be run once in an hour. // If there is a real need, this can be made configurable. - tidyCooldownPeriod: time.Hour, - EC2ClientsMap: make(map[string]map[string]*ec2.EC2), - IAMClientsMap: make(map[string]map[string]*iam.IAM), + tidyCooldownPeriod: time.Hour, + EC2ClientsMap: make(map[string]map[string]*ec2.EC2), + IAMClientsMap: make(map[string]map[string]*iam.IAM), + iamUserIdToArnCache: cache.New(7*24*time.Hour, 24*time.Hour), } b.resolveArnToUniqueIDFunc = b.resolveArnToRealUniqueId diff --git a/builtin/credential/aws/backend_test.go b/builtin/credential/aws/backend_test.go index b23d0ee24..881ca85dc 100644 --- a/builtin/credential/aws/backend_test.go +++ b/builtin/credential/aws/backend_test.go @@ -1522,22 +1522,14 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { t.Fatalf("bad: expected valid login: resp:%#v", resp) } - renewReq := &logical.Request{ - Storage: storage, - Auth: &logical.Auth{}, - } + renewReq := generateRenewRequest(storage, resp.Auth) + // dump a fake ARN into the metadata to ensure that we ONLY look + // at the unique ID that has been generated + renewReq.Auth.Metadata["canonical_arn"] = "fake_arn" empty_login_fd := &framework.FieldData{ Raw: map[string]interface{}{}, Schema: pathLogin(b).Fields, } - renewReq.Auth.InternalData = resp.Auth.InternalData - renewReq.Auth.Metadata = resp.Auth.Metadata - renewReq.Auth.LeaseOptions = resp.Auth.LeaseOptions - renewReq.Auth.Policies = resp.Auth.Policies - renewReq.Auth.IssueTime = time.Now() - // dump a fake ARN into the metadata to ensure that we ONLY look - // at the unique ID that has been generated - renewReq.Auth.Metadata["canonical_arn"] = "fake_arn" // ensure we can renew resp, err = b.pathLoginRenew(renewReq, empty_login_fd) if err != nil { @@ -1571,5 +1563,57 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { if err == nil || (resp != nil && !resp.IsError()) { t.Errorf("bad: expected failed renew due to changed AWS role ID: resp: %#v", resp, err) } + // Undo the fake resolver... + b.resolveArnToUniqueIDFunc = b.resolveArnToRealUniqueId + // Now test that wildcard matching works + wildcardRoleName := "valid_wildcard" + wildcardEntity := *entity + wildcardEntity.FriendlyName = "*" + roleData["bound_iam_principal_arn"] = wildcardEntity.canonicalArn() + roleRequest.Path = "role/" + wildcardRoleName + resp, err = b.HandleRequest(roleRequest) + if err != nil || (resp != nil && resp.IsError()) { + t.Fatalf("bad: failed to create wildcard role: resp:%#v\nerr:%v", resp, err) + } + + loginData["role"] = wildcardRoleName + resp, err = b.HandleRequest(loginRequest) + if err != nil { + t.Fatal(err) + } + if resp == nil || resp.Auth == nil || resp.IsError() { + t.Fatalf("bad: expected valid login: resp:%#v", resp) + } + // and ensure we can renew + renewReq = generateRenewRequest(storage, resp.Auth) + resp, err = b.pathLoginRenew(renewReq, empty_login_fd) + if err != nil { + t.Fatal(err) + } + if resp == nil { + t.Fatal("got nil response from renew") + } + if resp.IsError() { + t.Fatalf("got error when renewing: %#v", *resp) + } + // ensure the cache is populated + cachedArn := b.getCachedUserId(resp.Auth.Metadata["client_user_id"]) + if cachedArn == "" { + t.Errorf("got empty ARN back from user ID cache; expected full arn") + } +} + +func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Request { + renewReq := &logical.Request{ + Storage: s, + Auth: &logical.Auth{}, + } + renewReq.Auth.InternalData = auth.InternalData + renewReq.Auth.Metadata = auth.Metadata + renewReq.Auth.LeaseOptions = auth.LeaseOptions + renewReq.Auth.Policies = auth.Policies + renewReq.Auth.IssueTime = time.Now() + + return renewReq } diff --git a/builtin/credential/aws/client.go b/builtin/credential/aws/client.go index 3dc9327a3..aa3da0d12 100644 --- a/builtin/credential/aws/client.go +++ b/builtin/credential/aws/client.go @@ -141,6 +141,25 @@ func (b *backend) flushCachedIAMClients() { } } +// Gets an entry out of the user ID cache +func (b *backend) getCachedUserId(userId string) string { + if userId == "" { + return "" + } + if entry, ok := b.iamUserIdToArnCache.Get(userId); ok { + b.iamUserIdToArnCache.SetDefault(userId, entry) + return entry.(string) + } + return "" +} + +// Sets an entry in the user ID cache +func (b *backend) setCachedUserId(userId, arn string) { + if userId != "" { + b.iamUserIdToArnCache.SetDefault(userId, arn) + } +} + func (b *backend) stsRoleForAccount(s logical.Storage, accountID string) (string, error) { // Check if an STS configuration exists for the AWS account sts, err := b.lockedAwsStsEntry(s, accountID) diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index 60547a8cf..2f22c4d17 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -928,6 +928,12 @@ func (b *backend) pathLoginRenewIam( } } + // Note that the error messages below can leak a little bit of information about the role information + // For example, if on renew, the client gets the "error parsing ARN..." error message, the client + // will know that it's a wildcard bind (but not the actual bind), even if the client can't actually + // 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 != "" { // We might not get here if all bindings were on the inferred entity, which we've already validated // above @@ -936,10 +942,31 @@ func (b *backend) pathLoginRenewIam( // 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 ID %q", clientUserId) + return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn) + } + } else if strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") { + fullArn := b.getCachedUserId(clientUserId) + if fullArn == "" { + entity, err := parseIamArn(canonicalArn) + if err != nil { + return nil, fmt.Errorf("error parsing ARN %q: %v", canonicalArn, err) + } + fullArn, err = b.fullArn(entity, req.Storage) + if err != nil { + return nil, fmt.Errorf("error looking up full ARN of entity %v: %v", entity, err) + } + if fullArn == "" { + return nil, fmt.Errorf("got empty string back when looking up full ARN of entity %v", entity) + } + if clientUserId != "" { + b.setCachedUserId(clientUserId, fullArn) + } + } + if !strutil.GlobbedStringsMatch(roleEntry.BoundIamPrincipalARN, fullArn) { + 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) + return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn) } } @@ -1129,7 +1156,7 @@ func (b *backend) pathLoginUpdateIam( callerUniqueId := strings.Split(callerID.UserId, ":")[0] entity, err := parseIamArn(callerID.Arn) if err != nil { - return logical.ErrorResponse(fmt.Sprintf("Error parsing arn: %v", err)), nil + return logical.ErrorResponse(fmt.Sprintf("error parsing arn %q: %v", callerID.Arn, err)), nil } roleName := data.Get("role").(string) @@ -1158,8 +1185,27 @@ func (b *backend) pathLoginUpdateIam( 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 != "" && roleEntry.BoundIamPrincipalARN != entity.canonicalArn() { - return logical.ErrorResponse(fmt.Sprintf("IAM Principal %q does not belong to the role %q", callerID.Arn, roleName)), nil + } else if roleEntry.BoundIamPrincipalARN != "" { + if strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") { + fullArn := b.getCachedUserId(callerUniqueId) + if fullArn == "" { + fullArn, err = b.fullArn(entity, req.Storage) + if err != nil { + return logical.ErrorResponse(fmt.Sprintf("error looking up full ARN of entity %v: %v", entity, err)), nil + } + if fullArn == "" { + return logical.ErrorResponse(fmt.Sprintf("got empty string back when looking up full ARN of entity %v", entity)), nil + } + 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. + 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 + } } policies := roleEntry.Policies @@ -1508,6 +1554,7 @@ type iamEntity struct { SessionInfo string } +// Returns a Vault-internal canonical ARN for referring to an IAM entity func (e *iamEntity) canonicalArn() string { entityType := e.Type // canonicalize "assumed-role" into "role" @@ -1522,6 +1569,46 @@ func (e *iamEntity) canonicalArn() string { return fmt.Sprintf("arn:%s:iam::%s:%s/%s", e.Partition, e.AccountNumber, entityType, e.FriendlyName) } +// This returns the "full" ARN of an iamEntity, how it would be referred to in AWS proper +func (b *backend) fullArn(e *iamEntity, s logical.Storage) (string, error) { + // Not assuming path is reliable for any entity types + client, err := b.clientIAM(s, getAnyRegionForAwsPartition(e.Partition).ID(), e.AccountNumber) + if err != nil { + return "", fmt.Errorf("error creating IAM client: %v", err) + } + + switch e.Type { + case "user": + input := iam.GetUserInput{ + UserName: aws.String(e.FriendlyName), + } + resp, err := client.GetUser(&input) + if err != nil { + return "", fmt.Errorf("error fetching user %q: %v", e.FriendlyName, err) + } + if resp == nil { + return "", fmt.Errorf("nil response from GetUser") + } + return *(resp.User.Arn), nil + case "assumed-role": + fallthrough + case "role": + input := iam.GetRoleInput{ + RoleName: aws.String(e.FriendlyName), + } + resp, err := client.GetRole(&input) + if err != nil { + return "", fmt.Errorf("error fetching role %q: %v", e.FriendlyName, err) + } + if resp == nil { + return "", fmt.Errorf("nil response form GetRole") + } + return *(resp.Role.Arn), nil + default: + return "", fmt.Errorf("unrecognized entity type: %s", e.Type) + } +} + const iamServerIdHeader = "X-Vault-AWS-IAM-Server-ID" const pathLoginSyn = ` diff --git a/builtin/credential/aws/path_role.go b/builtin/credential/aws/path_role.go index 79f7a518e..78ff0dad1 100644 --- a/builtin/credential/aws/path_role.go +++ b/builtin/credential/aws/path_role.go @@ -318,7 +318,8 @@ func (b *backend) upgradeRoleEntry(s logical.Storage, roleEntry *awsRoleEntry) ( if roleEntry.AuthType == iamAuthType && roleEntry.ResolveAWSUniqueIDs && roleEntry.BoundIamPrincipalARN != "" && - roleEntry.BoundIamPrincipalID == "" { + roleEntry.BoundIamPrincipalID == "" && + !strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") { principalId, err := b.resolveArnToUniqueIDFunc(s, roleEntry.BoundIamPrincipalARN) if err != nil { return false, err @@ -493,14 +494,17 @@ func (b *backend) pathRoleCreateUpdate( // 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 { + if roleEntry.ResolveAWSUniqueIDs && !strings.HasSuffix(roleEntry.BoundIamPrincipalARN, "*") { principalID, err := b.resolveArnToUniqueIDFunc(req.Storage, principalARN) if err != nil { return logical.ErrorResponse(fmt.Sprintf("failed updating the unique ID of ARN %#v: %#v", principalARN, err)), nil } 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 != "" { + } 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(req.Storage, roleEntry.BoundIamPrincipalARN) if err != nil { diff --git a/website/source/api/auth/aws/index.html.md b/website/source/api/auth/aws/index.html.md index 01499a072..78ca41d3a 100644 --- a/website/source/api/auth/aws/index.html.md +++ b/website/source/api/auth/aws/index.html.md @@ -35,29 +35,29 @@ capabilities, the credentials are fetched automatically. ### Parameters -- `access_key` `(string: "")` - AWS Access key with permissions to query AWS +- `access_key` `(string: "")` - AWS Access key with permissions to query AWS APIs. The permissions required depend on the specific configurations. If using - the `iam` auth method without inferencing, then no credentials are necessary. - If using the `ec2` auth method or using the `iam` auth method with + the `iam` auth method without inferencing, then no credentials are necessary. + If using the `ec2` auth method or using the `iam` auth method with inferencing, then these credentials need access to `ec2:DescribeInstances`. If - additionally a `bound_iam_role` is specified, then these credentials also need - access to `iam:GetInstanceProfile`. If, however, an alternate sts + additionally a `bound_iam_role` is specified, then these credentials also need + access to `iam:GetInstanceProfile`. If, however, an alternate sts configuration is set for the target account, then the credentials must be - permissioned to call `sts:AssumeRole` on the configured role, and that role + permissioned to call `sts:AssumeRole` on the configured role, and that role must have the permissions described here. -- `secret_key` `(string: "")` - AWS Secret key with permissions to query AWS - APIs. +- `secret_key` `(string: "")` - AWS Secret key with permissions to query AWS + APIs. - `endpoint` `(string: "")` - URL to override the default generated endpoint for making AWS EC2 API calls. - `iam_endpoint` `(string: "")` - URL to override the default generated endpoint for making AWS IAM API calls. - `sts_endpoint` `(string: "")` - URL to override the default generated endpoint for making AWS STS API calls. -- `iam_server_id_header_value` `(string: "")` - The value to require in the - `X-Vault-AWS-IAM-Server-ID` header as part of GetCallerIdentity requests that - are used in the iam auth method. If not set, then no value is required or +- `iam_server_id_header_value` `(string: "")` - The value to require in the + `X-Vault-AWS-IAM-Server-ID` header as part of GetCallerIdentity requests that + are used in the iam auth method. If not set, then no value is required or validated. If set, clients must include an X-Vault-AWS-IAM-Server-ID header in - the headers of login requests, and further this header must be among the + the headers of login requests, and further this header must be among the signed headers validated by AWS. This is to protect against different types of replay attacks, for example a signed request sent to a dev server being resent to a production server. Consider setting this to the Vault server's DNS name. @@ -149,12 +149,12 @@ using the "type" parameter. ### Parameters - `cert_name` `(string: )` - Name of the certificate. -- `aws_public_cert` `(string: )` - AWS Public key required to verify +- `aws_public_cert` `(string: )` - AWS Public key required to verify PKCS7 signature of the EC2 instance metadata. -- `type` `(string: "pkcs7")` - Takes the value of either "pkcs7" or "identity", - indicating the type of document which can be verified using the given - certificate. The PKCS#7 document will have a DSA digest and the identity - signature will have an RSA signature, and accordingly the public certificates +- `type` `(string: "pkcs7")` - Takes the value of either "pkcs7" or "identity", + indicating the type of document which can be verified using the given + certificate. The PKCS#7 document will have a DSA digest and the identity + signature will have an RSA signature, and accordingly the public certificates to verify those also vary. Defaults to "pkcs7". ### Sample Payload @@ -259,11 +259,11 @@ be verified using credentials obtained by assumption of these STS roles. ### Parameters -- `account_id` `(string: )` - AWS account ID to be associated with - STS role. If set, Vault will use assumed credentials to verify any login +- `account_id` `(string: )` - AWS account ID to be associated with + STS role. If set, Vault will use assumed credentials to verify any login attempts from EC2 instances in this account. -- `sts_role` `(string: )` - AWS ARN for STS role to be assumed when - interacting with the account specified. The Vault server must have +- `sts_role` `(string: )` - AWS ARN for STS role to be assumed when + interacting with the account specified. The Vault server must have permissions to assume this role. ### Sample Payload @@ -294,8 +294,8 @@ Returns the previously configured STS role. ### Parameters -- `account_id` `(string: )` - AWS account ID to be associated with - STS role. If set, Vault will use assumed credentials to verify any login +- `account_id` `(string: )` - AWS account ID to be associated with + STS role. If set, Vault will use assumed credentials to verify any login attempts from EC2 instances in this account. ### Sample Request @@ -384,10 +384,10 @@ Configures the periodic tidying operation of the whitelisted identity entries. ### Parameters -- `safety_buffer` `(string: "72h")` - The amount of extra time that must have - passed beyond the `roletag` expiration, before it is removed from the backend +- `safety_buffer` `(string: "72h")` - The amount of extra time that must have + passed beyond the `roletag` expiration, before it is removed from the backend storage. Defaults to 72h. -- `disable_periodic_tidy` `(bool: false)` - If set to 'true', disables the +- `disable_periodic_tidy` `(bool: false)` - If set to 'true', disables the periodic tidying of the `identity-whitelist/` entries. ### Sample Payload @@ -467,10 +467,10 @@ Configures the periodic tidying operation of the blacklisted role tag entries. ### Parameters -- `safety_buffer` `(string: "72h")` - The amount of extra time that must have - passed beyond the `roletag` expiration, before it is removed from the backend +- `safety_buffer` `(string: "72h")` - The amount of extra time that must have + passed beyond the `roletag` expiration, before it is removed from the backend storage. Defaults to 72h. -- `disable_periodic_tidy` `(bool: false)` - If set to 'true', disables the +- `disable_periodic_tidy` `(bool: false)` - If set to 'true', disables the periodic tidying of the `roletag-blacklist/` entries. ### Sample Payload @@ -559,115 +559,121 @@ inferencing configuration of that role. ### Parameters - `role` `(string: )` - Name of the role. -- `auth_type` `(string: "iam")` - The auth type permitted for this role. Valid - choices are "ec2" or "iam". If no value is specified, then it will default to - "iam" (except for legacy `aws-ec2` auth types, for which it will default to +- `auth_type` `(string: "iam")` - The auth type permitted for this role. Valid + choices are "ec2" or "iam". If no value is specified, then it will default to + "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. - This constraint is checked during ec2 auth as well as the iam auth method only +- `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. + 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 - specified by this parameter. This constraint is checked during ec2 auth as +- `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 + 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 +- `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 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 +- `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 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 - by this parameter. This constraint is only checked by the ec2 auth method as +- `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 + 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 - 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 +- `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 + 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 - 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 (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 +- `bound_iam_instance_profile_arn` `(string: "")` - 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 (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. -- `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//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 +- `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//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 - "arn:aws:iam::123456789012:role/MyRoleName". This constraint is only checked - by the iam auth method. -- `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. - This only applies to the iam auth method. If you set this on an existing role - where it had not previously been set, tokens that had been created prior will + "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 + 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. +- `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. + This only applies to the iam auth method. If you set this on an existing role + where it had not previously been set, tokens that had been created prior will not be renewable; clients will need to get a new token. -- `inferred_aws_region` `(string: "")` - When role inferencing is activated, the - region to search for the inferred entities (e.g., EC2 instances). Required if +- `inferred_aws_region` `(string: "")` - When role inferencing is activated, the + region to search for the inferred entities (e.g., EC2 instances). Required if role inferencing is activated. This only applies to the iam auth method. -- `resolve_aws_unique_ids` `(bool: false)` - When set, resolves the - `bound_iam_principal_arn` to the [AWS Unique ID](http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids). - This requires Vault to be able to call `iam:GetUser` or `iam:GetRole` on the - `bound_iam_principal_arn` that is being bound. Resolving to internal AWS IDs - more closely mimics the behavior of AWS services in that if an IAM user or +- `resolve_aws_unique_ids` `(bool: false)` - When set, resolves the + `bound_iam_principal_arn` to the + [AWS Unique ID](http://docs.aws.amazon.com/IAM/latest/UserGuide/reference_identifiers.html#identifiers-unique-ids) + for the bound principal ARN. This field is ignored when + `bound_iam_principal_arn` ends with a wildcard character. + This requires Vault to be able to call `iam:GetUser` or `iam:GetRole` on the + `bound_iam_principal_arn` that is being bound. Resolving to internal AWS IDs + more closely mimics the behavior of AWS services in that if an IAM user or role is deleted and a new one is recreated with the same name, those new users - or roles won't get access to roles in Vault that were permissioned to the - prior principals of the same name. The default value for new roles is true, - while the default value for roles that existed prior to this option existing + or roles won't get access to roles in Vault that were permissioned to the + prior principals of the same name. The default value for new roles is true, + while the default value for roles that existed prior to this option existing is false (you can check the value for a given role using the GET method on the role). Any authentication tokens created prior to this being supported won't - verify the unique ID upon token renewal. When this is changed from false to - true on an existing role, Vault will attempt to resolve the role's bound IAM - ARN to the unique ID and, if unable to do so, will fail to enable this option. - Changing this from `true` to `false` is not supported; if absolutely - necessary, you would need to delete the role and recreate it explicitly - setting it to `false`. However; the instances in which you would want to do - this should be rare. If the role creation (or upgrading to use this) succeed, - then Vault has already been able to resolve internal IDs, and it doesn't need - any further IAM permissions to authenticate users. If a role has been deleted - and recreated, and Vault has cached the old unique ID, you should just call - this endpoint specifying the same `bound_iam_principal_arn` and, as long as - 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 + verify the unique ID upon token renewal. When this is changed from false to + true on an existing role, Vault will attempt to resolve the role's bound IAM + ARN to the unique ID and, if unable to do so, will fail to enable this option. + Changing this from `true` to `false` is not supported; if absolutely + necessary, you would need to delete the role and recreate it explicitly + setting it to `false`. However; the instances in which you would want to do + this should be rare. If the role creation (or upgrading to use this) succeed, + then Vault has already been able to resolve internal IDs, and it doesn't need + any further IAM permissions to authenticate users. If a role has been deleted + and recreated, and Vault has cached the old unique ID, you should just call + this endpoint specifying the same `bound_iam_principal_arn` and, as long as + 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 + 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. -- `ttl` `(string: "")` - The TTL period of tokens issued using this role, +- `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 this role. -- `period` `(string: "")` - If set, indicates that the token generated using +- `period` `(string: "")` - If set, indicates that the token generated using this role should never expire. The token should be renewed within the duration - specified by this value. At each renewal, the token's TTL will be set to the + specified by this value. At each renewal, the token's TTL will be set to the value of this parameter. The maximum allowed lifetime of tokens issued using this role. -- `policies` `(array: [])` - Policies to be set on tokens issued using this +- `policies` `(array: [])` - Policies to be set on tokens issued using this role. -- `allow_instance_migration` `(bool: false)` - If set, allows migration of the - underlying instance where the client resides. This keys off of pendingTime in - the metadata document, so essentially, this disables the client nonce check - whenever the instance is migrated to a new host and pendingTime is newer than +- `allow_instance_migration` `(bool: false)` - If set, allows migration of the + underlying instance where the client resides. This keys off of pendingTime in + the metadata document, so essentially, this disables the client nonce check + whenever the instance is migrated to a new host and pendingTime is newer than the previously-remembered time. Use with caution. This only applies to authentications via the ec2 auth method. -- `disallow_reauthentication` `(bool: false)` - If set, only allows a single - token to be granted per instance ID. In order to perform a fresh login, the +- `disallow_reauthentication` `(bool: false)` - If set, only allows a single + token to be granted per instance ID. In order to perform a fresh login, the entry in whitelist for the instance ID needs to be cleared using - 'auth/aws/identity-whitelist/' endpoint. Defaults to 'false'. + 'auth/aws/identity-whitelist/' endpoint. Defaults to 'false'. This only applies to authentications via the ec2 auth method. ### Sample Payload @@ -822,20 +828,20 @@ given instance can be allowed to gain in a worst-case scenario. ### Parameters - `role` `(string: )` - Name of the role. -- `policies` `(array: [])` - Policies to be associated with the tag. If set, - must be a subset of the role's policies. If set, but set to an empty value, +- `policies` `(array: [])` - Policies to be associated with the tag. If set, + must be a subset of the role's policies. If set, but set to an empty value, only the 'default' policy will be given to issued tokens. - `max_ttl` `(string: "")` - The maximum allowed lifetime of tokens issued using this role. -- `instance_id` `(string: "")` - Instance ID for which this tag is intended for. +- `instance_id` `(string: "")` - Instance ID for which this tag is intended for. If set, the created tag can only be used by the instance with the given ID. -- `allow_instance_migration` `(bool: false)` - If set, allows migration of the - underlying instance where the client resides. This keys off of pendingTime in - the metadata document, so essentially, this disables the client nonce check - whenever the instance is migrated to a new host and pendingTime is newer than +- `allow_instance_migration` `(bool: false)` - If set, allows migration of the + underlying instance where the client resides. This keys off of pendingTime in + the metadata document, so essentially, this disables the client nonce check + whenever the instance is migrated to a new host and pendingTime is newer than the previously-remembered time. Use with caution. Defaults to 'false'. -- `disallow_reauthentication` `(bool: false)` - If set, only allows a single - token to be granted per instance ID. This can be cleared with the +- `disallow_reauthentication` `(bool: false)` - If set, only allows a single + token to be granted per instance ID. This can be cleared with the auth/aws/identity-whitelist endpoint. Defaults to 'false'. ### Sample Payload @@ -888,54 +894,54 @@ along with its RSA digest can be supplied to this endpoint. ### Sample Payload -- `role` `(string: "")` - Name of the role against which the login is being - attempted. If `role` is not specified, then the login endpoint looks for a +- `role` `(string: "")` - Name of the role against which the login is being + attempted. If `role` is not specified, then the login endpoint looks for a role bearing the name of the AMI ID of the EC2 instance that is trying to login if using the ec2 auth method, or the "friendly name" (i.e., role name or username) of the IAM principal authenticated. If a matching role is not found, login fails. -- `identity` `(string: )` - Base64 encoded EC2 instance identity - document. This needs to be supplied along with the `signature` parameter. If - using `curl` for fetching the identity document, consider using the option +- `identity` `(string: )` - Base64 encoded EC2 instance identity + document. This needs to be supplied along with the `signature` parameter. If + using `curl` for fetching the identity document, consider using the option `-w 0` while piping the output to `base64` binary. -- `signature` `(string: )` - Base64 encoded SHA256 RSA signature of - the instance identity document. This needs to be supplied along with +- `signature` `(string: )` - Base64 encoded SHA256 RSA signature of + the instance identity document. This needs to be supplied along with `identity` parameter when using the ec2 auth method. - `pkcs7` `(string: )` - PKCS7 signature of the identity document with all `\n` characters removed. Either this needs to be set *OR* both `identity` and `signature` need to be set when using the ec2 auth method. -- `nonce` `(string: "")` - The nonce to be used for subsequent login requests. - If this parameter is not specified at all and if reauthentication is allowed, +- `nonce` `(string: "")` - The nonce to be used for subsequent login requests. + If this parameter is not specified at all and if reauthentication is allowed, then the backend will generate a random nonce, attaches it to the instance's - identity-whitelist entry and returns the nonce back as part of auth metadata. - This value should be used with further login requests, to establish client - authenticity. Clients can choose to set a custom nonce if preferred, in which - case, it is recommended that clients provide a strong nonce. If a nonce is - provided but with an empty value, it indicates intent to disable - reauthentication. Note that, when `disallow_reauthentication` option is + identity-whitelist entry and returns the nonce back as part of auth metadata. + This value should be used with further login requests, to establish client + authenticity. Clients can choose to set a custom nonce if preferred, in which + case, it is recommended that clients provide a strong nonce. If a nonce is + provided but with an empty value, it indicates intent to disable + reauthentication. Note that, when `disallow_reauthentication` option is enabled on either the role or the role tag, the `nonce` holds no significance. This is ignored unless using the ec2 auth method. -- `iam_http_request_method` `(string: )` - HTTP method used in the - signed request. Currently only POST is supported, but other methods may be +- `iam_http_request_method` `(string: )` - HTTP method used in the + signed request. Currently only POST is supported, but other methods may be supported in the future. This is required when using the iam auth method. - `iam_request_url` `(string: )` - Base64-encoded HTTP URL used in - the signed request. Most likely just `aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8=` - (base64-encoding of `https://sts.amazonaws.com/`) as most requests will + the signed request. Most likely just `aHR0cHM6Ly9zdHMuYW1hem9uYXdzLmNvbS8=` + (base64-encoding of `https://sts.amazonaws.com/`) as most requests will probably use POST with an empty URI. This is required when using the iam auth method. -- `iam_request_body` `(string: )` - Base64-encoded body of the +- `iam_request_body` `(string: )` - Base64-encoded body of the signed request. Most likely - `QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==` which is the - base64 encoding of `Action=GetCallerIdentity&Version=2011-06-15`. This is + `QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==` which is the + base64 encoding of `Action=GetCallerIdentity&Version=2011-06-15`. This is required when using the iam auth method. -- `iam_request_headers` `(string: )` - Base64-encoded, +- `iam_request_headers` `(string: )` - Base64-encoded, JSON-serialized representation of the sts:GetCallerIdentity HTTP request - headers. The JSON serialization assumes that each header key maps to either a - string value or an array of string values (though the length of that array - will probably only be one). If the `iam_server_id_header_value` is configured + headers. The JSON serialization assumes that each header key maps to either a + string value or an array of string values (though the length of that array + will probably only be one). If the `iam_server_id_header_value` is configured in Vault for the aws auth mount, then the headers must include the - X-Vault-AWS-IAM-Server-ID header, its value must match the value configured, - and the header must be included in the signed headers. This is required when + X-Vault-AWS-IAM-Server-ID header, its value must match the value configured, + and the header must be included in the signed headers. This is required when using the iam auth method. @@ -999,7 +1005,7 @@ token. ### Parameters - `role_tag` `(string: )` - Role tag to be blacklisted. The tag can be - supplied as-is. In order to avoid any encoding problems, it can be base64 + supplied as-is. In order to avoid any encoding problems, it can be base64 encoded. ### Sample Request @@ -1022,7 +1028,7 @@ Returns the blacklist entry of a previously blacklisted role tag. ### Parameters - `role_tag` `(string: )` - Role tag to be blacklisted. The tag can be - supplied as-is. In order to avoid any encoding problems, it can be base64 + supplied as-is. In order to avoid any encoding problems, it can be base64 encoded. ### Sample Request @@ -1096,7 +1102,7 @@ Deletes a blacklisted role tag. ### Parameters - `role_tag` `(string: )` - Role tag to be blacklisted. The tag can be - supplied as-is. In order to avoid any encoding problems, it can be base64 + supplied as-is. In order to avoid any encoding problems, it can be base64 encoded. @@ -1120,8 +1126,8 @@ Cleans up the entries in the blacklist based on expiration time on the entry and ### Parameters -- `safety_buffer` `(string: "72h")` - The amount of extra time that must have - passed beyond the `roletag` expiration, before it is removed from the backend +- `safety_buffer` `(string: "72h")` - The amount of extra time that must have + passed beyond the `roletag` expiration, before it is removed from the backend storage. Defaults to 72h. ### Sample Request @@ -1135,7 +1141,7 @@ $ curl \ ### Read Identity Whitelist Information -Returns an entry in the whitelist. An entry will be created/updated by every +Returns an entry in the whitelist. An entry will be created/updated by every successful login. | Method | Path | Produces | @@ -1144,8 +1150,8 @@ successful login. ### Parameters -- `instance_id` `(string: )` - EC2 instance ID. A successful login - operation from an EC2 instance gets cached in this whitelist, keyed off of +- `instance_id` `(string: )` - EC2 instance ID. A successful login + operation from an EC2 instance gets cached in this whitelist, keyed off of instance ID. ### Sample Request @@ -1221,8 +1227,8 @@ Deletes a cache of the successful login from an instance. ### Parameters -- `instance_id` `(string: )` - EC2 instance ID. A successful login - operation from an EC2 instance gets cached in this whitelist, keyed off of +- `instance_id` `(string: )` - EC2 instance ID. A successful login + operation from an EC2 instance gets cached in this whitelist, keyed off of instance ID. ### Sample Request @@ -1236,7 +1242,7 @@ $ curl \ ## Tidy Identity Whitelist Entries -Cleans up the entries in the whitelist based on expiration time and +Cleans up the entries in the whitelist based on expiration time and `safety_buffer`. | Method | Path | Produces | @@ -1245,8 +1251,8 @@ Cleans up the entries in the whitelist based on expiration time and ### Parameters -- `safety_buffer` `(string: "72h")` - The amount of extra time that must have - passed beyond the `roletag` expiration, before it is removed from the backend +- `safety_buffer` `(string: "72h")` - The amount of extra time that must have + passed beyond the `roletag` expiration, before it is removed from the backend storage. Defaults to 72h. ### Sample Request @@ -1256,4 +1262,4 @@ $ curl \ --header "X-Vault-Token: ..." \ --request POST \ https://vault.rocks/v1/auth/aws/tidy/identity-whitelist -``` \ No newline at end of file +``` diff --git a/website/source/docs/auth/aws.html.md b/website/source/docs/auth/aws.html.md index b14da024a..cce67fddb 100644 --- a/website/source/docs/auth/aws.html.md +++ b/website/source/docs/auth/aws.html.md @@ -97,7 +97,7 @@ and relies upon AWS to authenticate that signature. While AWS API endpoints support both signed GET and POST requests, for simplicity, the aws backend supports only POST requests. It also does not support `presigned` requests, i.e., requests with `X-Amz-Credential`, -`X-Amz-signature`, and `X-Amz-SignedHeaders` GET query parameters containing the +`X-Amz-Signature`, and `X-Amz-SignedHeaders` GET query parameters containing the authenticating information. It's also important to note that Amazon does NOT appear to include any sort @@ -119,6 +119,17 @@ 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. +The iam authentication 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 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 +123456789012 to login to it. Similarly, if it were +`arn:aws:iam::123456789012:role/*` it would allow any IAM role in the AWS +account to login to it. If you wish to specify a wildcard, you must give Vault +`iam:GetUser` and `iam:GetRole` permissions to properly resolve the full user +path. + In general, role bindings that are specific to an EC2 instance are only checked when the ec2 auth method is used to login, while bindings specific to IAM principals are only checked when the iam auth method is used to login. However, @@ -263,6 +274,60 @@ comparison of the two authentication methods. to, or make use of inferencing. If you need to make use of role tags, then you will need to use the ec2 auth method. +## Recommended Vault IAM Policy + +This specifies the recommended IAM policy needed by the AWS auth backend. Note +that if you are using the same credentials for the AWS auth and secret backends +(e.g., if you're running Vault on an EC2 instance in an IAM instance profile), +then you will need to add additional permissions as required by the AWS secret +backend. + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:DescribeInstances", + "iam:GetInstanceProfile", + "iam:GetUser", + "iam:GetRole" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": ["sts:AssumeRole"], + "Resource": [ + "arn:aws:iam::role/" + ] + } + ] +} +``` + +Here are some of the scenarios in which Vault would need to use each of these +permissions. This isn't intended to be an exhaustive list of all the scenarios +in which Vault might make an AWS API call, but rather illustrative of why these +are needed. + +* `ec2:DescribeInstances` is necessary when you are using the `ec2` auth method + or when you are inferring an `ec2_instance` entity type to validate the EC2 + instance meets binding requirements of the role +* `iam:GetInstanceProfile` is used when you have a `bound_iam_role_arn` in the + ec2 auth method. Vault needs determine which IAM role is attached to the + instance profile. +* `iam:GetUser` and `iam:GetRole` are used when using the iam auth method and + binding to an IAM user or role principal to determine the unique AWS user ID + or when using a wildcard on the bound ARN to resolve the full ARN of the user + or role. +* The `sts:AssumeRole` stanza is necessary when you are using [Cross Account + Access](#cross-account-access). The `Resource`s specified should be a list of + all the roles for which you have configured cross-account access, and each of + those roles should have this IAM policy attached (except for the + `sts:AssumeRole` statement). + ## Client Nonce Note: this only applies to the ec2 authentication method. @@ -639,4 +704,4 @@ The response will be in JSON. For example: The AWS authentication backend has a full HTTP API. Please see the [AWS Auth API](/api/auth/aws/index.html) for more -details. \ No newline at end of file +details.