Skip to content

Commit

Permalink
VAULT-18307: update rotation period for aws static roles on update (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kpcraig authored Oct 11, 2023
1 parent 2f8e59c commit 30f19b3
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 7 deletions.
14 changes: 14 additions & 0 deletions builtin/logical/aws/path_static_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ func (b *backend) pathStaticRolesWrite(ctx context.Context, req *logical.Request
if err != nil {
return nil, fmt.Errorf("failed to add item into the rotation queue for role %q: %w", config.Name, err)
}
} else {
// creds already exist, so all we need to do is update the rotation
// what here stays the same and what changes? Can we change the name?
i, err := b.credRotationQueue.PopByKey(config.Name)
if err != nil {
return nil, fmt.Errorf("expected an item with name %q, but got an error: %w", config.Name, err)
}
i.Value = config
// update the next rotation to occur at now + the new rotation period
i.Priority = time.Now().Add(config.RotationPeriod).Unix()
err = b.credRotationQueue.Push(i)
if err != nil {
return nil, fmt.Errorf("failed to add updated item into the rotation queue for role %q: %w", config.Name, err)
}
}

return &logical.Response{
Expand Down
65 changes: 59 additions & 6 deletions builtin/logical/aws/path_static_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,21 @@ func TestStaticRolesWrite(t *testing.T) {
bgCTX := context.Background()

cases := []struct {
name string
opts []awsutil.MockIAMOption
name string
// objects to return from mock IAM.
// You'll need a GetUserOutput (to validate the existence of the user being written,
// the keys the user has already been assigned,
// and the new key vault requests.
opts []awsutil.MockIAMOption // objects to return from the mock IAM
// the name, username if updating, and rotation_period of the user. This is the inbound request the cod would get.
data map[string]interface{}
expectedError bool
findUser bool
isUpdate bool
// if data is sent the name "johnny", then we'll match an existing user with rotation period 24 hours.
isUpdate bool
newPriority int64 // update time of new item in queue, skip if isUpdate false. There is a wiggle room of 5 seconds
// so the deltas between the old and the new update time should be larger than that to ensure the difference
// can be detected.
}{
{
name: "happy path",
Expand Down Expand Up @@ -168,7 +177,7 @@ func TestStaticRolesWrite(t *testing.T) {
expectedError: true,
},
{
name: "update existing user",
name: "update existing user, decreased rotation duration",
opts: []awsutil.MockIAMOption{
awsutil.WithGetUserOutput(&iam.GetUserOutput{User: &iam.User{UserName: aws.String("john-doe"), UserId: aws.String("unique-id")}}),
awsutil.WithListAccessKeysOutput(&iam.ListAccessKeysOutput{
Expand All @@ -187,8 +196,33 @@ func TestStaticRolesWrite(t *testing.T) {
"name": "johnny",
"rotation_period": "19m",
},
findUser: true,
isUpdate: true,
findUser: true,
isUpdate: true,
newPriority: time.Now().Add(19 * time.Minute).Unix(),
},
{
name: "update existing user, increased rotation duration",
opts: []awsutil.MockIAMOption{
awsutil.WithGetUserOutput(&iam.GetUserOutput{User: &iam.User{UserName: aws.String("john-doe"), UserId: aws.String("unique-id")}}),
awsutil.WithListAccessKeysOutput(&iam.ListAccessKeysOutput{
AccessKeyMetadata: []*iam.AccessKeyMetadata{},
IsTruncated: aws.Bool(false),
}),
awsutil.WithCreateAccessKeyOutput(&iam.CreateAccessKeyOutput{
AccessKey: &iam.AccessKey{
AccessKeyId: aws.String("abcdefghijklmnopqrstuvwxyz"),
SecretAccessKey: aws.String("zyxwvutsrqponmlkjihgfedcba"),
UserName: aws.String("john-doe"),
},
}),
},
data: map[string]interface{}{
"name": "johnny",
"rotation_period": "40h",
},
findUser: true,
isUpdate: true,
newPriority: time.Now().Add(40 * time.Hour).Unix(),
},
}

Expand Down Expand Up @@ -269,6 +303,11 @@ func TestStaticRolesWrite(t *testing.T) {
expectedData = staticRole
}

var actualItem *queue.Item
if c.isUpdate {
actualItem, _ = b.credRotationQueue.PopByKey(expectedData.Name)
}

if u, ok := fieldData.GetOk("username"); ok {
expectedData.Username = u.(string)
}
Expand All @@ -289,6 +328,20 @@ func TestStaticRolesWrite(t *testing.T) {
if en, an := expectedData.Name, actualData.Name; en != an {
t.Fatalf("mismatched role name, expected %q, but got %q", en, an)
}

// one-off to avoid importing/casting
abs := func(x int64) int64 {
if x < 0 {
return -x
}
return x
}

if c.isUpdate {
if ep, ap := c.newPriority, actualItem.Priority; abs(ep-ap) > 5 { // 5 second wiggle room for how long the test takes
t.Fatalf("mismatched updated priority, expected %d but got %d", ep, ap)
}
}
})
}
}
Expand Down
3 changes: 3 additions & 0 deletions changelog/23528.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
secrets/aws: update credential rotation deadline when static role rotation period is updated
```
3 changes: 2 additions & 1 deletion website/content/api-docs/secret/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -586,7 +586,7 @@ $ curl \
}
```

## Create static role
## Create/Update static role
This endpoint creates or updates static role definitions. A static role is a 1-to-1 mapping
with an AWS IAM User, which will be adopted and managed by Vault, including rotating it according
to the configured `rotation_period`.
Expand All @@ -613,6 +613,7 @@ is specified as part of the URL.
- `rotation_period` `(string/int: <required>)` – Specifies the amount of time
Vault should wait before rotating the password. The minimum is 1 minute. Can be
specified in either `24h` or `86400` format (see [duration format strings](/vault/docs/concepts/duration-format)).
Updating the rotation period will 'reset' the next rotation to occur at `now` + `rotation_period`.

### Sample payload

Expand Down

0 comments on commit 30f19b3

Please sign in to comment.