mirror of
https://github.com/versity/versitygw.git
synced 2026-09-25 01:14:22 +00:00
fix: infer UserName from the calling access key in the access-key APIs
`CreateAccessKey`, `UpdateAccessKey`, `DeleteAccessKey` and `ListAccessKeys` required an explicit `UserName`. Real IAM treats it as optional and resolves it from the access key signing the request, so an IAM user managing its own keys had to name itself. They now infer it, matching AWS: only an entirely absent parameter is inferred, while a present-but-empty one stays a `ValidationError`, and the inferred scope is strictly the caller's own user — another user's key id returns `NoSuchEntity` rather than being touched. A caller with no IAM user of its own gets IAM's own `Must specify userName when calling with non-User credentials` `ValidationError`, shared with `GetUser` as `iamerr.MustSpecifyUserName`. That covers assumed-role sessions and also the gateway's root credential, which is configured rather than stored as an IAM user and so owns no access keys the API could manage — real IAM manages the root account's own keys here, which has no equivalent in this gateway. The policy middleware resolves the same four actions through `callerOrNamedUserResource`, so the resource-level check targets the caller's own user ARN when UserName is omitted instead of falling back to no resource at all, which would have denied every request authorized by an own-ARN-scoped grant.
This commit is contained in:
@@ -170,11 +170,10 @@ func resourceForAction(ctx fiber.Ctx, store iamutil.IdentityStore, action string
|
||||
switch action {
|
||||
case "CreateUser":
|
||||
return newUserResource(ctx), nil
|
||||
case "GetUser":
|
||||
return getUserResource(ctx, store)
|
||||
case "DeleteUser", "UpdateUser", "CreateAccessKey", "UpdateAccessKey", "DeleteAccessKey",
|
||||
"ListAccessKeys", "PutUserPolicy", "GetUserPolicy", "DeleteUserPolicy", "ListUserPolicies",
|
||||
"TagUser", "UntagUser", "ListUserTags":
|
||||
case "GetUser", "CreateAccessKey", "UpdateAccessKey", "DeleteAccessKey", "ListAccessKeys":
|
||||
return callerOrNamedUserResource(ctx, store)
|
||||
case "DeleteUser", "UpdateUser", "PutUserPolicy", "GetUserPolicy", "DeleteUserPolicy",
|
||||
"ListUserPolicies", "TagUser", "UntagUser", "ListUserTags":
|
||||
return existingUserResource(ctx, store)
|
||||
case "GetAccessKeyLastUsed":
|
||||
return accessKeyOwnerResource(ctx, store)
|
||||
@@ -231,13 +230,14 @@ func existingUserResource(ctx fiber.Ctx, store iamutil.IdentityStore) (string, [
|
||||
return user.Arn, user.Tags
|
||||
}
|
||||
|
||||
// getUserResource resolves GetUser's target: the named user's stored Arn and
|
||||
// Tags, or — when UserName is omitted, matching the controller's (and real
|
||||
// IAM's) "look up the caller's own identity" behavior — the calling user's
|
||||
// own Arn and Tags. A session (assumed role) has no self IAM user to
|
||||
// resolve, so it falls back to ("", nil), the same lookup-failure fallback
|
||||
// used elsewhere.
|
||||
func getUserResource(ctx fiber.Ctx, store iamutil.IdentityStore) (string, []types.Tag) {
|
||||
// callerOrNamedUserResource resolves the target of the actions that accept
|
||||
// an omitted UserName — GetUser and the four access-key APIs: the named
|
||||
// user's stored Arn and Tags, or, when UserName is left out, the calling
|
||||
// user's own Arn and Tags, matching the controllers' (and real IAM's)
|
||||
// "operate on the caller's own identity" behavior. A caller with no IAM
|
||||
// user of its own (a session, or root) has nothing to resolve, so it falls
|
||||
// back to ("", nil), the same lookup-failure fallback used elsewhere.
|
||||
func callerOrNamedUserResource(ctx fiber.Ctx, store iamutil.IdentityStore) (string, []types.Tag) {
|
||||
userName, ok := iamutil.RequestParam(ctx, "UserName")
|
||||
if !ok || userName == "" {
|
||||
identity, _ := httpctx.ContextKeyCallerIdentity.Get(ctx).(types.Identity)
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/versity/versitygw/debuglogger"
|
||||
"github.com/versity/versitygw/iamapi/iamerr"
|
||||
"github.com/versity/versitygw/iamapi/types"
|
||||
"github.com/versity/versitygw/internal/httpctx"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -114,6 +115,33 @@ func GetUserName(ctx fiber.Ctx, operation string, maxLen int, missingErr error)
|
||||
return userName, nil
|
||||
}
|
||||
|
||||
// GetUserNameOrCaller resolves the UserName request parameter like
|
||||
// GetUserName, except that an omitted parameter resolves to the calling
|
||||
// user's own name instead of being an error — matching real IAM, which
|
||||
// infers the user from the access key signing the request when UserName is
|
||||
// left out.
|
||||
//
|
||||
// Only an entirely absent parameter is inferred. A UserName that is present
|
||||
// but empty stays a ValidateName rejection, as on real IAM, so a client that
|
||||
// sends the parameter with no value is told the value is invalid rather than
|
||||
// silently acting on a different user than it named.
|
||||
func GetUserNameOrCaller(ctx fiber.Ctx, operation string, maxLen int) (string, error) {
|
||||
userName, ok := RequestParam(ctx, "UserName")
|
||||
if !ok {
|
||||
identity, _ := httpctx.ContextKeyCallerIdentity.Get(ctx).(types.Identity)
|
||||
if identity.User == nil {
|
||||
debuglogger.Logf("%s omitted UserName with credentials that have no IAM user", operation)
|
||||
return "", iamerr.MustSpecifyUserName()
|
||||
}
|
||||
return identity.User.UserName, nil
|
||||
}
|
||||
if err := ValidateName("userName", userName, maxLen); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return userName, nil
|
||||
}
|
||||
|
||||
// GetRoleName resolves the RoleName request parameter and validates it
|
||||
// against maxLen, returning missingErr if the parameter is absent or empty.
|
||||
func GetRoleName(ctx fiber.Ctx, operation string, maxLen int, missingErr error) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user