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

Update genUsername to cap STS usernames at 32 chars #12185

Merged
merged 12 commits into from Aug 9, 2021
55 changes: 19 additions & 36 deletions builtin/logical/aws/secret_access_keys.go
Expand Up @@ -20,7 +20,6 @@ import (
const (
secretAccessKeyType = "access_keys"
storageKey = "config/root"
defaultSTSTemplate = `{{ printf "vault-%d-%d" (unix_time) (random 20) | truncate 32 }}`
)

func secretAccessKeys(b *backend) *framework.Secret {
Expand Down Expand Up @@ -48,42 +47,26 @@ func secretAccessKeys(b *backend) *framework.Secret {
}

func genUsername(displayName, policyName, userType, usernameTemplate string) (ret string, warning string, err error) {
switch userType {
case "iam_user":
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
// IAM users are capped at 64 chars; this leaves, after the beginning and
// end added below, 42 chars to play with.
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return "", "", fmt.Errorf("unable to initialize username template: %w", err)
}

um := UsernameMetadata{
DisplayName: normalizeDisplayName(displayName),
PolicyName: normalizeDisplayName(policyName),
}
// IAM and STS usernames are capped at 64 chars; this leaves, after the beginning and
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
// end added below, 42 chars to play with.
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return "", "", fmt.Errorf("unable to initialize username template: %w", err)
}

ret, err = up.Generate(um)
if err != nil {
return "", "", fmt.Errorf("failed to generate username: %w", err)
}
// To prevent template from exceeding IAM length limits
if len(ret) > 64 {
ret = ret[0:64]
warning = "the calling token display name/IAM policy name were truncated to 64 characters to fit within IAM username length limits"
}
case "sts":
// Capped at 32 chars, which leaves only a couple of characters to play
// with, so don't insert display name or policy name at all
up, err := template.NewTemplate(template.Template(usernameTemplate))
if err != nil {
return "", "", fmt.Errorf("unable to initialize username template: %w", err)
}
um := UsernameMetadata{
DisplayName: normalizeDisplayName(displayName),
PolicyName: normalizeDisplayName(policyName),
}

um := UsernameMetadata{}
ret, err = up.Generate(um)
if err != nil {
return "", "", fmt.Errorf("failed to generate username: %w", err)
}
ret, err = up.Generate(um)
if err != nil {
return "", "", fmt.Errorf("failed to generate username: %w", err)
}
// To prevent template from exceeding IAM/STS length limits
if len(ret) > 64 {
ret = ret[0:64]
warning = fmt.Sprintf("the calling token's %s user name was truncated to 64 characters to fit within username length limits", userType)
}
return
}
Expand Down Expand Up @@ -112,7 +95,7 @@ func (b *backend) getFederationToken(ctx context.Context, s logical.Storage,
return logical.ErrorResponse(err.Error()), nil
}

username, usernameWarning, usernameError := genUsername(displayName, policyName, "sts", defaultSTSTemplate)
username, usernameWarning, usernameError := genUsername(displayName, policyName, "sts", defaultUserNameTemplate)
// Send a 400 to Framework.OperationFunc Handler
if usernameError != nil {
return nil, usernameError
Expand Down
96 changes: 51 additions & 45 deletions builtin/logical/aws/secret_access_keys_test.go
Expand Up @@ -2,6 +2,7 @@ package aws

import (
"context"
"fmt"
"strings"
"testing"

Expand Down Expand Up @@ -47,53 +48,58 @@ func TestNormalizeDisplayName_NormNotRequired(t *testing.T) {
}

func TestGenUsername(t *testing.T) {
type testCase struct {
name string
policy string
userType string
UsernameTemplate string
warningExpected bool
}

tests := map[string]testCase{
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
`^vault-name1-policy1-[0-9]+-[a-zA-Z0-9]+`: {
name: "name1",
policy: "policy1",
userType: "iam_user",
UsernameTemplate: `{{ printf "vault-%s-%s-%s-%s" (.DisplayName) (.PolicyName) (unix_time) (random 20) | truncate 64 }}`,
warningExpected: false,
},
`this---is---a---very---long---name-long------policy------name-[0-9][0-9]`: {
name: "this---is---a---very---long---name",
policy: "long------policy------name",
userType: "iam_user",
UsernameTemplate: `{{ printf "%s-%s-%s-%s" (.DisplayName) (.PolicyName) (unix_time) (random 20) }}`,
warningExpected: true,
},
}

for k, v := range tests {
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
testUsername, warning, err := genUsername(v.name, v.policy, v.userType, v.UsernameTemplate)
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
t.Fatalf(
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
"expected no err; got %s",
err,
)
}

testUsername, warning, err := genUsername("name1", "policy1", "iam_user", `{{ printf "vault-%s-%s-%s-%s" (.DisplayName) (.PolicyName) (unix_time) (random 20) | truncate 64 }}`)
if err != nil {
t.Fatalf(
"expected no err; got %s",
err,
)
}

expectedUsernameRegex := `^vault-name1-policy1-[0-9]+-[a-zA-Z0-9]+`
require.Regexp(t, expectedUsernameRegex, testUsername)
// IAM usernames are capped at 64 characters
if len(testUsername) > 64 {
t.Fatalf(
"expected IAM username to be of length 64, got %d",
len(testUsername),
)
}

testUsername, warning, err = genUsername(
"this---is---a---very---long---name",
"long------policy------name",
"iam_user",
`{{ printf "%s-%s-%s-%s" (.DisplayName) (.PolicyName) (unix_time) (random 20) }}`,
)

if warning == "" || !strings.Contains(warning, "calling token display name/IAM policy name were truncated to 64 characters") {
t.Fatalf("expected a truncate warning; received empty string")
}
if len(testUsername) != 64 {
t.Fatalf("expected a username cap at 64 chars; got length: %d", len(testUsername))
}
expectedUsernameRegex := k
require.Regexp(t, expectedUsernameRegex, testUsername)
// IAM/STS usernames are capped at 64 characters
if len(testUsername) > 64 {
t.Fatalf(
"expected username to be of length 64, got %d",
len(testUsername),
)
}

testUsername, warning, err = genUsername("name1", "policy1", "sts", defaultSTSTemplate)
if strings.Contains(testUsername, "name1") || strings.Contains(testUsername, "policy1") {
t.Fatalf(
"expected sts username to not contain display name or policy name; got %s",
testUsername,
)
}
// STS usernames are capped at 64 characters
if len(testUsername) > 32 {
t.Fatalf(
"expected sts username to be under 32 chars; got %s of length %d",
testUsername,
len(testUsername),
)
if v.warningExpected {
if warning == "" || !strings.Contains(warning, fmt.Sprintf("calling token's %s user name was truncated to 64 characters", v.userType)) {
t.Fatalf("expected a truncate warning; received empty string")
}
if len(testUsername) != 64 {
t.Fatalf("expected a username cap at 64 chars; got length: %d", len(testUsername))
}
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions changelog/12185.txt
@@ -0,0 +1,3 @@
```release-note:improvement
secrets/aws: Cap STS usernames at 64 characters
```