From 8ba27c93e4699c33e541e8380528a68a9d475ed1 Mon Sep 17 00:00:00 2001 From: Joel Thompson Date: Fri, 18 Aug 2017 00:29:09 -0400 Subject: [PATCH 1/3] auth/aws: Allow wildcard in bound_iam_principal_id This allows specifying a trailing wildcard in the bound_iam_principal_id in a semantically similar way to AWS. If a user specifies a bound_iam_principal_arn with a trailing wildcard, then Vault will NOT resolve the unqiue ID (analogous to the way AWS handles wildcards) and instead will just store the ARN. At login time, if a wildcard is specified in the bound ARN, Vault will first resolve the full ARN of the authenticating principal because the path component of roles is not visible from the GetCallerIdentity response. A cache has been included to speed up performance of these lookups and should be append only. To prevent unbounded growth of the cache, old entries are cleaned up if not used within a period of time. Also, as the complexity of scenarios of when Vault might make AWS API calls increases, including a recommended IAM policy to give the Vault server that can be more simply referenced in the future. Fixes #3179 --- builtin/credential/aws/backend.go | 12 +- builtin/credential/aws/backend_test.go | 75 ++++- builtin/credential/aws/client.go | 48 ++++ 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 | 65 ++++- 7 files changed, 445 insertions(+), 178 deletions(-) diff --git a/builtin/credential/aws/backend.go b/builtin/credential/aws/backend.go index 9497436b4d32..14c97fd266ec 100644 --- a/builtin/credential/aws/backend.go +++ b/builtin/credential/aws/backend.go @@ -60,6 +60,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 + iamUserIdToArn map[string]*awsUniqueIdMapEntry + // 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 @@ -77,6 +82,7 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { tidyCooldownPeriod: time.Hour, EC2ClientsMap: make(map[string]map[string]*ec2.EC2), IAMClientsMap: make(map[string]map[string]*iam.IAM), + iamUserIdToArn: make(map[string]*awsUniqueIdMapEntry), } b.resolveArnToUniqueIDFunc = b.resolveArnToRealUniqueId @@ -124,7 +130,8 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { // Currently this will be triggered once in a minute by the RollbackManager. // // The tasks being done currently by this function are to cleanup the expired -// entries of both blacklist role tags and whitelist identities. Tidying is done +// entries of both blacklist role tags and whitelist identities as well as stale +// entries in the iamUserIdToArn cache. Tidying is done // not once in a minute, but once in an hour, controlled by 'tidyCooldownPeriod'. // Tidying of blacklist and whitelist are by default enabled. This can be // changed using `config/tidy/roletags` and `config/tidy/identities` endpoints. @@ -174,6 +181,9 @@ func (b *backend) periodicFunc(req *logical.Request) error { b.tidyWhitelistIdentity(req.Storage, safety_buffer) } + // get rid of old unique ID entries + b.cleanOldCachedUniqueIdMapping() + // Update the time at which to run the tidy functions again. b.nextTidyTime = time.Now().Add(b.tidyCooldownPeriod) } diff --git a/builtin/credential/aws/backend_test.go b/builtin/credential/aws/backend_test.go index b23d0ee24de6..8c0d93320a70 100644 --- a/builtin/credential/aws/backend_test.go +++ b/builtin/credential/aws/backend_test.go @@ -1330,6 +1330,10 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { t.Fatal(err) } + // Calling this once here to ensure it won't raise any unexpected errors on an empty + // cache + b.cleanOldCachedUniqueIdMapping() + // Override the default AWS env vars (if set) with our test creds // so that the credential provider chain will pick them up // NOTE that I'm not bothing to override the shared config file location, @@ -1522,22 +1526,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 +1567,60 @@ 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") + } + // Calling this again to ensure it won't panic or raise unexpected errors when there + // are cached values + b.cleanOldCachedUniqueIdMapping() +} + +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 3dc9327a3731..51d57039773c 100644 --- a/builtin/credential/aws/client.go +++ b/builtin/credential/aws/client.go @@ -2,6 +2,7 @@ package awsauth import ( "fmt" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" @@ -141,6 +142,48 @@ func (b *backend) flushCachedIAMClients() { } } +// Deletes old cached mappings of unique IDs to ARNs +// Write locks configMutex +func (b *backend) cleanOldCachedUniqueIdMapping() { + b.configMutex.Lock() + defer b.configMutex.Unlock() + for id, mapEntry := range b.iamUserIdToArn { + // clean all entries last accessed more than MaxLeaseTTL ago + if time.Since(mapEntry.LastAccessed) > b.System().MaxLeaseTTL() { + delete(b.iamUserIdToArn, id) + } + } +} + +// Gets an entry out of the user ID cache +// Write locks configMutex +func (b *backend) getCachedUserId(userId string) string { + if userId == "" { + return "" + } + b.configMutex.Lock() + defer b.configMutex.Unlock() + if entry, ok := b.iamUserIdToArn[userId]; ok { + entry.LastAccessed = time.Now() + return entry.Arn + } + return "" +} + +// Sets an entry in the user ID cache +// Write locks configMutex +func (b *backend) setCachedUserId(userId, arn string) { + if userId != "" { + b.configMutex.Lock() + defer b.configMutex.Unlock() + entry := awsUniqueIdMapEntry{ + LastAccessed: time.Now(), + Arn: arn, + } + b.iamUserIdToArn[userId] = &entry + } +} + 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) @@ -250,3 +293,8 @@ func (b *backend) clientIAM(s logical.Storage, region, accountID string) (*iam.I } return b.IAMClientsMap[region][stsRole], nil } + +type awsUniqueIdMapEntry struct { + LastAccessed time.Time // This is primarily so we can clean up the cache so it doesn't monotonically grow + Arn string +} diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index 66effda56773..a20fc2072816 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 konw 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) } } @@ -1123,7 +1150,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) @@ -1152,8 +1179,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 @@ -1502,6 +1548,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" @@ -1516,6 +1563,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 79f7a518e931..78ff0dad1ab7 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 f998d21f0740..99b913d2989d 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 @@ -258,11 +258,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 @@ -293,8 +293,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 @@ -382,10 +382,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 @@ -465,10 +465,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 @@ -557,115 +557,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 @@ -819,20 +825,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 @@ -885,54 +891,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. @@ -996,7 +1002,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 @@ -1019,7 +1025,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 @@ -1092,7 +1098,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. @@ -1116,8 +1122,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 @@ -1131,7 +1137,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 | @@ -1140,8 +1146,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 @@ -1216,8 +1222,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 @@ -1231,7 +1237,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 | @@ -1240,8 +1246,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 @@ -1251,4 +1257,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 b14da024a10c..c305518e5abb 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 was +`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 resole 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,56 @@ 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 to run Vault with. + +```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 +700,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. From 41b9365a34dd9585eae217d784ffc05bed1c5fac Mon Sep 17 00:00:00 2001 From: Joel Thompson Date: Tue, 29 Aug 2017 23:04:49 -0400 Subject: [PATCH 2/3] Switch to go-cache for userID mappings Responding to PR feedback --- builtin/credential/aws/backend.go | 17 +++++------- builtin/credential/aws/backend_test.go | 7 ----- builtin/credential/aws/client.go | 37 +++----------------------- website/source/docs/auth/aws.html.md | 10 ++++--- 4 files changed, 18 insertions(+), 53 deletions(-) diff --git a/builtin/credential/aws/backend.go b/builtin/credential/aws/backend.go index 14c97fd266ec..30feba9ca0a4 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) { @@ -63,7 +64,7 @@ type backend struct { // 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 - iamUserIdToArn map[string]*awsUniqueIdMapEntry + iamUserIdToArnCache *cache.Cache // AWS Account ID of the "default" AWS credentials // This cache avoids the need to call GetCallerIdentity repeatedly to learn it @@ -79,10 +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), - iamUserIdToArn: make(map[string]*awsUniqueIdMapEntry), + 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 @@ -130,8 +131,7 @@ func Backend(conf *logical.BackendConfig) (*backend, error) { // Currently this will be triggered once in a minute by the RollbackManager. // // The tasks being done currently by this function are to cleanup the expired -// entries of both blacklist role tags and whitelist identities as well as stale -// entries in the iamUserIdToArn cache. Tidying is done +// entries of both blacklist role tags and whitelist identities. Tidying is done // not once in a minute, but once in an hour, controlled by 'tidyCooldownPeriod'. // Tidying of blacklist and whitelist are by default enabled. This can be // changed using `config/tidy/roletags` and `config/tidy/identities` endpoints. @@ -181,9 +181,6 @@ func (b *backend) periodicFunc(req *logical.Request) error { b.tidyWhitelistIdentity(req.Storage, safety_buffer) } - // get rid of old unique ID entries - b.cleanOldCachedUniqueIdMapping() - // Update the time at which to run the tidy functions again. b.nextTidyTime = time.Now().Add(b.tidyCooldownPeriod) } diff --git a/builtin/credential/aws/backend_test.go b/builtin/credential/aws/backend_test.go index 8c0d93320a70..881ca85dc957 100644 --- a/builtin/credential/aws/backend_test.go +++ b/builtin/credential/aws/backend_test.go @@ -1330,10 +1330,6 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { t.Fatal(err) } - // Calling this once here to ensure it won't raise any unexpected errors on an empty - // cache - b.cleanOldCachedUniqueIdMapping() - // Override the default AWS env vars (if set) with our test creds // so that the credential provider chain will pick them up // NOTE that I'm not bothing to override the shared config file location, @@ -1606,9 +1602,6 @@ func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) { if cachedArn == "" { t.Errorf("got empty ARN back from user ID cache; expected full arn") } - // Calling this again to ensure it won't panic or raise unexpected errors when there - // are cached values - b.cleanOldCachedUniqueIdMapping() } func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Request { diff --git a/builtin/credential/aws/client.go b/builtin/credential/aws/client.go index 51d57039773c..aa3da0d12f2c 100644 --- a/builtin/credential/aws/client.go +++ b/builtin/credential/aws/client.go @@ -2,7 +2,6 @@ package awsauth import ( "fmt" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials/stscreds" @@ -142,45 +141,22 @@ func (b *backend) flushCachedIAMClients() { } } -// Deletes old cached mappings of unique IDs to ARNs -// Write locks configMutex -func (b *backend) cleanOldCachedUniqueIdMapping() { - b.configMutex.Lock() - defer b.configMutex.Unlock() - for id, mapEntry := range b.iamUserIdToArn { - // clean all entries last accessed more than MaxLeaseTTL ago - if time.Since(mapEntry.LastAccessed) > b.System().MaxLeaseTTL() { - delete(b.iamUserIdToArn, id) - } - } -} - // Gets an entry out of the user ID cache -// Write locks configMutex func (b *backend) getCachedUserId(userId string) string { if userId == "" { return "" } - b.configMutex.Lock() - defer b.configMutex.Unlock() - if entry, ok := b.iamUserIdToArn[userId]; ok { - entry.LastAccessed = time.Now() - return entry.Arn + 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 -// Write locks configMutex func (b *backend) setCachedUserId(userId, arn string) { if userId != "" { - b.configMutex.Lock() - defer b.configMutex.Unlock() - entry := awsUniqueIdMapEntry{ - LastAccessed: time.Now(), - Arn: arn, - } - b.iamUserIdToArn[userId] = &entry + b.iamUserIdToArnCache.SetDefault(userId, arn) } } @@ -293,8 +269,3 @@ func (b *backend) clientIAM(s logical.Storage, region, accountID string) (*iam.I } return b.IAMClientsMap[region][stsRole], nil } - -type awsUniqueIdMapEntry struct { - LastAccessed time.Time // This is primarily so we can clean up the cache so it doesn't monotonically grow - Arn string -} diff --git a/website/source/docs/auth/aws.html.md b/website/source/docs/auth/aws.html.md index c305518e5abb..cce67fddb627 100644 --- a/website/source/docs/auth/aws.html.md +++ b/website/source/docs/auth/aws.html.md @@ -122,12 +122,12 @@ 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 was +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 resole the full user +`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 @@ -276,7 +276,11 @@ comparison of the two authentication methods. ## Recommended Vault IAM Policy -This specifies the recommended IAM policy to run Vault with. +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 { From 09972b16cfb6092251aa050fc41ee21b9d039525 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Wed, 30 Aug 2017 17:51:17 -0400 Subject: [PATCH 3/3] Fix typo --- builtin/credential/aws/path_login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/credential/aws/path_login.go b/builtin/credential/aws/path_login.go index 03af9b2ffc5b..2f22c4d17d7e 100644 --- a/builtin/credential/aws/path_login.go +++ b/builtin/credential/aws/path_login.go @@ -930,7 +930,7 @@ 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 konw that it's a wildcard bind (but not the actual bind), even if the client can't actually + // 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.