From 8470de9bcddd050b78715d7de91e98cb7e696473 Mon Sep 17 00:00:00 2001 From: Harsimran Singh Maan Date: Mon, 23 Jan 2023 17:04:05 -0800 Subject: [PATCH 1/6] Add Session tags and external id support for AWS Secrets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is now possible to set the AWS session tags and external id when assuming an IAM role via STS AssumeRole. Here's an example setup AWS Role - Trust relationship needs the `sts:TagSession` permission set as described in https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required ```bash vault secrets enable aws vault write aws/config/root access_key= secret_key= region=us-west-2 vault write aws/roles/my-role credential_type=assumed_role max_sts_ttl=1h role_arns="arn:aws:iam::000000000000:role/test" session_tags="department=eng" session_tags="project=p1" external_id=123 ``` Read the role definition ``` vault read aws/roles/my-role Key Value --- ----- credential_type assumed_role default_sts_ttl 0s external_id 123 iam_groups iam_tags max_sts_ttl 1h permissions_boundary_arn n/a policy_arns policy_document n/a role_arns [arn:aws:iam::000000000000:role/test] session_tags map[department:eng project:p1] user_path n/a ``` ```bash vault read aws/sts/my-role Key Value --- ----- access_key XXXX arn arn:aws:sts::000000000000:assumed-role/test/vault-token-my-role-1674516375-mBKZQssrwtP6gl3kbmWq secret_key XXX security_token XXX ttl 59m59s ``` The cloudtrail for AssumeRole looks like ``` "userIdentity": { "type": "IAMUser", "principalId": "XXX", "arn": "arn:aws:iam::000000000000:user/vault-user", "accountId": "000000000000", "accessKeyId": "XXX", "userName": "vault-user" }, "eventTime": "2023-01-23T23:31:12Z", "eventSource": "sts.amazonaws.com", "eventName": "AssumeRole", "awsRegion": "us-east-1", "sourceIPAddress": "XXX", "userAgent": "aws-sdk-go/1.44.128 (go1.19.5; darwin; arm64)", "requestParameters": { "roleArn": "arn:aws:iam::000000000000:role/test", "roleSessionName": "vault-token-my-role-1674516375", "durationSeconds": 3600, "tags": [ { "key": "department", "value": "eng" }, { "key": "project", "value": "p1" } ], "externalId": "123" }, ... ``` When using the creds are used for accessing an S3 resource with Attribute-based Access Control (ABAC), you can now see that only requests with principalTag project=p1 are allowed while requests to path p2 are denied. ``` aws s3 cp a.txt s3://session-tags-test/p1/a.txt upload: ./a.txt to s3://session-tags-test/p1/a.txt ➜ ~ aws s3 cp a.txt s3://session-tags-test/p2/a.txt upload failed: ./a.txt to s3://session-tags-test/p2/a.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Denied ``` The IAM policy had the following block in the above case ``` { "Action": [ "s3:Describe*", "s3:Get*", "s3:List*", "s3:PutObject*" ], "Effect": "Allow", "Resource": [ "arn:aws:s3:::session-tags-test/${aws:PrincipalTag/project}/*", "arn:aws:s3:::session-tags-test/${aws:PrincipalTag/project}" ] } ``` Troubleshooting: Error: ``` Error assuming role: AccessDenied: User: arn:aws:iam::000000000000:user/vault-user is not authorized to perform: sts:TagSession on resource: arn:aws:iam::000000000000:role/test status code: 403, request id: ba8ab60e-2fdf-4668-81ad-5fe83e9b898e ``` Remedy: Assign the `sts:TagSession` permission to the `arn:aws:iam::000000000000:role/test`. See https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_permissions-required Error: ``` * Error assuming role: AccessDenied: User: arn:aws:iam::000000000000:user/vault-user is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::000000000000:role/test status code: 403, request id: c0117588-9c84-490d-9b36-91135545dec1 ``` Remedy: If the external ID is set, follow https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user_externalid.html to ensure that external ID matches the ID set on the role. If you have not added the externalID condition on the role, it would not affect the assume role operation when an external ID is set only in Vault. This change does not add support for transitive keys but it should be simple to add it in the future. Closes #3790 #7960 --- builtin/logical/aws/path_roles.go | 44 +++++++++++++++++++++-- builtin/logical/aws/path_roles_test.go | 9 +++-- builtin/logical/aws/path_user.go | 2 +- builtin/logical/aws/secret_access_keys.go | 12 ++++++- 4 files changed, 60 insertions(+), 7 deletions(-) diff --git a/builtin/logical/aws/path_roles.go b/builtin/logical/aws/path_roles.go index 67545e641434..b8902e8fffd5 100644 --- a/builtin/logical/aws/path_roles.go +++ b/builtin/logical/aws/path_roles.go @@ -91,7 +91,6 @@ user generated. When credential_type is assumed_role or federation_token, this will be passed in as the Policy parameter to the AssumeRole or GetFederationToken API call, acting as a filter on permissions available.`, }, - "iam_groups": { Type: framework.TypeCommaStringSlice, Description: `Names of IAM groups that generated IAM users will be added to. For a credential @@ -115,7 +114,23 @@ delimited key pairs.`, Value: "[key1=value1, key2=value2]", }, }, - + "session_tags": { + Type: framework.TypeKVPairs, + Description: fmt.Sprintf(`Session tags to be set for %q creds created by this role. These must be presented +as Key-Value pairs. This can be represented as a map or a list of equal sign +delimited key pairs.`, assumedRoleCred), + DisplayAttrs: &framework.DisplayAttributes{ + Name: "Session Tags", + Value: "[key1=value1, key2=value2]", + }, + }, + "external_id": { + Type: framework.TypeString, + Description: "External ID to set when assuming the role; only valid when credential_type is" + assumedRoleCred, + DisplayAttrs: &framework.DisplayAttributes{ + Name: "External ID", + }, + }, "default_sts_ttl": { Type: framework.TypeDurationSecond, Description: fmt.Sprintf("Default TTL for %s and %s credential types when no TTL is explicitly requested with the credentials", assumedRoleCred, federationTokenCred), @@ -328,6 +343,14 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f roleEntry.IAMTags = iamTags.(map[string]string) } + if sessionTags, ok := d.GetOk("session_tags"); ok { + roleEntry.SessionTags = sessionTags.(map[string]string) + } + + if externalID, ok := d.GetOk("external_id"); ok { + roleEntry.ExternalID = externalID.(string) + } + if legacyRole != "" { roleEntry = upgradeLegacyPolicyEntry(legacyRole) if roleEntry.InvalidData != "" { @@ -514,6 +537,8 @@ type awsRoleEntry struct { PolicyDocument string `json:"policy_document"` // JSON-serialized inline policy to attach to IAM users and/or to specify as the Policy parameter in AssumeRole calls IAMGroups []string `json:"iam_groups"` // Names of IAM groups that generated IAM users will be added to IAMTags map[string]string `json:"iam_tags"` // IAM tags that will be added to the generated IAM users + SessionTags map[string]string `json:"session_tags"` // Session tags that will be added as Tags parameter in AssumedRole calls + ExternalID string `json:"external_id"` // External ID to added as ExternalID in AssumeRole calls InvalidData string `json:"invalid_data,omitempty"` // Invalid role data. Exists to support converting the legacy role data into the new format ProhibitFlexibleCredPath bool `json:"prohibit_flexible_cred_path,omitempty"` // Disallow accessing STS credentials via the creds path and vice verse Version int `json:"version"` // Version number of the role format @@ -531,6 +556,8 @@ func (r *awsRoleEntry) toResponseData() map[string]interface{} { "policy_document": r.PolicyDocument, "iam_groups": r.IAMGroups, "iam_tags": r.IAMTags, + "session_tags": r.SessionTags, + "external_id": r.ExternalID, "default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()), "max_sts_ttl": int64(r.MaxSTSTTL.Seconds()), "user_path": r.UserPath, @@ -576,7 +603,7 @@ func (r *awsRoleEntry) validate() error { errors = multierror.Append(errors, fmt.Errorf("user_path parameter only valid for %s credential type", iamUserCred)) } if !userPathRegex.MatchString(r.UserPath) { - errors = multierror.Append(errors, fmt.Errorf("The specified value for user_path is invalid. It must match %q regexp", userPathRegex.String())) + errors = multierror.Append(errors, fmt.Errorf("invalid user_path value. It must match %q regexp", userPathRegex.String())) } } @@ -592,6 +619,17 @@ func (r *awsRoleEntry) validate() error { if len(r.RoleArns) > 0 && !strutil.StrListContains(r.CredentialTypes, assumedRoleCred) { errors = multierror.Append(errors, fmt.Errorf("cannot supply role_arns when credential_type isn't %s", assumedRoleCred)) } + if len(r.SessionTags) > 0 && !strutil.StrListContains(r.CredentialTypes, assumedRoleCred) { + errors = multierror.Append(errors, fmt.Errorf("cannot supply session_tags when credential_type isn't %s", assumedRoleCred)) + // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_know + if len(r.SessionTags) > 50 { + errors = multierror.Append(errors, fmt.Errorf("cannot supply more than %d session_tags", 50)) + } + } + + if r.ExternalID != "" && !strutil.StrListContains(r.CredentialTypes, assumedRoleCred) { + errors = multierror.Append(errors, fmt.Errorf("cannot supply external_id when credential_type isn't %s", assumedRoleCred)) + } return errors.ErrorOrNil() } diff --git a/builtin/logical/aws/path_roles_test.go b/builtin/logical/aws/path_roles_test.go index c5bf167866cc..e1da0c79b210 100644 --- a/builtin/logical/aws/path_roles_test.go +++ b/builtin/logical/aws/path_roles_test.go @@ -392,8 +392,13 @@ func TestRoleEntryValidationAssumedRoleCred(t *testing.T) { RoleArns: []string{"arn:aws:iam::123456789012:role/SomeRole"}, PolicyArns: []string{adminAccessPolicyARN}, PolicyDocument: allowAllPolicyDocument, - DefaultSTSTTL: 2, - MaxSTSTTL: 3, + ExternalID: "my-ext-id", + SessionTags: map[string]string{ + "Key1": "Value1", + "Key2": "Value2", + }, + DefaultSTSTTL: 2, + MaxSTSTTL: 3, } if err := roleEntry.validate(); err != nil { t.Errorf("bad: valid roleEntry %#v failed validation: %v", roleEntry, err) diff --git a/builtin/logical/aws/path_user.go b/builtin/logical/aws/path_user.go index 1b6a5cd3b0e3..3dbefe1cd7db 100644 --- a/builtin/logical/aws/path_user.go +++ b/builtin/logical/aws/path_user.go @@ -152,7 +152,7 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr case !strutil.StrListContains(role.RoleArns, roleArn): return logical.ErrorResponse(fmt.Sprintf("role_arn %q not in allowed role arns for Vault role %q", roleArn, roleName)), nil } - return b.assumeRole(ctx, req.Storage, req.DisplayName, roleName, roleArn, role.PolicyDocument, role.PolicyArns, role.IAMGroups, ttl, roleSessionName) + return b.assumeRole(ctx, req.Storage, req.DisplayName, roleName, roleArn, role.PolicyDocument, role.PolicyArns, role.IAMGroups, ttl, roleSessionName, role.SessionTags, role.ExternalID) case federationTokenCred: return b.getFederationToken(ctx, req.Storage, req.DisplayName, roleName, role.PolicyDocument, role.PolicyArns, role.IAMGroups, ttl) default: diff --git a/builtin/logical/aws/secret_access_keys.go b/builtin/logical/aws/secret_access_keys.go index 2f1ac442bcbf..866d82b4e6c3 100644 --- a/builtin/logical/aws/secret_access_keys.go +++ b/builtin/logical/aws/secret_access_keys.go @@ -184,7 +184,7 @@ func (b *backend) getFederationToken(ctx context.Context, s logical.Storage, func (b *backend) assumeRole(ctx context.Context, s logical.Storage, displayName, roleName, roleArn, policy string, policyARNs []string, - iamGroups []string, lifeTimeInSeconds int64, roleSessionName string) (*logical.Response, error, + iamGroups []string, lifeTimeInSeconds int64, roleSessionName string, sessionTags map[string]string, externalID string) (*logical.Response, error, ) { // grab any IAM group policies associated with the vault role, both inline // and managed @@ -241,6 +241,16 @@ func (b *backend) assumeRole(ctx context.Context, s logical.Storage, if len(policyARNs) > 0 { assumeRoleInput.SetPolicyArns(convertPolicyARNs(policyARNs)) } + if externalID != "" { + assumeRoleInput.SetExternalId(externalID) + } + if len(sessionTags) > 0 { + var tags []*sts.Tag + for k, v := range sessionTags { + tags = append(tags, &sts.Tag{Key: aws.String(k), Value: aws.String(v)}) + } + assumeRoleInput.SetTags(tags) + } tokenResp, err := stsClient.AssumeRoleWithContext(ctx, assumeRoleInput) if err != nil { return logical.ErrorResponse("Error assuming role: %s", err), awsutil.CheckAWSError(err) From 90769d2d019dd453466a42b519c660554332e230 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 26 Jun 2024 09:52:38 -0400 Subject: [PATCH 2/6] Remove max tags check Prefer to let the AWS API enforce its own constraints. If the max number of tags is exceeded AWS will return an error. In addition, the check was never being enforced since it as conditional on an invalid config error. --- builtin/logical/aws/path_roles.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/builtin/logical/aws/path_roles.go b/builtin/logical/aws/path_roles.go index 92005c33cf40..b08548b33c14 100644 --- a/builtin/logical/aws/path_roles.go +++ b/builtin/logical/aws/path_roles.go @@ -16,6 +16,7 @@ import ( "github.com/aws/aws-sdk-go/aws/arn" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" @@ -642,10 +643,6 @@ func (r *awsRoleEntry) validate() error { if len(r.SessionTags) > 0 && !strutil.StrListContains(r.CredentialTypes, assumedRoleCred) { errors = multierror.Append(errors, fmt.Errorf("cannot supply session_tags when credential_type isn't %s", assumedRoleCred)) - // https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_know - if len(r.SessionTags) > 50 { - errors = multierror.Append(errors, fmt.Errorf("cannot supply more than %d session_tags", 50)) - } } if r.ExternalID != "" && !strutil.StrListContains(r.CredentialTypes, assumedRoleCred) { From a39aa8a70a17345be632f29613a6a0216b63b469 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 26 Jun 2024 10:01:26 -0400 Subject: [PATCH 3/6] Fix external_id description --- builtin/logical/aws/path_roles.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builtin/logical/aws/path_roles.go b/builtin/logical/aws/path_roles.go index b08548b33c14..1739032b0cea 100644 --- a/builtin/logical/aws/path_roles.go +++ b/builtin/logical/aws/path_roles.go @@ -128,7 +128,7 @@ delimited key pairs.`, assumedRoleCred), }, "external_id": { Type: framework.TypeString, - Description: "External ID to set when assuming the role; only valid when credential_type is" + assumedRoleCred, + Description: "External ID to set when assuming the role; only valid when credential_type is " + assumedRoleCred, DisplayAttrs: &framework.DisplayAttributes{ Name: "External ID", }, From 6dfb22abd54fe00ff04af50b541da30727904f42 Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 26 Jun 2024 10:53:23 -0400 Subject: [PATCH 4/6] Add validation tests --- builtin/logical/aws/path_roles_test.go | 71 ++++++++++++++++++++--- builtin/logical/aws/secret_access_keys.go | 16 +++-- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/builtin/logical/aws/path_roles_test.go b/builtin/logical/aws/path_roles_test.go index 09da6ee73b36..846b60d0ca92 100644 --- a/builtin/logical/aws/path_roles_test.go +++ b/builtin/logical/aws/path_roles_test.go @@ -5,11 +5,14 @@ package aws import ( "context" + "errors" "reflect" "strconv" "strings" "testing" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/vault/sdk/logical" ) @@ -366,22 +369,74 @@ func TestRoleEntryValidationIamUserCred(t *testing.T) { CredentialTypes: []string{iamUserCred}, RoleArns: []string{"arn:aws:iam::123456789012:role/SomeRole"}, } - if roleEntry.validate() == nil { - t.Errorf("bad: invalid roleEntry with invalid RoleArns parameter %#v passed validation", roleEntry) - } + assertMultiError(t, roleEntry.validate(), + []error{ + errors.New( + "cannot supply role_arns when credential_type isn't assumed_role", + ), + }) roleEntry = awsRoleEntry{ CredentialTypes: []string{iamUserCred}, PolicyArns: []string{adminAccessPolicyARN}, DefaultSTSTTL: 1, } - if roleEntry.validate() == nil { - t.Errorf("bad: invalid roleEntry with unrecognized DefaultSTSTTL %#v passed validation", roleEntry) - } + assertMultiError(t, roleEntry.validate(), + []error{ + errors.New( + "default_sts_ttl parameter only valid for assumed_role, federation_token, and session_token credential types", + ), + }) roleEntry.DefaultSTSTTL = 0 + roleEntry.MaxSTSTTL = 1 - if roleEntry.validate() == nil { - t.Errorf("bad: invalid roleEntry with unrecognized MaxSTSTTL %#v passed validation", roleEntry) + assertMultiError(t, roleEntry.validate(), + []error{ + errors.New( + "max_sts_ttl parameter only valid for assumed_role, federation_token, and session_token credential types", + ), + }) + roleEntry.MaxSTSTTL = 0 + + roleEntry.SessionTags = map[string]string{ + "Key1": "Value1", + "Key2": "Value2", + } + assertMultiError(t, roleEntry.validate(), + []error{ + errors.New( + "cannot supply session_tags when credential_type isn't assumed_role", + ), + }) + roleEntry.SessionTags = nil + + roleEntry.ExternalID = "my-ext-id" + assertMultiError(t, roleEntry.validate(), + []error{ + errors.New( + "cannot supply external_id when credential_type isn't assumed_role"), + }) +} + +func assertMultiError(t *testing.T, err error, expected []error) { + t.Helper() + + if err == nil { + t.Errorf("expected error, got nil") + return + } + + var multiErr *multierror.Error + if errors.As(err, &multiErr) { + if multiErr.Len() != len(expected) { + t.Errorf("expected %d error, got %d", len(expected), multiErr.Len()) + } else { + if !reflect.DeepEqual(expected, multiErr.Errors) { + t.Errorf("expected error %q, actual %q", expected, multiErr.Errors) + } + } + } else { + t.Errorf("expected multierror, got %T", err) } } diff --git a/builtin/logical/aws/secret_access_keys.go b/builtin/logical/aws/secret_access_keys.go index 0a126a6ed6af..5120dcb48a2d 100644 --- a/builtin/logical/aws/secret_access_keys.go +++ b/builtin/logical/aws/secret_access_keys.go @@ -14,6 +14,7 @@ import ( "github.com/aws/aws-sdk-go/service/sts" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-secure-stdlib/awsutil" + "github.com/hashicorp/vault/sdk/framework" "github.com/hashicorp/vault/sdk/helper/template" "github.com/hashicorp/vault/sdk/logical" @@ -298,13 +299,16 @@ func (b *backend) assumeRole(ctx context.Context, s logical.Storage, if externalID != "" { assumeRoleInput.SetExternalId(externalID) } - if len(sessionTags) > 0 { - var tags []*sts.Tag - for k, v := range sessionTags { - tags = append(tags, &sts.Tag{Key: aws.String(k), Value: aws.String(v)}) - } - assumeRoleInput.SetTags(tags) + var tags []*sts.Tag + for k, v := range sessionTags { + tags = append(tags, + &sts.Tag{ + Key: aws.String(k), + Value: aws.String(v), + }, + ) } + assumeRoleInput.SetTags(tags) tokenResp, err := stsClient.AssumeRoleWithContext(ctx, assumeRoleInput) if err != nil { return logical.ErrorResponse("Error assuming role: %s", err), awsutil.CheckAWSError(err) From 017d562c1371b2a5cd27586ebfb0dd10cb11c51c Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 26 Jun 2024 16:45:49 +0000 Subject: [PATCH 5/6] Add changelog --- changelog/18813.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/18813.txt diff --git a/changelog/18813.txt b/changelog/18813.txt new file mode 100644 index 000000000000..e808a0b4e0e7 --- /dev/null +++ b/changelog/18813.txt @@ -0,0 +1,5 @@ +```release-note:feature +**AWS secrets engine STS session tags support**: Adds support for setting STS +session tags when generating temporary credentials using the AWS secrets +engine. +``` From 1bc35eef073a98fa632659c46b5927a2cb4dde3a Mon Sep 17 00:00:00 2001 From: Ben Ash Date: Wed, 26 Jun 2024 13:18:41 -0400 Subject: [PATCH 6/6] Update API docs --- website/content/api-docs/secret/aws.mdx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/website/content/api-docs/secret/aws.mdx b/website/content/api-docs/secret/aws.mdx index f93589e5396d..768f72c06e5d 100644 --- a/website/content/api-docs/secret/aws.mdx +++ b/website/content/api-docs/secret/aws.mdx @@ -31,7 +31,7 @@ files, or IAM/ECS instances. - Static credentials provided to the API as a payload -- [Plugin workload identity federation](/vault/docs/secrets/aws#plugin-workload-identity-federation-wif) +- [Plugin workload identity federation](/vault/docs/secrets/aws#plugin-workload-identity-federation-wif) credentials - Credentials in the `AWS_ACCESS_KEY`, `AWS_SECRET_KEY`, and `AWS_REGION` @@ -60,15 +60,15 @@ valid AWS credentials with proper permissions. - `secret_key` `(string: "")` – Specifies the AWS secret access key. Must be provided with `access_key`. -- `role_arn` `(string: "")` – Role ARN to assume +- `role_arn` `(string: "")` – Role ARN to assume for plugin workload identity federation. Required with `identity_token_audience`. -- `identity_token_audience` `(string: "")` - The - audience claim value for plugin identity tokens. Must match an allowed audience configured +- `identity_token_audience` `(string: "")` - The + audience claim value for plugin identity tokens. Must match an allowed audience configured for the target [IAM OIDC identity provider](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html#manage-oidc-provider-console). Mutually exclusive with `access_key`. -- `identity_token_ttl` `(string/int: 3600)` - The +- `identity_token_ttl` `(string/int: 3600)` - The TTL of generated tokens. Defaults to 1 hour. Uses [duration format strings](/vault/docs/concepts/duration-format). - `region` `(string: )` – Specifies the AWS region. If not set it @@ -316,6 +316,13 @@ updated with the new attributes. TTL are capped to `max_sts_ttl`). Valid only when `credential_type` is one of `assumed_role` or `federation_token`. +- `session_tags` `(list: [])` - The set of key-value pairs to be included as tags for the STS session. + Allowed formats are a map of strings or a list of strings in the format `key=value`. + Valid only when `credential_type` is set to `assumed_role`. + +- `external_id` `(string)` - The external ID to use when assuming the role. + Valid only when `credential_type` is set to `assumed_role`. + - `user_path` `(string)` - The path for the user name. Valid only when `credential_type` is `iam_user`. Default is `/` @@ -645,7 +652,7 @@ $ curl \ "data": { "access_key": "AKIA...", "secret_key": "xlCs...", - "session_token": "FwoG...", + "session_token": "FwoG..." } } ```