fix: return ValidationError for IAM name, path and enum violations

AWS answers a bad name charset, a path not bounded by slashes and an
enum violation such as UpdateAccessKey's status with ValidationError.
Length and tag violations keep InvalidInput until AWS's code for them
is captured.
This commit is contained in:
Julian Sommer
2026-09-28 15:59:51 +10:00
parent 866392e680
commit b784483a03
7 changed files with 48 additions and 44 deletions
+2 -2
View File
@@ -95,7 +95,7 @@ func TestCreateGroup_InvalidPath(t *testing.T) {
Path: aws.String("no-leading-slash/"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestGetGroup(t *testing.T) {
@@ -773,7 +773,7 @@ func TestPutGroupPolicy_InvalidName(t *testing.T) {
PolicyDocument: aws.String(validPolicyDocument()),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestPutGroupPolicy_MalformedDocument(t *testing.T) {
@@ -82,7 +82,7 @@ func TestCreateInstanceProfile_InvalidPath(t *testing.T) {
Path: aws.String("missing-slashes"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestCreateInstanceProfile_Duplicate(t *testing.T) {
+2 -2
View File
@@ -110,7 +110,7 @@ func TestCreateRole_InvalidPath(t *testing.T) {
Path: aws.String("no-leading-slash/"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestCreateRole_PermissionsBoundarySet(t *testing.T) {
@@ -1028,7 +1028,7 @@ func TestPutRolePolicy_InvalidName(t *testing.T) {
PolicyDocument: aws.String(validPolicyDocument()),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestPutRolePolicy_MalformedDocument(t *testing.T) {
+3 -3
View File
@@ -2111,7 +2111,7 @@ func validateIAMName(field, name string, maxLen int) error {
}
for i := range len(name) {
if !isIAMNameChar(name[i]) {
return awserrors.Errorf(awserrors.ErrorIAMInvalidInput,
return awserrors.Errorf(awserrors.ErrorValidationError,
"The specified value for %s is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-", field)
}
}
@@ -2124,7 +2124,7 @@ func lengthViolation(value, field, bound string) error {
}
func enumViolation(field string, allowed []string) error {
return awserrors.Errorf(awserrors.ErrorIAMInvalidInput,
return awserrors.Errorf(awserrors.ErrorValidationError,
"1 validation error detected: Value at '%s' failed to satisfy constraint: Member must satisfy enum value set: [%s]", field, strings.Join(allowed, ", "))
}
@@ -2141,7 +2141,7 @@ func validatePermissionsBoundary(boundary *string) error {
func validatePath(path string) error {
if !strings.HasPrefix(path, "/") || !strings.HasSuffix(path, "/") {
return awserrors.Errorf(awserrors.ErrorIAMInvalidInput,
return awserrors.Errorf(awserrors.ErrorValidationError,
"The specified value for path is invalid. It must begin and end with / and contain only alphanumeric characters and/or / characters.")
}
if len(path) > 512 {
+38 -34
View File
@@ -396,7 +396,7 @@ func TestCreateUser_InvalidName_BadChars(t *testing.T) {
_, err := svc.CreateUser(testAccountID, &iam.CreateUserInput{
UserName: aws.String("bad name!"),
})
requireIAMInvalidInput(t, err,
requireIAMError(t, err, awserrors.ErrorValidationError,
"The specified value for userName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-")
}
@@ -408,7 +408,7 @@ func TestCreateUser_InvalidPath(t *testing.T) {
UserName: aws.String("validuser"),
Path: aws.String("noslash"),
})
requireIAMInvalidInput(t, err,
requireIAMError(t, err, awserrors.ErrorValidationError,
"The specified value for path is invalid. It must begin and end with / and contain only alphanumeric characters and/or / characters.")
}
@@ -435,7 +435,7 @@ func TestCreatePolicy_InvalidPath(t *testing.T) {
Path: aws.String("bad-path"),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestValidatePolicyDocument_TooLarge(t *testing.T) {
@@ -690,7 +690,7 @@ func TestUpdateAccessKey_InvalidStatus(t *testing.T) {
AccessKeyId: keyOut.AccessKey.AccessKeyId,
Status: aws.String("Invalid"),
})
requireIAMInvalidInput(t, err,
requireIAMError(t, err, awserrors.ErrorValidationError,
"1 validation error detected: Value at 'status' failed to satisfy constraint: Member must satisfy enum value set: [Expired, Active, Inactive]")
}
@@ -2585,25 +2585,27 @@ func TestIsIAMNameChar(t *testing.T) {
func TestValidateIAMName(t *testing.T) {
t.Parallel()
const charset = "It must contain only alphanumeric characters and/or the following: +=,.@_-"
invalid, validation := awserrors.ErrorIAMInvalidInput, awserrors.ErrorValidationError
tests := []struct {
name string
field string
input string
maxLen int
wantMsg string
name string
field string
input string
maxLen int
wantCode string
wantMsg string
}{
{"valid simple", "userName", "alice", 64, ""},
{"valid with special chars", "userName", "alice.bob+test@example_com", 64, ""},
{"valid single char", "userName", "a", 64, ""},
{"valid at max length", "userName", strings.Repeat("a", 64), 64, ""},
{"valid policy at max length", "policyName", strings.Repeat("x", 128), 128, ""},
{"empty", "userName", "", 64,
{"valid simple", "userName", "alice", 64, "", ""},
{"valid with special chars", "userName", "alice.bob+test@example_com", 64, "", ""},
{"valid single char", "userName", "a", 64, "", ""},
{"valid at max length", "userName", strings.Repeat("a", 64), 64, "", ""},
{"valid policy at max length", "policyName", strings.Repeat("x", 128), 128, "", ""},
{"empty", "userName", "", 64, invalid,
"1 validation error detected: Value '' at 'userName' failed to satisfy constraint: Member must have length greater than or equal to 1"},
{"too long", "policyName", strings.Repeat("x", 129), 128,
{"too long", "policyName", strings.Repeat("x", 129), 128, invalid,
"1 validation error detected: Value '" + strings.Repeat("x", 129) + "' at 'policyName' failed to satisfy constraint: Member must have length less than or equal to 128"},
{"space", "userName", "bad name!", 64, "The specified value for userName is invalid. " + charset},
{"slash", "roleName", "alice/bob", 64, "The specified value for roleName is invalid. " + charset},
{"colon", "groupName", "alice:bob", 128, "The specified value for groupName is invalid. " + charset},
{"space", "userName", "bad name!", 64, validation, "The specified value for userName is invalid. " + charset},
{"slash", "roleName", "alice/bob", 64, validation, "The specified value for roleName is invalid. " + charset},
{"colon", "groupName", "alice:bob", 128, validation, "The specified value for groupName is invalid. " + charset},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -2613,17 +2615,17 @@ func TestValidateIAMName(t *testing.T) {
assert.NoError(t, err)
return
}
requireIAMInvalidInput(t, err, tt.wantMsg)
requireIAMError(t, err, tt.wantCode, tt.wantMsg)
})
}
}
func requireIAMInvalidInput(t *testing.T, err error, wantMsg string) {
func requireIAMError(t *testing.T, err error, wantCode, wantMsg string) {
t.Helper()
require.Error(t, err)
code, msg, ok := awserrors.ResolveErrorDetail(err)
require.True(t, ok, "error must carry a registered code: %v", err)
assert.Equal(t, awserrors.ErrorIAMInvalidInput, code)
assert.Equal(t, wantCode, code)
assert.Equal(t, wantMsg, msg)
}
@@ -2631,19 +2633,21 @@ func TestValidatePath(t *testing.T) {
t.Parallel()
const shape = "The specified value for path is invalid. It must begin and end with / and contain only alphanumeric characters and/or / characters."
tooLong := "/" + strings.Repeat("a", 511) + "/"
invalid, validation := awserrors.ErrorIAMInvalidInput, awserrors.ErrorValidationError
tests := []struct {
name string
input string
wantMsg string
name string
input string
wantCode string
wantMsg string
}{
{"root path", "/", ""},
{"nested path", "/division/engineering/", ""},
{"no leading slash", "division/", shape},
{"no trailing slash", "/division", shape},
{"empty string", "", shape},
{"just text", "noslash", shape},
{"max length 512", "/" + strings.Repeat("a", 510) + "/", ""},
{"over max length 513", tooLong,
{"root path", "/", "", ""},
{"nested path", "/division/engineering/", "", ""},
{"no leading slash", "division/", validation, shape},
{"no trailing slash", "/division", validation, shape},
{"empty string", "", validation, shape},
{"just text", "noslash", validation, shape},
{"max length 512", "/" + strings.Repeat("a", 510) + "/", "", ""},
{"over max length 513", tooLong, invalid,
"1 validation error detected: Value '" + tooLong + "' at 'path' failed to satisfy constraint: Member must have length less than or equal to 512"},
}
for _, tt := range tests {
@@ -2654,7 +2658,7 @@ func TestValidatePath(t *testing.T) {
assert.NoError(t, err)
return
}
requireIAMInvalidInput(t, err, tt.wantMsg)
requireIAMError(t, err, tt.wantCode, tt.wantMsg)
})
}
}
+1 -1
View File
@@ -75,7 +75,7 @@ func TestValidateTags_NamesFailingMember(t *testing.T) {
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
requireIAMInvalidInput(t, validateTags(tc.tags), tc.wantMsg)
requireIAMError(t, validateTags(tc.tags), awserrors.ErrorIAMInvalidInput, tc.wantMsg)
})
}
}
+1 -1
View File
@@ -82,7 +82,7 @@ func TestPutUserPolicy_InvalidName(t *testing.T) {
PolicyDocument: aws.String(validPolicyDocument()),
})
assert.Error(t, err)
assert.Contains(t, err.Error(), awserrors.ErrorIAMInvalidInput)
assert.Contains(t, err.Error(), awserrors.ErrorValidationError)
}
func TestPutUserPolicy_MalformedDocument(t *testing.T) {