Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow specifying role-default TTLs in AWS secret engine #5138

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions builtin/logical/aws/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,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 @@ -595,6 +612,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 @@ -691,6 +709,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 @@ -757,6 +776,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 @@ -790,6 +843,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{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of naming this just sts_ttl? A value on the role will be used unless specified otherwise. The supplied value can be lesser but not greater and this can be documented.

Copy link
Contributor

@andrejvanderzee andrejvanderzee Sep 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most plugins just call it ttl, so sts_ttl is a better fit I would say. The only exception is databases as far as I can see.

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 @@ -56,7 +56,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 @@ -179,6 +179,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 @@ -351,7 +356,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