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

[VAULT-1986] Cap AWS Token TTL based on Default Lease TTL #12026

Merged
merged 6 commits into from Jul 15, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions builtin/credential/aws/path_role.go
Expand Up @@ -892,9 +892,11 @@ func (b *backend) pathRoleCreateUpdate(ctx context.Context, req *logical.Request
defaultLeaseTTL := b.System().DefaultLeaseTTL()
systemMaxTTL := b.System().MaxLeaseTTL()
if roleEntry.TokenTTL > defaultLeaseTTL {
roleEntry.TokenTTL = defaultLeaseTTL
Copy link
Collaborator

Choose a reason for hiding this comment

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

How come we clamp TTL to at most the default? I would only expect clamping to be needed for max. Is .DefaultLeaseTTL() just a poorly named function?

Copy link
Contributor

Choose a reason for hiding this comment

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

The function is correctly named, but this is existing behavior within the plugin that was never actually implemented. We can either remove it and only cap on max, or implement the original intent. Thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In that case, personally I would err towards removing the warning rather than adding capping - it's the more correct and safer change.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with Tom's recommendation

Copy link
Member

Choose a reason for hiding this comment

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

@jasonodonnell what do you mean by never implemented? The value from b.System().DefaultLeaseTTL() is determined by this method:

func (d dynamicSystemView) DefaultLeaseTTL() time.Duration {
def, _ := d.fetchTTLs()
return def
}

Copy link
Member

Choose a reason for hiding this comment

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

Sync'ed up with Vinay on this and it turns out that TTL calculation caps the max TTL but doesn't care about the ttl (or token_ttl) value as long as that's under the max. I'm fine with removing this. At some point we were capping the default but then removed it when we changed things to use tokenutil.

resp.AddWarning(fmt.Sprintf("Given ttl of %d seconds greater than current mount/system default of %d seconds; ttl will be capped at login time", roleEntry.TokenTTL/time.Second, defaultLeaseTTL/time.Second))
}
if roleEntry.TokenMaxTTL > systemMaxTTL {
roleEntry.TokenMaxTTL = systemMaxTTL
resp.AddWarning(fmt.Sprintf("Given max ttl of %d seconds greater than current mount/system default of %d seconds; max ttl will be capped at login time", roleEntry.TokenMaxTTL/time.Second, systemMaxTTL/time.Second))
Copy link
Collaborator

Choose a reason for hiding this comment

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

max ttl will be capped at login time

Is this now slightly inaccurate? I believe if you read the config back for the role, it will probably have stored the system max TTL instead of the specified max TTL right? So it's actually already been capped.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, we should remove the about "at login". We're now setting this on the role.

Copy link
Member

Choose a reason for hiding this comment

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

I believe if you read the config back for the role, it will probably have stored the system max TTL instead of the specified max TTL right?

I don't think that this is the case because we're not actually modifying/overriding roleEntry.TokenMaxTTL so the cap is actually determined at login time. The outdated diff looks like we were at one point but then we reverted back (which I think is the right call).

}
if roleEntry.TokenMaxTTL != 0 && roleEntry.TokenMaxTTL < roleEntry.TokenTTL {
Expand Down
101 changes: 99 additions & 2 deletions builtin/credential/aws/path_role_test.go
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strings"
"testing"
"time"

"github.com/go-test/deep"
"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -762,16 +763,112 @@ func TestAwsEc2_RoleDurationSeconds(t *testing.T) {
}

if resp.Data["ttl"].(int64) != 10 {
t.Fatalf("bad: period; expected: 10, actual: %d", resp.Data["ttl"])
t.Fatalf("bad: ttl; expected: 10, actual: %d", resp.Data["ttl"])
}
if resp.Data["max_ttl"].(int64) != 20 {
t.Fatalf("bad: period; expected: 20, actual: %d", resp.Data["max_ttl"])
t.Fatalf("bad: max_ttl; expected: 20, actual: %d", resp.Data["max_ttl"])
}
if resp.Data["period"].(int64) != 30 {
t.Fatalf("bad: period; expected: 30, actual: %d", resp.Data["period"])
}
}

func TestAwsIam_RoleDurationSeconds(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage

b, err := Backend(config)
if err != nil {
t.Fatal(err)
}

err = b.Setup(context.Background(), config)
if err != nil {
t.Fatal(err)
}

roleData := map[string]interface{}{
"auth_type": "iam",
"bound_iam_principal_arn": "arn:aws:iam::123456789012:role/testrole",
"resolve_aws_unique_ids": false,
"ttl": "20m",
"max_ttl": "30m",
}

roleReq := &logical.Request{
Operation: logical.CreateOperation,
Storage: storage,
Path: "role/testrole",
Data: roleData,
}

resp, err := b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v, err: %v", resp, err)
}

roleReq.Operation = logical.ReadOperation
Copy link
Collaborator

@tomhjp tomhjp Jul 9, 2021

Choose a reason for hiding this comment

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

nit: It does make the tests more verbose, but I think as a general rule it's nice to treat structs as immutable in tests so that the definition of your requests is always fully defined near the call site, and you reduce the risk of surprising bugs from dependencies between parts of the test.


resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v, err: %v", resp, err)
}

// since default lease TTL for system is 24hr, Token TTL should not be capped
// since max lease TTL for system is 48hr, Token Max TTL should not be capped
if resp.Data["token_ttl"].(int64) != 1200 {
t.Fatalf("bad: token_ttl; expected: 1200, actual: %d", resp.Data["ttl"])
}
if resp.Data["max_ttl"].(int64) != 1800 {
t.Fatalf("bad: max_ttl; expected: 1800, actual: %d", resp.Data["max_ttl"])
}

// set default lease TTL to 10m; Token TTL should be capped at 10m
// set max lease TTL to 20m; Token Max TTL should be capped at 20m
config = &logical.BackendConfig{
Logger: logging.NewVaultLogger(hclog.Trace),

System: &logical.StaticSystemView{
DefaultLeaseTTLVal: time.Minute * 10,
MaxLeaseTTLVal: time.Minute * 20,
},
StorageView: &logical.InmemStorage{},
}

b, err = Backend(config)
if err != nil {
t.Fatal(err)
}

err = b.Setup(context.Background(), config)
if err != nil {
t.Fatal(err)
}

roleReq.Operation = logical.CreateOperation

resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v, err: %v", resp, err)
}

roleReq.Operation = logical.ReadOperation

resp, err = b.HandleRequest(context.Background(), roleReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatalf("resp: %#v, err: %v", resp, err)
}

if resp.Data["token_ttl"].(int64) != 600 {
t.Fatalf("bad: token_ttl; expected: 600, actual: %d", resp.Data["ttl"])
}

if resp.Data["token_max_ttl"].(int64) != 1200 {
t.Fatalf("bad: token_max_ttl; expected: 1200, actual: %d", resp.Data["ttl"])
}
}

func TestRoleEntryUpgradeV(t *testing.T) {
config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
Expand Down
3 changes: 3 additions & 0 deletions changelog/12026.txt
@@ -0,0 +1,3 @@
```release-note:bug
auth/aws: Fix a bug where the Token TTL for AWS is not capped by Default Lease TTL.
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this still true?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Forgot about that, fixed now :)

```