From 30f49013e1a30841167d0011e0d127d53b38bc6f Mon Sep 17 00:00:00 2001 From: Elias Paitz Date: Sat, 30 May 2026 19:51:03 +0200 Subject: [PATCH] perf(s3.iam.GetUser): Make the API default to the request username if not specified (#9746) * perf(s3.iam.GetUser): Make the API default to the request username if not specified This makes the Embedded S3 IAM API align with the documented behavior of the AWS IAM API as per AWS Docs: https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetUser.html BREAKING CHANGE: This changes the default behavior of the Embedded IAM API to use the username of the user holding the accesskey used to make the request in the GetUsername request handler. * test: cover GetUser implicit username default --------- Co-authored-by: Chris Lu --- weed/s3api/s3api_embedded_iam.go | 2 +- weed/s3api/s3api_embedded_iam_test.go | 40 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/weed/s3api/s3api_embedded_iam.go b/weed/s3api/s3api_embedded_iam.go index d2e5da180..f7808e50f 100644 --- a/weed/s3api/s3api_embedded_iam.go +++ b/weed/s3api/s3api_embedded_iam.go @@ -2854,7 +2854,7 @@ func (e *EmbeddedIamApi) DoActions(w http.ResponseWriter, r *http.Request) { // Handle implicit username for HTTP requests switch r.Form.Get("Action") { - case "ListAccessKeys", "CreateAccessKey", "DeleteAccessKey", "UpdateAccessKey", "ListUserPolicies": + case "ListAccessKeys", "CreateAccessKey", "DeleteAccessKey", "UpdateAccessKey", "ListUserPolicies", "GetUser": e.handleImplicitUsername(r, values) case "CreateServiceAccount": createdBy := s3_constants.GetIdentityNameFromContext(r) diff --git a/weed/s3api/s3api_embedded_iam_test.go b/weed/s3api/s3api_embedded_iam_test.go index c44ab5aa3..459d0ec4b 100644 --- a/weed/s3api/s3api_embedded_iam_test.go +++ b/weed/s3api/s3api_embedded_iam_test.go @@ -339,6 +339,46 @@ func TestEmbeddedIamGetUser(t *testing.T) { assert.Equal(t, "TestUser", *out.GetUserResult.User.UserName) } +// TestEmbeddedIamGetUserImplicitUsername verifies GetUser without a UserName defaults +// to the user that signed the request rather than returning NoSuchEntity for an empty +// username, matching documented AWS IAM behavior. +func TestEmbeddedIamGetUserImplicitUsername(t *testing.T) { + const accessKey = UserAccessKeyPrefix + "TESTFAKEKEY000001" + + api := NewEmbeddedIamApiForTest() + api.mockConfig = &iam_pb.S3ApiConfiguration{ + Identities: []*iam_pb.Identity{ + { + Name: "TestUser", + Credentials: []*iam_pb.Credential{ + {AccessKey: accessKey, SecretKey: "testsecretfake"}, + }, + }, + }, + } + // handleImplicitUsername resolves the username via iam.LookupByAccessKey, + // so the iam state must know about the credential. + if err := api.iam.LoadS3ApiConfigurationFromBytes(mustMarshalJSON(api.mockConfig)); err != nil { + t.Fatalf("failed to load iam config: %v", err) + } + + // GetUser request with NO UserName, but signed by accessKey. + form := url.Values{} + form.Set("Action", "GetUser") + req, _ := http.NewRequest(http.MethodPost, "/", strings.NewReader(form.Encode())) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Authorization", "AWS4-HMAC-SHA256 Credential="+accessKey+ + "/20220420/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=fakesig") + + out := iamGetUserResponse{} + rr, err := executeEmbeddedIamRequest(api, req, &out) + require.NoError(t, err) + require.Equal(t, http.StatusOK, rr.Code, "body=%s", rr.Body.String()) + + require.NotNil(t, out.GetUserResult.User.UserName) + assert.Equal(t, "TestUser", *out.GetUserResult.User.UserName) +} + // TestEmbeddedIamCreatePolicy tests creating a policy via the embedded IAM API func TestEmbeddedIamCreatePolicy(t *testing.T) { api := NewEmbeddedIamApiForTest()