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 ':'")
}