diff --git a/builtin/logical/aws/backend_test.go b/builtin/logical/aws/backend_test.go index 86acc65bd219..5eb76104d945 100644 --- a/builtin/logical/aws/backend_test.go +++ b/builtin/logical/aws/backend_test.go @@ -652,6 +652,7 @@ func testAccStepReadPolicy(t *testing.T, name string, value string) logicaltest. "policy_document": value, "credential_types": []string{iamUserCred, federationTokenCred}, "default_sts_ttl": int64(0), + "max_sts_ttl": int64(0), } if !reflect.DeepEqual(resp.Data, expected) { return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected) @@ -749,6 +750,7 @@ func TestBackend_iamUserManagedInlinePolicies(t *testing.T) { "credential_types": []string{iamUserCred}, "role_arns": []string(nil), "default_sts_ttl": int64(0), + "max_sts_ttl": int64(0), } logicaltest.Test(t, logicaltest.TestCase{ AcceptanceTest: true, @@ -828,6 +830,7 @@ func TestBackend_RoleDefaultSTSTTL(t *testing.T) { "role_arns": []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)}, "credential_type": assumedRoleCred, "default_sts_ttl": minAwsAssumeRoleDuration, + "max_sts_ttl": minAwsAssumeRoleDuration, } logicaltest.Test(t, logicaltest.TestCase{ AcceptanceTest: true, @@ -883,6 +886,7 @@ func testAccStepReadArnPolicy(t *testing.T, name string, value string) logicalte "policy_document": "", "credential_types": []string{iamUserCred}, "default_sts_ttl": int64(0), + "max_sts_ttl": int64(0), } if !reflect.DeepEqual(resp.Data, expected) { return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected) diff --git a/builtin/logical/aws/path_roles.go b/builtin/logical/aws/path_roles.go index ae225e3790e1..a3a30f5617cd 100644 --- a/builtin/logical/aws/path_roles.go +++ b/builtin/logical/aws/path_roles.go @@ -67,6 +67,11 @@ GetFederationToken API call, acting as a filter on permissions available.`, Description: fmt.Sprintf("Default TTL for %s and %s credential types when no TTL is explicitly requested with the credentials", assumedRoleCred, federationTokenCred), }, + "max_sts_ttl": &framework.FieldSchema{ + Type: framework.TypeDurationSecond, + Description: fmt.Sprintf("Max allowed TTL for %s and %s credential types", assumedRoleCred, federationTokenCred), + }, + "arn": &framework.FieldSchema{ Type: framework.TypeString, Description: `Deprecated; use role_arns or policy_arns instead. ARN Reference to a managed policy @@ -222,6 +227,23 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f roleEntry.DefaultSTSTTL = time.Duration(defaultSTSTTLRaw.(int)) * time.Second } + if maxSTSTTLRaw, ok := d.GetOk("max_sts_ttl"); ok { + if legacyRole != "" { + return logical.ErrorResponse("cannot supply deprecated role or policy parameters with max_sts_ttl"), nil + } + if !strutil.StrListContains(roleEntry.CredentialTypes, assumedRoleCred) && !strutil.StrListContains(roleEntry.CredentialTypes, federationTokenCred) { + return logical.ErrorResponse(fmt.Sprintf("max_sts_ttl parameter only valid for %s and %s credential types", assumedRoleCred, federationTokenCred)), nil + } + + roleEntry.MaxSTSTTL = time.Duration(maxSTSTTLRaw.(int)) * time.Second + } + + if roleEntry.MaxSTSTTL > 0 && + roleEntry.DefaultSTSTTL > 0 && + roleEntry.DefaultSTSTTL > roleEntry.MaxSTSTTL { + return logical.ErrorResponse(`"default_sts_ttl" value must be less than or equal to "max_sts_ttl" value`), nil + } + if legacyRole != "" { roleEntry = upgradeLegacyPolicyEntry(legacyRole) if roleEntry.InvalidData != "" { @@ -402,6 +424,7 @@ type awsRoleEntry struct { 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 DefaultSTSTTL time.Duration `json:"default_sts_ttl"` // Default TTL for STS credentials + MaxSTSTTL time.Duration `json:"max_sts_ttl"` // Max allowed TTL for STS credentials } func (r *awsRoleEntry) toResponseData() map[string]interface{} { @@ -411,6 +434,7 @@ func (r *awsRoleEntry) toResponseData() map[string]interface{} { "role_arns": r.RoleArns, "policy_document": r.PolicyDocument, "default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()), + "max_sts_ttl": int64(r.MaxSTSTTL.Seconds()), } if r.InvalidData != "" { respData["invalid_data"] = r.InvalidData diff --git a/builtin/logical/aws/path_roles_test.go b/builtin/logical/aws/path_roles_test.go index b0d4bff38d7a..3ac2473e1a34 100644 --- a/builtin/logical/aws/path_roles_test.go +++ b/builtin/logical/aws/path_roles_test.go @@ -24,6 +24,7 @@ func TestBackend_PathListRoles(t *testing.T) { "role_arns": []string{"arn:aws:iam::123456789012:role/path/RoleName"}, "credential_type": assumedRoleCred, "default_sts_ttl": 3600, + "max_sts_ttl": 3600, } roleReq := &logical.Request{ diff --git a/builtin/logical/aws/path_user.go b/builtin/logical/aws/path_user.go index 99aa8bac3984..99f09c6a392b 100644 --- a/builtin/logical/aws/path_user.go +++ b/builtin/logical/aws/path_user.go @@ -68,6 +68,18 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr default: ttl = int64(d.Get("ttl").(int)) } + + var maxTTL int64 + if role.MaxSTSTTL > 0 { + maxTTL = int64(role.MaxSTSTTL.Seconds()) + } else { + maxTTL = int64(b.System().MaxLeaseTTL().Seconds()) + } + + if ttl > maxTTL { + ttl = maxTTL + } + roleArn := d.Get("role_arn").(string) var credentialType string diff --git a/website/source/api/secret/aws/index.html.md b/website/source/api/secret/aws/index.html.md index c14aec6fcf7a..9f54f88c32c2 100644 --- a/website/source/api/secret/aws/index.html.md +++ b/website/source/api/secret/aws/index.html.md @@ -225,6 +225,10 @@ updated with the new attributes. on the role, then this default TTL will be used. Valid only when `credential_type` is one of `assumed_role` or `federation_token`. +- `max_sts_ttl` `(string)` - The max allowed TTL for STS credentials (credentials + TTL are capped to `max_sts_ttl`). Valid only when `credential_type` is one of + `assumed_role` or `federation_token`. + Legacy parameters: These parameters are supported for backwards compatibility only. They cannot be