diff --git a/.changelog/12797.txt b/.changelog/12797.txt new file mode 100644 index 000000000..d1d8ae932 --- /dev/null +++ b/.changelog/12797.txt @@ -0,0 +1,3 @@ +```release-note:bug +acl: Fix parsing of IAM user and role tags in IAM auth method +``` diff --git a/internal/iamauth/auth_test.go b/internal/iamauth/auth_test.go index 736c3203a..909b64509 100644 --- a/internal/iamauth/auth_test.go +++ b/internal/iamauth/auth_test.go @@ -7,6 +7,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/hashicorp/consul/internal/iamauth/iamauthtest" + "github.com/hashicorp/consul/internal/iamauth/responses" "github.com/hashicorp/consul/internal/iamauth/responsestest" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" @@ -18,11 +19,11 @@ func TestValidateLogin(t *testing.T) { var ( serverForRoleMismatchedIds = &iamauthtest.Server{ GetCallerIdentityResponse: f.ServerForRole.GetCallerIdentityResponse, - GetRoleResponse: responsestest.MakeGetRoleResponse(f.RoleARN, "AAAAsomenonmatchingid"), + GetRoleResponse: responsestest.MakeGetRoleResponse(f.RoleARN, "AAAAsomenonmatchingid", responses.Tags{}), } serverForUserMismatchedIds = &iamauthtest.Server{ GetCallerIdentityResponse: f.ServerForUser.GetCallerIdentityResponse, - GetUserResponse: responsestest.MakeGetUserResponse(f.UserARN, "AAAAsomenonmatchingid"), + GetUserResponse: responsestest.MakeGetUserResponse(f.UserARN, "AAAAsomenonmatchingid", responses.Tags{}), } ) diff --git a/internal/iamauth/iamauthtest/testing.go b/internal/iamauth/iamauthtest/testing.go index 4cb8519a9..b2e1fb37c 100644 --- a/internal/iamauth/iamauthtest/testing.go +++ b/internal/iamauth/iamauthtest/testing.go @@ -133,7 +133,7 @@ func MakeFixture() Fixture { f.AssumedRoleARN, f.EntityIDWithSession, f.AccountID, ), GetRoleResponse: responsestest.MakeGetRoleResponse( - f.RoleARN, f.EntityID, toTags(f.RoleTags)..., + f.RoleARN, f.EntityID, toTags(f.RoleTags), ), } @@ -142,7 +142,7 @@ func MakeFixture() Fixture { f.UserARN, f.EntityID, f.AccountID, ), GetUserResponse: responsestest.MakeGetUserResponse( - f.UserARN, f.EntityID, toTags(f.UserTags)..., + f.UserARN, f.EntityID, toTags(f.UserTags), ), } @@ -155,15 +155,15 @@ func (f *Fixture) RoleTagValues() []string { return values(f.RoleTags) } func (f *Fixture) UserTagValues() []string { return values(f.UserTags) } // toTags converts the map to a slice of responses.Tag -func toTags(tags map[string]string) []responses.Tag { - result := []responses.Tag{} +func toTags(tags map[string]string) responses.Tags { + members := []responses.TagMember{} for k, v := range tags { - result = append(result, responses.Tag{ + members = append(members, responses.TagMember{ Key: k, Value: v, }) } - return result + return responses.Tags{Members: members} } diff --git a/internal/iamauth/responses/responses.go b/internal/iamauth/responses/responses.go index e050b7734..ed57ca97b 100644 --- a/internal/iamauth/responses/responses.go +++ b/internal/iamauth/responses/responses.go @@ -45,7 +45,7 @@ type Role struct { Path string `xml:"Path"` RoleId string `xml:"RoleId"` RoleName string `xml:"RoleName"` - Tags []Tag `xml:"Tags"` + Tags Tags `xml:"Tags"` } func (r *Role) EntityPath() string { return r.Path } @@ -69,7 +69,7 @@ type User struct { Path string `xml:"Path"` UserId string `xml:"UserId"` UserName string `xml:"UserName"` - Tags []Tag `xml:"Tags"` + Tags Tags `xml:"Tags"` } func (u *User) EntityPath() string { return u.Path } @@ -78,14 +78,18 @@ func (u *User) EntityName() string { return u.UserName } func (u *User) EntityId() string { return u.UserId } func (u *User) EntityTags() map[string]string { return tagsToMap(u.Tags) } -type Tag struct { +type Tags struct { + Members []TagMember `xml:"member"` +} + +type TagMember struct { Key string `xml:"Key"` Value string `xml:"Value"` } -func tagsToMap(tags []Tag) map[string]string { +func tagsToMap(tags Tags) map[string]string { result := map[string]string{} - for _, tag := range tags { + for _, tag := range tags.Members { result[tag.Key] = tag.Value } return result diff --git a/internal/iamauth/responses/responses_test.go b/internal/iamauth/responses/responses_test.go index df4a9c1e3..a641be45a 100644 --- a/internal/iamauth/responses/responses_test.go +++ b/internal/iamauth/responses/responses_test.go @@ -1,6 +1,7 @@ package responses import ( + "encoding/xml" "testing" "github.com/stretchr/testify/require" @@ -155,3 +156,138 @@ func TestCanonicalArn(t *testing.T) { }) } } + +func TestUnmarshalXML(t *testing.T) { + t.Run("user xml", func(t *testing.T) { + var resp GetUserResponse + err := xml.Unmarshal([]byte(rawUserXML), &resp) + require.NoError(t, err) + require.Equal(t, expectedParsedUserXML, resp) + }) + t.Run("role xml", func(t *testing.T) { + var resp GetRoleResponse + err := xml.Unmarshal([]byte(rawRoleXML), &resp) + require.NoError(t, err) + require.Equal(t, expectedParsedRoleXML, resp) + }) +} + +var ( + rawUserXML = ` + + + / + arn:aws:iam::000000000000:user/my-user + my-user + AIDAexampleuserid + 2021-01-01T00:01:02Z + + + some-value + some-tag + + + another-value + another-tag + + + third-value + third-tag + + + + + + 11815b96-cb16-4d33-b2cf-0042fa4db4cd + +` + + expectedParsedUserXML = GetUserResponse{ + XMLName: xml.Name{ + Space: "https://iam.amazonaws.com/doc/2010-05-08/", + Local: "GetUserResponse", + }, + GetUserResult: []GetUserResult{ + { + User: User{ + Arn: "arn:aws:iam::000000000000:user/my-user", + Path: "/", + UserId: "AIDAexampleuserid", + UserName: "my-user", + Tags: Tags{ + Members: []TagMember{ + {Key: "some-tag", Value: "some-value"}, + {Key: "another-tag", Value: "another-value"}, + {Key: "third-tag", Value: "third-value"}, + }, + }, + }, + }, + }, + ResponseMetadata: []ResponseMetadata{ + {RequestId: "11815b96-cb16-4d33-b2cf-0042fa4db4cd"}, + }, + } + + rawRoleXML = ` + + + / + some-json-document-that-we-ignore + 43200 + AROAsomeuniqueid + + 2022-01-01T01:02:03Z + us-east-1 + + my-role + arn:aws:iam::000000000000:role/my-role + 2020-01-01T00:00:01Z + + + some-value + some-key + + + another-value + another-key + + + a-third-value + third-key + + + + + + a9866067-c0e5-4b5e-86ba-429c1151e2fb + +` + + expectedParsedRoleXML = GetRoleResponse{ + XMLName: xml.Name{ + Space: "https://iam.amazonaws.com/doc/2010-05-08/", + Local: "GetRoleResponse", + }, + GetRoleResult: []GetRoleResult{ + { + Role: Role{ + Arn: "arn:aws:iam::000000000000:role/my-role", + Path: "/", + RoleId: "AROAsomeuniqueid", + RoleName: "my-role", + Tags: Tags{ + Members: []TagMember{ + {Key: "some-key", Value: "some-value"}, + {Key: "another-key", Value: "another-value"}, + {Key: "third-key", Value: "a-third-value"}, + }, + }, + }, + }, + }, + ResponseMetadata: []ResponseMetadata{ + {RequestId: "a9866067-c0e5-4b5e-86ba-429c1151e2fb"}, + }, + } +) diff --git a/internal/iamauth/responsestest/testing.go b/internal/iamauth/responsestest/testing.go index 683308677..7daec0517 100644 --- a/internal/iamauth/responsestest/testing.go +++ b/internal/iamauth/responsestest/testing.go @@ -31,7 +31,7 @@ func MakeGetCallerIdentityResponse(arn, userId, accountId string) responses.GetC } } -func MakeGetRoleResponse(arn, id string, tags ...responses.Tag) responses.GetRoleResponse { +func MakeGetRoleResponse(arn, id string, tags responses.Tags) responses.GetRoleResponse { if strings.Contains(id, ":") { panic("RoleId in GetRole response must not contain ':'") } @@ -51,7 +51,7 @@ func MakeGetRoleResponse(arn, id string, tags ...responses.Tag) responses.GetRol } } -func MakeGetUserResponse(arn, id string, tags ...responses.Tag) responses.GetUserResponse { +func MakeGetUserResponse(arn, id string, tags responses.Tags) responses.GetUserResponse { if strings.Contains(id, ":") { panic("UserId in GetUser resposne must not contain ':'") }