Skip to content

Commit

Permalink
Allow specifying role-default TTLs in AWS secret engine (#5138)
Browse files Browse the repository at this point in the history
* Allow specifying role-default TTLs in AWS secret engine

* Add an acceptance test

* Add docs for AWS secret role-default TTLs

* Rename default_ttl to default_sts_ttl

* Return default_ttl as int64 instead of time.Duration

* Fix broken tests

The merge of #5383 broke the tests due to some changes in the test style
that didn't actually cause a git merge conflict. This updates the tests
to the new style.
  • Loading branch information
joelthompson authored and chrishoffman committed Oct 2, 2018
1 parent edd5db3 commit 7e610e6
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 9 deletions.
54 changes: 54 additions & 0 deletions builtin/logical/aws/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,23 @@ func testAccStepRead(t *testing.T, path, name string, credentialTests []credenti
}
}

func testAccStepReadTTL(name string, maximumTTL time.Duration) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
if resp.Secret == nil {
return fmt.Errorf("bad: nil Secret returned")
}
ttl := resp.Secret.TTL
if ttl > maximumTTL {
return fmt.Errorf("bad: ttl of %d greater than maximum of %d", ttl/time.Second, maximumTTL/time.Second)
}
return nil
},
}
}

func describeInstancesTest(accessKey, secretKey, token string) error {
creds := credentials.NewStaticCredentials(accessKey, secretKey, token)
awsConfig := &aws.Config{
Expand Down Expand Up @@ -634,6 +651,7 @@ func testAccStepReadPolicy(t *testing.T, name string, value string) logicaltest.
"role_arns": []string(nil),
"policy_document": value,
"credential_types": []string{iamUserCred, federationTokenCred},
"default_sts_ttl": int64(0),
}
if !reflect.DeepEqual(resp.Data, expected) {
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
Expand Down Expand Up @@ -730,6 +748,7 @@ func TestBackend_iamUserManagedInlinePolicies(t *testing.T) {
"policy_arns": []string{ec2PolicyArn, iamPolicyArn},
"credential_types": []string{iamUserCred},
"role_arns": []string(nil),
"default_sts_ttl": int64(0),
}
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
Expand Down Expand Up @@ -796,6 +815,40 @@ func TestBackend_AssumedRoleWithPolicyDoc(t *testing.T) {
})
}

func TestBackend_RoleDefaultSTSTTL(t *testing.T) {
t.Parallel()
roleName := generateUniqueName(t.Name())
minAwsAssumeRoleDuration := 900
awsAccountID, err := getAccountID()
if err != nil {
t.Logf("Unable to retrive user via sts:GetCallerIdentity: %#v", err)
t.Skip("Could not determine AWS account ID from sts:GetCallerIdentity for acceptance tests, skipping")
}
roleData := map[string]interface{}{
"role_arns": []string{fmt.Sprintf("arn:aws:iam::%s:role/%s", awsAccountID, roleName)},
"credential_type": assumedRoleCred,
"default_sts_ttl": minAwsAssumeRoleDuration,
}
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() {
testAccPreCheck(t)
createRole(t, roleName, awsAccountID)
log.Println("[WARN] Sleeping for 10 seconds waiting for AWS...")
time.Sleep(10 * time.Second)
},
Backend: getBackend(t),
Steps: []logicaltest.TestStep{
testAccStepConfig(t),
testAccStepWriteRole(t, "test", roleData),
testAccStepReadTTL("test", time.Duration(minAwsAssumeRoleDuration)*time.Second), // allow a little slack
},
Teardown: func() error {
return deleteTestRole(roleName)
},
})
}

