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
54 changes: 18 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,25 @@ 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;
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 +94,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
97 changes: 50 additions & 47 deletions builtin/logical/aws/secret_access_keys_test.go
Expand Up @@ -47,53 +47,56 @@ func TestNormalizeDisplayName_NormNotRequired(t *testing.T) {
}

func TestGenUsername(t *testing.T) {

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))
}

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),
)
type testCase struct {
name string
policy string
userType string
UsernameTemplate string
warningExpected string
expectedRegex string
}

tests := map[string]testCase{
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
"Truncated to 64. No warnings expected": {
name: "name1",
policy: "policy1",
userType: "iam_user",
UsernameTemplate: `{{ printf "vault-%s-%s-%s-%s" (.DisplayName) (.PolicyName) (unix_time) (random 20) | truncate 64 }}`,
warningExpected: "",
expectedRegex: `^vault-name1-policy1-[0-9]+-[a-zA-Z0-9]+`,
},
"Too long. Warning expected": {
vinay-gopalan marked this conversation as resolved.
Show resolved Hide resolved
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: "calling token's iam_user user name was truncated to 64 characters",
expectedRegex: `this---is---a---very---long---name-long------policy------name-[0-9][0-9]`,
},
}

for testDescription, testCase := range tests {
t.Run(testDescription, func(t *testing.T) {
testUsername, warning, err := genUsername(testCase.name, testCase.policy, testCase.userType, testCase.UsernameTemplate)
if err != nil {
t.Fatalf("expected no err; got %s", err)
}

expectedUsernameRegex := testCase.expectedRegex
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))
}

if !strings.Contains(warning, testCase.warningExpected) {
t.Fatalf("expected a truncate warning %s; received %s", testCase.warningExpected, warning)
}

if len(warning) > 0 && 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
```