mirror of
https://github.com/versity/versitygw.git
synced 2026-09-27 18:34:25 +00:00
feat: add IAM role tagging actions
Adds `TagRole`, `UntagRole` and `ListRoleTags` to the standalone IAM service, backed by both the internal and Vault storers, with the same semantics the user tagging actions already have: tag keys are matched case-insensitively but stored case-preserving, TagRole merges into the role's existing tags and rejects duplicate keys, UntagRole removal is idempotent, and ListRoleTags is sorted by key and paginated. The per-request member count and the per-role tag total are enforced as separate quotas, so replacing a tag on a role already at the 50-tag cap still succeeds. Role tags and the tags of a user sharing the same name are independent sets. All three actions are authorized against the target role's ARN, so `aws:ResourceTag/<key>` reads the role's own tags, and TagRole and UntagRole populate `aws:RequestTag/<key>` and `aws:TagKeys` respectively, so a tag-scoped policy Condition governs which tags a caller may set or remove. The tag storage helpers are now shared between users and roles: `MaxTagsPerUser` becomes `MaxTagsPerResource`, `ListUserTagsOutput` becomes `ListTagsOutput`, and `paginateTags` takes the marker and page size directly instead of a user-specific input struct. The WebGUI gains a Tags section in the IAM role manage view, reusing the tag editor the user view already uses, which applies a whole edited tag set as a single UntagRole and TagRole pair. Also corrects the `roleName` length bound across every role action: it was validated against the 128-character user-lookup limit, where IAM caps role names at 64.
This commit is contained in:
+446
-2
@@ -647,12 +647,12 @@ func TestIAMApiControllerTagUserExceedsQuota(t *testing.T) {
|
||||
}
|
||||
|
||||
atQuota := url.Values{"Action": {"TagUser"}, "UserName": {"alice"}}
|
||||
for i := 1; i <= storage.MaxTagsPerUser; i++ {
|
||||
for i := 1; i <= storage.MaxTagsPerResource; i++ {
|
||||
atQuota.Set(fmt.Sprintf("Tags.member.%d.Key", i), fmt.Sprintf("k%d", i))
|
||||
atQuota.Set(fmt.Sprintf("Tags.member.%d.Value", i), fmt.Sprintf("v%d", i))
|
||||
}
|
||||
if resp := doIAMAction(t, server, atQuota); resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("TagUser with %d tags status = %d, body=%s", storage.MaxTagsPerUser, resp.StatusCode, readBody(t, resp))
|
||||
t.Fatalf("TagUser with %d tags status = %d, body=%s", storage.MaxTagsPerResource, resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
|
||||
// Replacing an existing key at the quota is fine: the total doesn't grow.
|
||||
@@ -1469,6 +1469,450 @@ func TestIAMApiControllerRoleLifecycle(t *testing.T) {
|
||||
requireIAMError(t, missing, http.StatusNotFound, "Sender", "NoSuchEntity", "The role with name my-role cannot be found.")
|
||||
}
|
||||
|
||||
func TestIAMApiControllerRoleTagLifecycle(t *testing.T) {
|
||||
server := newIAMControllerTestServer(t)
|
||||
createTestRoleForTrust(t, server, "my-role", validTrustPolicy)
|
||||
|
||||
if got := listRoleTags(t, server, "my-role"); len(got.Tags.Members) != 0 || got.IsTruncated {
|
||||
t.Fatalf("ListRoleTags on a fresh role = %#v, want no tags", got)
|
||||
}
|
||||
|
||||
tagRole(t, server, "my-role", map[string]string{"env": "prod", "team": "storage", "empty": ""})
|
||||
|
||||
got := listRoleTags(t, server, "my-role")
|
||||
// Sorted by key, regardless of the order they were added in.
|
||||
want := []iamtypes.Tag{{Key: "empty", Value: ""}, {Key: "env", Value: "prod"}, {Key: "team", Value: "storage"}}
|
||||
if !slices.Equal(got.Tags.Members, want) {
|
||||
t.Fatalf("Tags = %#v, want %#v", got.Tags.Members, want)
|
||||
}
|
||||
|
||||
// A repeated key replaces its value in place; a differently-cased key
|
||||
// is the same tag, and the new casing wins.
|
||||
tagRole(t, server, "my-role", map[string]string{"env": "staging"})
|
||||
tagRole(t, server, "my-role", map[string]string{"TEAM": "compute"})
|
||||
|
||||
got = listRoleTags(t, server, "my-role")
|
||||
want = []iamtypes.Tag{{Key: "TEAM", Value: "compute"}, {Key: "empty", Value: ""}, {Key: "env", Value: "staging"}}
|
||||
if !slices.Equal(got.Tags.Members, want) {
|
||||
t.Fatalf("Tags after overwrite = %#v, want %#v", got.Tags.Members, want)
|
||||
}
|
||||
|
||||
// GetRole reports the same tags the tag actions maintain.
|
||||
getRole := doIAMAction(t, server, url.Values{"Action": {"GetRole"}, "RoleName": {"my-role"}})
|
||||
var getResp struct {
|
||||
Result struct{ Role iamtypes.Role } `xml:"GetRoleResult"`
|
||||
}
|
||||
unmarshalXML(t, readBody(t, getRole), &getResp)
|
||||
if len(getResp.Result.Role.Tags) != 3 {
|
||||
t.Fatalf("GetRole Tags = %#v, want 3 tags", getResp.Result.Role.Tags)
|
||||
}
|
||||
|
||||
// Removal is case-insensitive, and a key naming no tag is not an error.
|
||||
untag := doIAMAction(t, server, url.Values{
|
||||
"Action": {"UntagRole"},
|
||||
"RoleName": {"my-role"},
|
||||
"TagKeys.member.1": {"EnV"},
|
||||
"TagKeys.member.2": {"never-existed"},
|
||||
})
|
||||
if untag.StatusCode != http.StatusOK {
|
||||
t.Fatalf("UntagRole status = %d, body=%s", untag.StatusCode, readBody(t, untag))
|
||||
}
|
||||
|
||||
got = listRoleTags(t, server, "my-role")
|
||||
want = []iamtypes.Tag{{Key: "TEAM", Value: "compute"}, {Key: "empty", Value: ""}}
|
||||
if !slices.Equal(got.Tags.Members, want) {
|
||||
t.Fatalf("Tags after untag = %#v, want %#v", got.Tags.Members, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIAMApiControllerRoleTagsIndependentOfUserTags covers a role and a user
|
||||
// sharing a name: they are separate entities, so neither one's tags leak
|
||||
// into the other's.
|
||||
func TestIAMApiControllerRoleTagsIndependentOfUserTags(t *testing.T) {
|
||||
server := newIAMControllerTestServer(t)
|
||||
createTestRoleForTrust(t, server, "shared", validTrustPolicy)
|
||||
if resp := doIAMAction(t, server, url.Values{"Action": {"CreateUser"}, "UserName": {"shared"}}); resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("CreateUser status = %d, body=%s", resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
|
||||
tagRole(t, server, "shared", map[string]string{"owner": "role"})
|
||||
tagUser(t, server, "shared", map[string]string{"owner": "user"})
|
||||
|
||||
if got := listRoleTags(t, server, "shared").Tags.Members; !slices.Equal(got, []iamtypes.Tag{{Key: "owner", Value: "role"}}) {
|
||||
t.Fatalf("role tags = %#v", got)
|
||||
}
|
||||
if got := listUserTags(t, server, "shared").Tags.Members; !slices.Equal(got, []iamtypes.Tag{{Key: "owner", Value: "user"}}) {
|
||||
t.Fatalf("user tags = %#v", got)
|
||||
}
|
||||
|
||||
untag := doIAMAction(t, server, url.Values{
|
||||
"Action": {"UntagRole"}, "RoleName": {"shared"}, "TagKeys.member.1": {"owner"},
|
||||
})
|
||||
if untag.StatusCode != http.StatusOK {
|
||||
t.Fatalf("UntagRole status = %d, body=%s", untag.StatusCode, readBody(t, untag))
|
||||
}
|
||||
if got := listRoleTags(t, server, "shared").Tags.Members; len(got) != 0 {
|
||||
t.Fatalf("role tags after untag = %#v, want none", got)
|
||||
}
|
||||
if got := listUserTags(t, server, "shared").Tags.Members; !slices.Equal(got, []iamtypes.Tag{{Key: "owner", Value: "user"}}) {
|
||||
t.Fatalf("user tags after untagging the role = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIAMApiControllerListRoleTagsPagination(t *testing.T) {
|
||||
server := newIAMControllerTestServer(t)
|
||||
createTestRoleForTrust(t, server, "my-role", validTrustPolicy)
|
||||
tagRole(t, server, "my-role", map[string]string{"a": "1", "b": "2", "c": "3"})
|
||||
|
||||
var seen []iamtypes.Tag
|
||||
marker := ""
|
||||
for page := 1; ; page++ {
|
||||
params := url.Values{"Action": {"ListRoleTags"}, "RoleName": {"my-role"}, "MaxItems": {"1"}}
|
||||
if marker != "" {
|
||||
params.Set("Marker", marker)
|
||||
}
|
||||
resp := doIAMAction(t, server, params)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("ListRoleTags status = %d, body=%s", resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
var out struct {
|
||||
Result iamtypes.ListRoleTagsResult `xml:"ListRoleTagsResult"`
|
||||
}
|
||||
unmarshalXML(t, readBody(t, resp), &out)
|
||||
|
||||
if len(out.Result.Tags.Members) != 1 {
|
||||
t.Fatalf("page %d holds %d tags, want 1", page, len(out.Result.Tags.Members))
|
||||
}
|
||||
seen = append(seen, out.Result.Tags.Members...)
|
||||
if !out.Result.IsTruncated {
|
||||
if out.Result.Marker != "" {
|
||||
t.Fatalf("final page Marker = %q, want empty", out.Result.Marker)
|
||||
}
|
||||
break
|
||||
}
|
||||
marker = out.Result.Marker
|
||||
}
|
||||
|
||||
want := []iamtypes.Tag{{Key: "a", Value: "1"}, {Key: "b", Value: "2"}, {Key: "c", Value: "3"}}
|
||||
if !slices.Equal(seen, want) {
|
||||
t.Fatalf("paged tags = %#v, want %#v", seen, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIAMApiControllerTagRoleExceedsQuota(t *testing.T) {
|
||||
server := newIAMControllerTestServer(t)
|
||||
createTestRoleForTrust(t, server, "my-role", validTrustPolicy)
|
||||
|
||||
atQuota := url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}}
|
||||
for i := 1; i <= storage.MaxTagsPerResource; i++ {
|
||||
atQuota.Set(fmt.Sprintf("Tags.member.%d.Key", i), fmt.Sprintf("k%d", i))
|
||||
atQuota.Set(fmt.Sprintf("Tags.member.%d.Value", i), fmt.Sprintf("v%d", i))
|
||||
}
|
||||
if resp := doIAMAction(t, server, atQuota); resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("TagRole with %d tags status = %d, body=%s", storage.MaxTagsPerResource, resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
|
||||
// Replacing an existing key at the quota is fine: the total doesn't grow.
|
||||
replace := doIAMAction(t, server, url.Values{
|
||||
"Action": {"TagRole"}, "RoleName": {"my-role"},
|
||||
"Tags.member.1.Key": {"k1"}, "Tags.member.1.Value": {"replaced"},
|
||||
})
|
||||
if replace.StatusCode != http.StatusOK {
|
||||
t.Fatalf("TagRole replacing at quota status = %d, body=%s", replace.StatusCode, readBody(t, replace))
|
||||
}
|
||||
|
||||
// One more distinct key does not fit.
|
||||
overflow := doIAMAction(t, server, url.Values{
|
||||
"Action": {"TagRole"}, "RoleName": {"my-role"},
|
||||
"Tags.member.1.Key": {"overflow"}, "Tags.member.1.Value": {"x"},
|
||||
})
|
||||
requireIAMError(t, overflow, http.StatusConflict, "Sender", "LimitExceeded",
|
||||
"The number of tags has reached the maximum limit.")
|
||||
}
|
||||
|
||||
func TestIAMApiControllerRoleTagValidationErrors(t *testing.T) {
|
||||
tooManyTags := url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}}
|
||||
for i := 1; i <= iamutil.MaxTagMembersPerRequest+1; i++ {
|
||||
tooManyTags.Set(fmt.Sprintf("Tags.member.%d.Key", i), fmt.Sprintf("k%d", i))
|
||||
tooManyTags.Set(fmt.Sprintf("Tags.member.%d.Value", i), fmt.Sprintf("v%d", i))
|
||||
}
|
||||
tooManyTagKeys := url.Values{"Action": {"UntagRole"}, "RoleName": {"my-role"}}
|
||||
for i := 1; i <= iamutil.MaxTagMembersPerRequest+1; i++ {
|
||||
tooManyTagKeys.Set(fmt.Sprintf("TagKeys.member.%d", i), fmt.Sprintf("k%d", i))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
setupRole bool
|
||||
params url.Values
|
||||
status int
|
||||
code string
|
||||
message string
|
||||
}{
|
||||
{
|
||||
name: "tag missing role name",
|
||||
params: url.Values{"Action": {"TagRole"}, "Tags.member.1.Key": {"env"}, "Tags.member.1.Value": {"prod"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'roleName' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "tag missing tags",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "tag missing key",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Value": {"prod"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags.1.member.key' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "tag missing value",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {"env"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags.1.member.value' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "tag empty key",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {""}, "Tags.member.1.Value": {"prod"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags.1.member.key' failed to satisfy constraint: Member must have length greater than or equal to 1",
|
||||
},
|
||||
{
|
||||
name: "tag key too long",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {strings.Repeat("k", 129)}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags.1.member.key' failed to satisfy constraint: Member must have length less than or equal to 128",
|
||||
},
|
||||
{
|
||||
name: "tag invalid key characters",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {"bad*key"}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: `1 validation error detected: Value at 'tags.1.member.key' failed to satisfy constraint: Member must satisfy regular expression pattern: [\p{L}\p{Z}\p{N}_.:/=+\-@]+`,
|
||||
},
|
||||
{
|
||||
name: "tag value too long",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {"k"}, "Tags.member.1.Value": {strings.Repeat("v", 257)}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags.1.member.value' failed to satisfy constraint: Member must have length less than or equal to 256",
|
||||
},
|
||||
{
|
||||
name: "tag invalid value characters",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"my-role"}, "Tags.member.1.Key": {"k"}, "Tags.member.1.Value": {"bad*value"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: `1 validation error detected: Value at 'tags.1.member.value' failed to satisfy constraint: Member must satisfy regular expression pattern: [\p{L}\p{Z}\p{N}_.:/=+\-@]*`,
|
||||
},
|
||||
{
|
||||
name: "tag duplicate keys",
|
||||
setupRole: true,
|
||||
params: url.Values{
|
||||
"Action": {"TagRole"}, "RoleName": {"my-role"},
|
||||
"Tags.member.1.Key": {"env"}, "Tags.member.1.Value": {"a"},
|
||||
"Tags.member.2.Key": {"ENV"}, "Tags.member.2.Value": {"b"},
|
||||
},
|
||||
status: http.StatusBadRequest,
|
||||
code: "InvalidInput",
|
||||
message: "Duplicate tag keys found. Please note that Tag keys are case insensitive.",
|
||||
},
|
||||
{
|
||||
name: "tag too many tags",
|
||||
setupRole: true,
|
||||
params: tooManyTags,
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tags' failed to satisfy constraint: Member must have length less than or equal to 50",
|
||||
},
|
||||
{
|
||||
name: "tag invalid role name characters",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"bad!name"}, "Tags.member.1.Key": {"k"}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "The specified value for roleName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_-",
|
||||
},
|
||||
{
|
||||
name: "tag role name too long",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {strings.Repeat("r", 65)}, "Tags.member.1.Key": {"k"}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'roleName' failed to satisfy constraint: Member must have length less than or equal to 64",
|
||||
},
|
||||
{
|
||||
// A malformed tag is reported before the role is looked up.
|
||||
name: "tag non existing role with invalid tag",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"nosuchrole"}, "Tags.member.1.Key": {"bad*key"}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: `1 validation error detected: Value at 'tags.1.member.key' failed to satisfy constraint: Member must satisfy regular expression pattern: [\p{L}\p{Z}\p{N}_.:/=+\-@]+`,
|
||||
},
|
||||
{
|
||||
name: "tag non existing role",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"TagRole"}, "RoleName": {"nosuchrole"}, "Tags.member.1.Key": {"k"}, "Tags.member.1.Value": {"v"}},
|
||||
status: http.StatusNotFound,
|
||||
code: "NoSuchEntity",
|
||||
message: "The role with name nosuchrole cannot be found.",
|
||||
},
|
||||
{
|
||||
name: "untag missing role name",
|
||||
params: url.Values{"Action": {"UntagRole"}, "TagKeys.member.1": {"env"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'roleName' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "untag missing tag keys",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"UntagRole"}, "RoleName": {"my-role"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tagKeys' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "untag empty key",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"UntagRole"}, "RoleName": {"my-role"}, "TagKeys.member.1": {""}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: invalidTagKeysMessage,
|
||||
},
|
||||
{
|
||||
name: "untag key too long",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"UntagRole"}, "RoleName": {"my-role"}, "TagKeys.member.1": {strings.Repeat("k", 129)}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: invalidTagKeysMessage,
|
||||
},
|
||||
{
|
||||
name: "untag invalid key characters",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"UntagRole"}, "RoleName": {"my-role"}, "TagKeys.member.1": {"bad*key"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: invalidTagKeysMessage,
|
||||
},
|
||||
{
|
||||
name: "untag too many tag keys",
|
||||
setupRole: true,
|
||||
params: tooManyTagKeys,
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'tagKeys' failed to satisfy constraint: Member must have length less than or equal to 50",
|
||||
},
|
||||
{
|
||||
name: "untag non existing role",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"UntagRole"}, "RoleName": {"nosuchrole"}, "TagKeys.member.1": {"env"}},
|
||||
status: http.StatusNotFound,
|
||||
code: "NoSuchEntity",
|
||||
message: "The role with name nosuchrole cannot be found.",
|
||||
},
|
||||
{
|
||||
name: "list missing role name",
|
||||
params: url.Values{"Action": {"ListRoleTags"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'roleName' failed to satisfy constraint: Member must not be null",
|
||||
},
|
||||
{
|
||||
name: "list max items too small",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"ListRoleTags"}, "RoleName": {"my-role"}, "MaxItems": {"0"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'maxItems' failed to satisfy constraint: Member must have value greater than or equal to 1",
|
||||
},
|
||||
{
|
||||
name: "list max items too large",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"ListRoleTags"}, "RoleName": {"my-role"}, "MaxItems": {"1001"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "ValidationError",
|
||||
message: "1 validation error detected: Value at 'maxItems' failed to satisfy constraint: Member must have value less than or equal to 1000",
|
||||
},
|
||||
{
|
||||
name: "list max items not a number",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"ListRoleTags"}, "RoleName": {"my-role"}, "MaxItems": {"abc"}},
|
||||
status: http.StatusBadRequest,
|
||||
code: "MalformedInput",
|
||||
message: "",
|
||||
},
|
||||
{
|
||||
name: "list non existing role",
|
||||
setupRole: true,
|
||||
params: url.Values{"Action": {"ListRoleTags"}, "RoleName": {"nosuchrole"}},
|
||||
status: http.StatusNotFound,
|
||||
code: "NoSuchEntity",
|
||||
message: "The role with name nosuchrole cannot be found.",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server := newIAMControllerTestServer(t)
|
||||
if tt.setupRole {
|
||||
createTestRoleForTrust(t, server, "my-role", validTrustPolicy)
|
||||
}
|
||||
resp := doIAMAction(t, server, tt.params)
|
||||
requireIAMError(t, resp, tt.status, "Sender", tt.code, tt.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func tagRole(t *testing.T, server *IAMApiServer, roleName string, tags map[string]string) {
|
||||
t.Helper()
|
||||
|
||||
params := url.Values{"Action": {"TagRole"}, "RoleName": {roleName}}
|
||||
i := 1
|
||||
for key, value := range tags {
|
||||
params.Set(fmt.Sprintf("Tags.member.%d.Key", i), key)
|
||||
params.Set(fmt.Sprintf("Tags.member.%d.Value", i), value)
|
||||
i++
|
||||
}
|
||||
|
||||
resp := doIAMAction(t, server, params)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("TagRole status = %d, body=%s", resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
}
|
||||
|
||||
func listRoleTags(t *testing.T, server *IAMApiServer, roleName string) iamtypes.ListRoleTagsResult {
|
||||
t.Helper()
|
||||
|
||||
resp := doIAMAction(t, server, url.Values{"Action": {"ListRoleTags"}, "RoleName": {roleName}})
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("ListRoleTags status = %d, body=%s", resp.StatusCode, readBody(t, resp))
|
||||
}
|
||||
|
||||
var out struct {
|
||||
Result iamtypes.ListRoleTagsResult `xml:"ListRoleTagsResult"`
|
||||
}
|
||||
unmarshalXML(t, readBody(t, resp), &out)
|
||||
return out.Result
|
||||
}
|
||||
|
||||
func TestIAMApiControllerCreateRoleValidationErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Reference in New Issue
Block a user