func TestBackend_policyArnCrud(t *testing.T) {
t.Parallel()
logicaltest.Test(t, logicaltest.TestCase{
Expand Down Expand Up @@ -829,6 +882,7 @@ func testAccStepReadArnPolicy(t *testing.T, name string, value string) logicalte
"role_arns": []string(nil),
"policy_document": "",
"credential_types": []string{iamUserCred},
"default_sts_ttl": int64(0),
}
if !reflect.DeepEqual(resp.Data, expected) {
return fmt.Errorf("bad: got: %#v\nexpected: %#v", resp.Data, expected)
Expand Down
32 changes: 25 additions & 7 deletions builtin/logical/aws/path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws/arn"
"github.com/hashicorp/vault/helper/consts"
Expand Down Expand Up @@ -61,6 +62,11 @@ will be passed in as the Policy parameter to the AssumeRole or
GetFederationToken API call, acting as a filter on permissions available.`,
},

"default_sts_ttl": &framework.FieldSchema{
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),
},

"arn": &framework.FieldSchema{
Type: framework.TypeString,
Description: `Deprecated; use role_arns or policy_arns instead. ARN Reference to a managed policy
Expand Down Expand Up @@ -206,6 +212,16 @@ func (b *backend) pathRolesWrite(ctx context.Context, req *logical.Request, d *f
roleEntry.PolicyDocument = compacted
}

if defaultSTSTTLRaw, ok := d.GetOk("default_sts_ttl"); ok {
if legacyRole != "" {
return logical.ErrorResponse("cannot supply deprecated role or policy parameters with default_sts_ttl"), nil
}
if !strutil.StrListContains(roleEntry.CredentialTypes, assumedRoleCred) && !strutil.StrListContains(roleEntry.CredentialTypes, federationTokenCred) {
return logical.ErrorResponse(fmt.Sprintf("default_sts_ttl parameter only valid for %s and %s credential types", assumedRoleCred, federationTokenCred)), nil
}
roleEntry.DefaultSTSTTL = time.Duration(defaultSTSTTLRaw.(int)) * time.Second
}

if legacyRole != "" {
roleEntry = upgradeLegacyPolicyEntry(legacyRole)
if roleEntry.InvalidData != "" {
Expand Down Expand Up @@ -374,13 +390,14 @@ func setAwsRole(ctx context.Context, s logical.Storage, roleName string, roleEnt
}

type awsRoleEntry struct {
CredentialTypes []string `json:"credential_types"` // Entries must all be in the set of ("iam_user", "assumed_role", "federation_token")
PolicyArns []string `json:"policy_arns"` // ARNs of managed policies to attach to an IAM user
RoleArns []string `json:"role_arns"` // ARNs of roles to assume for AssumedRole credentials
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
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
CredentialTypes []string `json:"credential_types"` // Entries must all be in the set of ("iam_user", "assumed_role", "federation_token")
PolicyArns []string `json:"policy_arns"` // ARNs of managed policies to attach to an IAM user
RoleArns []string `json:"role_arns"` // ARNs of roles to assume for AssumedRole credentials
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
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
DefaultSTSTTL time.Duration `json:"default_sts_ttl"` // Default TTL for STS credentials
}

func (r *awsRoleEntry) toResponseData() map[string]interface{} {
Expand All @@ -389,6 +406,7 @@ func (r *awsRoleEntry) toResponseData() map[string]interface{} {
"policy_arns": r.PolicyArns,
"role_arns": r.RoleArns,
"policy_document": r.PolicyDocument,
"default_sts_ttl": int64(r.DefaultSTSTTL.Seconds()),
}
if r.InvalidData != "" {
respData["invalid_data"] = r.InvalidData
Expand Down
1 change: 1 addition & 0 deletions builtin/logical/aws/path_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func TestBackend_PathListRoles(t *testing.T) {
roleData := map[string]interface{}{
"role_arns": []string{"arn:aws:iam::123456789012:role/path/RoleName"},
"credential_type": assumedRoleCred,
"default_sts_ttl": 3600,
}

roleReq := &logical.Request{
Expand Down
11 changes: 10 additions & 1 deletion builtin/logical/aws/path_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,16 @@ func (b *backend) pathCredsRead(ctx context.Context, req *logical.Request, d *fr
"Role '%s' not found", roleName)), nil
}

ttl := int64(d.Get("ttl").(int))
var ttl int64
ttlRaw, ok := d.GetOk("ttl")
switch {
case ok:
ttl = int64(ttlRaw.(int))
case role.DefaultSTSTTL > 0:
ttl = int64(role.DefaultSTSTTL.Seconds())
default:
ttl = int64(d.Get("ttl").(int))
}
roleArn := d.Get("role_arn").(string)

var credentialType string
Expand Down
9 changes: 8 additions & 1 deletion website/source/api/secret/aws/index.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,11 @@ updated with the new attributes.
user has. With `assumed_role` and `federation_token`, the policy document will
act as a filter on what the credentials can do.

- `default_sts_ttl` `(string)` - The default TTL for STS credentials. When a TTL is not
specified when STS credentials are requested, and a default TTL is specified
on the role, then this default TTL will be used. 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
Expand Down Expand Up @@ -392,7 +397,9 @@ credentials retrieved through `/aws/creds` must be of the `iam_user` type.
required otherwise.
- `ttl` `(string: "3600s")` – Specifies the TTL for the use of the STS token.
This is specified as a string with a duration suffix. Valid only when
`credential_type` is `assumed_role` or `federation_token`. AWS places limits
`credential_type` is `assumed_role` or `federation_token`. When not specified,
the `default_sts_ttl` set for the role will be used. If that is also not set, then
the default value of `3600s` will be used. AWS places limits
on the maximum TTL allowed. See the AWS documentation on the `DurationSeconds`
parameter for
[AssumeRole](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html)
Expand Down

0 comments on commit 7e610e6

Please sign in to comment.