fix(iam): return a valid user ARN from CreateUser and GetUser (#9794)
* fix(iam): return a valid user ARN from CreateUser and GetUser The terraform aws provider 6.41 reads a user back after creating it and blocks until GetUser returns a value that passes arn.IsARN. We only set UserName, so the ARN was empty and apply hung until the 2m timeout. Populate Arn (and Path) via a shared iam.NewUser helper in both the embedded and standalone IAM handlers. * fix(iam): use the userName parameter directly in NewUser Drop the redundant local copy; the value parameter is already function-local. * fix(iam): return full user objects with ARNs from GetGroup GetGroup listed members with only UserName set. Build them via the shared NewUser helper so group members carry a valid Arn and Path like the other user responses, in both the embedded and standalone IAM handlers.
This commit is contained in:
parent
f711868fb6
commit
4e5839ce82
@ -9,6 +9,10 @@ const (
|
||||
// Policy document version
|
||||
const PolicyDocumentVersion = "2012-10-17"
|
||||
|
||||
// DefaultAccountID is the placeholder AWS account id used in generated ARNs,
|
||||
// matching the value used throughout the S3 API.
|
||||
const DefaultAccountID = "000000000000"
|
||||
|
||||
// Error message templates
|
||||
const UserDoesNotExist = "the user with name %s cannot be found."
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
)
|
||||
|
||||
@ -17,6 +18,21 @@ func Hash(s *string) string {
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// UserArn builds an AWS-compatible IAM user ARN.
|
||||
func UserArn(userName string) string {
|
||||
return fmt.Sprintf("arn:aws:iam::%s:user/%s", DefaultAccountID, userName)
|
||||
}
|
||||
|
||||
// NewUser builds an iam.User for IAM API responses. The Arn must be a real ARN:
|
||||
// the terraform aws provider (>= 6.41) reads a user back after creating it and
|
||||
// blocks until GetUser returns a value that passes arn.IsARN, so an empty Arn
|
||||
// leaves apply hanging until it times out.
|
||||
func NewUser(userName string) iam.User {
|
||||
arn := UserArn(userName)
|
||||
path := "/"
|
||||
return iam.User{UserName: &userName, Arn: &arn, Path: &path}
|
||||
}
|
||||
|
||||
// GenerateRandomString generates a cryptographically secure random string.
|
||||
// Uses crypto/rand for security-sensitive credential generation.
|
||||
func GenerateRandomString(length int, charset string) (string, error) {
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
iamlib "github.com/seaweedfs/seaweedfs/weed/iam"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
||||
@ -105,8 +106,8 @@ func (iama *IamApiServer) GetGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.
|
||||
if g.Name == groupName {
|
||||
resp.GetGroupResult.Group.GroupName = &g.Name
|
||||
for _, member := range g.Members {
|
||||
m := member
|
||||
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &iam.User{UserName: &m})
|
||||
user := iamlib.NewUser(member)
|
||||
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &user)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -188,7 +188,8 @@ func validateAccessKeyStatus(status string) error {
|
||||
func (iama *IamApiServer) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp *ListUsersResponse) {
|
||||
resp = &ListUsersResponse{}
|
||||
for _, ident := range s3cfg.Identities {
|
||||
resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
|
||||
user := iamlib.NewUser(ident.Name)
|
||||
resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &user)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
@ -218,7 +219,7 @@ func (iama *IamApiServer) ListAccessKeys(s3cfg *iam_pb.S3ApiConfiguration, value
|
||||
func (iama *IamApiServer) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) (resp *CreateUserResponse) {
|
||||
resp = &CreateUserResponse{}
|
||||
userName := values.Get("UserName")
|
||||
resp.CreateUserResult.User.UserName = &userName
|
||||
resp.CreateUserResult.User = iamlib.NewUser(userName)
|
||||
s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName})
|
||||
return resp
|
||||
}
|
||||
@ -253,7 +254,7 @@ func (iama *IamApiServer) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName str
|
||||
resp = &GetUserResponse{}
|
||||
for _, ident := range s3cfg.Identities {
|
||||
if userName == ident.Name {
|
||||
resp.GetUserResult.User = iam.User{UserName: &ident.Name}
|
||||
resp.GetUserResult.User = iamlib.NewUser(ident.Name)
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
@ -631,6 +633,23 @@ func TestListAttachedUserPolicies(t *testing.T) {
|
||||
assert.Equal(t, iam.ErrCodeNoSuchEntityException, iamErr.Code)
|
||||
}
|
||||
|
||||
// TestUserArnIsValid is a regression test for issue #9786: the terraform aws
|
||||
// provider (>= 6.41) reads a user back after creating it and blocks until
|
||||
// GetUser returns a value that passes arn.IsARN.
|
||||
func TestUserArnIsValid(t *testing.T) {
|
||||
iama := newTestIamApiServer(Policies{})
|
||||
s3cfg := &iam_pb.S3ApiConfiguration{}
|
||||
|
||||
createResp := iama.CreateUser(s3cfg, url.Values{"UserName": []string{"alice"}})
|
||||
assert.True(t, arn.IsARN(aws.StringValue(createResp.CreateUserResult.User.Arn)),
|
||||
"CreateUser must return a valid ARN, got %q", aws.StringValue(createResp.CreateUserResult.User.Arn))
|
||||
|
||||
getResp, iamErr := iama.GetUser(s3cfg, "alice")
|
||||
assert.Nil(t, iamErr)
|
||||
assert.True(t, arn.IsARN(aws.StringValue(getResp.GetUserResult.User.Arn)),
|
||||
"GetUser must return a valid ARN, got %q", aws.StringValue(getResp.GetUserResult.User.Arn))
|
||||
}
|
||||
|
||||
func TestCreateAccessKeyWithCallerSuppliedKeys(t *testing.T) {
|
||||
iama := newTestIamApiServer(Policies{})
|
||||
s3cfg := &iam_pb.S3ApiConfiguration{
|
||||
|
||||
@ -267,7 +267,8 @@ func (e *EmbeddedIamApi) ReloadConfiguration() error {
|
||||
func (e *EmbeddedIamApi) ListUsers(s3cfg *iam_pb.S3ApiConfiguration, values url.Values) *iamListUsersResponse {
|
||||
resp := &iamListUsersResponse{}
|
||||
for _, ident := range s3cfg.Identities {
|
||||
resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &iam.User{UserName: &ident.Name})
|
||||
user := iamlib.NewUser(ident.Name)
|
||||
resp.ListUsersResult.Users = append(resp.ListUsersResult.Users, &user)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
@ -315,7 +316,7 @@ func (e *EmbeddedIamApi) CreateUser(s3cfg *iam_pb.S3ApiConfiguration, values url
|
||||
}
|
||||
}
|
||||
|
||||
resp.CreateUserResult.User.UserName = &userName
|
||||
resp.CreateUserResult.User = iamlib.NewUser(userName)
|
||||
s3cfg.Identities = append(s3cfg.Identities, &iam_pb.Identity{Name: userName}) // Disabled defaults to false (enabled)
|
||||
return resp, nil
|
||||
}
|
||||
@ -355,7 +356,7 @@ func (e *EmbeddedIamApi) GetUser(s3cfg *iam_pb.S3ApiConfiguration, userName stri
|
||||
resp := &iamGetUserResponse{}
|
||||
for _, ident := range s3cfg.Identities {
|
||||
if userName == ident.Name {
|
||||
resp.GetUserResult.User = iam.User{UserName: &ident.Name}
|
||||
resp.GetUserResult.User = iamlib.NewUser(ident.Name)
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
@ -2096,8 +2097,8 @@ func (e *EmbeddedIamApi) GetGroup(s3cfg *iam_pb.S3ApiConfiguration, values url.V
|
||||
if g.Name == groupName {
|
||||
resp.GetGroupResult.Group.GroupName = &g.Name
|
||||
for _, member := range g.Members {
|
||||
memberName := member
|
||||
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &iam.User{UserName: &memberName})
|
||||
user := iamlib.NewUser(member)
|
||||
resp.GetGroupResult.Users = append(resp.GetGroupResult.Users, &user)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/arn"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/iam"
|
||||
"github.com/gorilla/mux"
|
||||
@ -189,6 +190,10 @@ func TestEmbeddedIamCreateUser(t *testing.T) {
|
||||
assert.NotNil(t, out.CreateUserResult.User.UserName)
|
||||
assert.Equal(t, "TestUser", *out.CreateUserResult.User.UserName)
|
||||
|
||||
// Issue #9786: terraform aws provider >= 6.41 blocks on a valid ARN.
|
||||
assert.True(t, arn.IsARN(aws.StringValue(out.CreateUserResult.User.Arn)),
|
||||
"CreateUser must return a valid ARN, got %q", aws.StringValue(out.CreateUserResult.User.Arn))
|
||||
|
||||
// Verify user was persisted in config
|
||||
assert.Len(t, api.mockConfig.Identities, 1)
|
||||
assert.Equal(t, "TestUser", api.mockConfig.Identities[0].Name)
|
||||
@ -337,6 +342,11 @@ func TestEmbeddedIamGetUser(t *testing.T) {
|
||||
// Verify response contains correct username
|
||||
assert.NotNil(t, out.GetUserResult.User.UserName)
|
||||
assert.Equal(t, "TestUser", *out.GetUserResult.User.UserName)
|
||||
|
||||
// Issue #9786: the terraform aws provider reads the user back after creating
|
||||
// it and waits until GetUser returns a value that passes arn.IsARN.
|
||||
assert.True(t, arn.IsARN(aws.StringValue(out.GetUserResult.User.Arn)),
|
||||
"GetUser must return a valid ARN, got %q", aws.StringValue(out.GetUserResult.User.Arn))
|
||||
}
|
||||
|
||||
// TestEmbeddedIamGetUserImplicitUsername verifies GetUser without a UserName defaults
|
||||
|
||||
Loading…
Reference in New Issue
Block a user