acl: Fix tag parsing for IAM users and roles in IAM auth method (#12797)

* acl: Fix tag parsing on IAM users and roles in IAM auth method

* Add changelog
This commit is contained in:
Paul Glass 2022-04-14 16:45:35 -05:00 committed by GitHub
parent e62745c82c
commit 1a938e4a49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 159 additions and 15 deletions

3
.changelog/12797.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
acl: Fix parsing of IAM user and role tags in IAM auth method
```

View File

@ -7,6 +7,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials"
"github.com/hashicorp/consul/internal/iamauth/iamauthtest" "github.com/hashicorp/consul/internal/iamauth/iamauthtest"
"github.com/hashicorp/consul/internal/iamauth/responses"
"github.com/hashicorp/consul/internal/iamauth/responsestest" "github.com/hashicorp/consul/internal/iamauth/responsestest"
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -18,11 +19,11 @@ func TestValidateLogin(t *testing.T) {
var ( var (
serverForRoleMismatchedIds = &iamauthtest.Server{ serverForRoleMismatchedIds = &iamauthtest.Server{
GetCallerIdentityResponse: f.ServerForRole.GetCallerIdentityResponse, GetCallerIdentityResponse: f.ServerForRole.GetCallerIdentityResponse,
GetRoleResponse: responsestest.MakeGetRoleResponse(f.RoleARN, "AAAAsomenonmatchingid"), GetRoleResponse: responsestest.MakeGetRoleResponse(f.RoleARN, "AAAAsomenonmatchingid", responses.Tags{}),
} }
serverForUserMismatchedIds = &iamauthtest.Server{ serverForUserMismatchedIds = &iamauthtest.Server{
GetCallerIdentityResponse: f.ServerForUser.GetCallerIdentityResponse, GetCallerIdentityResponse: f.ServerForUser.GetCallerIdentityResponse,
GetUserResponse: responsestest.MakeGetUserResponse(f.UserARN, "AAAAsomenonmatchingid"), GetUserResponse: responsestest.MakeGetUserResponse(f.UserARN, "AAAAsomenonmatchingid", responses.Tags{}),
} }
) )

View File

@ -133,7 +133,7 @@ func MakeFixture() Fixture {
f.AssumedRoleARN, f.EntityIDWithSession, f.AccountID, f.AssumedRoleARN, f.EntityIDWithSession, f.AccountID,
), ),
GetRoleResponse: responsestest.MakeGetRoleResponse( 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, f.UserARN, f.EntityID, f.AccountID,
), ),
GetUserResponse: responsestest.MakeGetUserResponse( 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) } func (f *Fixture) UserTagValues() []string { return values(f.UserTags) }
// toTags converts the map to a slice of responses.Tag // toTags converts the map to a slice of responses.Tag
func toTags(tags map[string]string) []responses.Tag { func toTags(tags map[string]string) responses.Tags {
result := []responses.Tag{} members := []responses.TagMember{}
for k, v := range tags { for k, v := range tags {
result = append(result, responses.Tag{ members = append(members, responses.TagMember{
Key: k, Key: k,
Value: v, Value: v,
}) })
} }
return result return responses.Tags{Members: members}
} }

View File

@ -45,7 +45,7 @@ type Role struct {
Path string `xml:"Path"` Path string `xml:"Path"`
RoleId string `xml:"RoleId"` RoleId string `xml:"RoleId"`
RoleName string `xml:"RoleName"` RoleName string `xml:"RoleName"`
Tags []Tag `xml:"Tags"` Tags Tags `xml:"Tags"`
} }
func (r *Role) EntityPath() string { return r.Path } func (r *Role) EntityPath() string { return r.Path }
@ -69,7 +69,7 @@ type User struct {
Path string `xml:"Path"` Path string `xml:"Path"`
UserId string `xml:"UserId"` UserId string `xml:"UserId"`
UserName string `xml:"UserName"` UserName string `xml:"UserName"`
Tags []Tag `xml:"Tags"` Tags Tags `xml:"Tags"`
} }
func (u *User) EntityPath() string { return u.Path } 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) EntityId() string { return u.UserId }
func (u *User) EntityTags() map[string]string { return tagsToMap(u.Tags) } 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"` Key string `xml:"Key"`
Value string `xml:"Value"` Value string `xml:"Value"`
} }
func tagsToMap(tags []Tag) map[string]string { func tagsToMap(tags Tags) map[string]string {
result := map[string]string{} result := map[string]string{}
for _, tag := range tags { for _, tag := range tags.Members {
result[tag.Key] = tag.Value result[tag.Key] = tag.Value
} }
return result return result

View File

@ -1,6 +1,7 @@
package responses package responses
import ( import (
"encoding/xml"
"testing" "testing"
"github.com/stretchr/testify/require" "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 = `<GetUserResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<GetUserResult>
<User>
<Path>/</Path>
<Arn>arn:aws:iam::000000000000:user/my-user</Arn>
<UserName>my-user</UserName>
<UserId>AIDAexampleuserid</UserId>
<CreateDate>2021-01-01T00:01:02Z</CreateDate>
<Tags>
<member>
<Value>some-value</Value>
<Key>some-tag</Key>
</member>
<member>
<Value>another-value</Value>
<Key>another-tag</Key>
</member>
<member>
<Value>third-value</Value>
<Key>third-tag</Key>
</member>
</Tags>
</User>
</GetUserResult>
<ResponseMetadata>
<RequestId>11815b96-cb16-4d33-b2cf-0042fa4db4cd</RequestId>
</ResponseMetadata>
</GetUserResponse>`
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 = `<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<GetRoleResult>
<Role>
<Path>/</Path>
<AssumeRolePolicyDocument>some-json-document-that-we-ignore</AssumeRolePolicyDocument>
<MaxSessionDuration>43200</MaxSessionDuration>
<RoleId>AROAsomeuniqueid</RoleId>
<RoleLastUsed>
<LastUsedDate>2022-01-01T01:02:03Z</LastUsedDate>
<Region>us-east-1</Region>
</RoleLastUsed>
<RoleName>my-role</RoleName>
<Arn>arn:aws:iam::000000000000:role/my-role</Arn>
<CreateDate>2020-01-01T00:00:01Z</CreateDate>
<Tags>
<member>
<Value>some-value</Value>
<Key>some-key</Key>
</member>
<member>
<Value>another-value</Value>
<Key>another-key</Key>
</member>
<member>
<Value>a-third-value</Value>
<Key>third-key</Key>
</member>
</Tags>
</Role>
</GetRoleResult>
<ResponseMetadata>
<RequestId>a9866067-c0e5-4b5e-86ba-429c1151e2fb</RequestId>
</ResponseMetadata>
</GetRoleResponse>`
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"},
},
}
)

View File

@ -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, ":") { if strings.Contains(id, ":") {
panic("RoleId in GetRole response must not contain ':'") 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, ":") { if strings.Contains(id, ":") {
panic("UserId in GetUser resposne must not contain ':'") panic("UserId in GetUser resposne must not contain ':'")
} }