mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-22 05:54:24 +02:00
iam: take the implicit user name from the verified identity (#11903)
* iam: take the implicit user name from the verified identity For self-service actions sent without UserName, the implicit user was resolved by re-parsing the raw Authorization header for a SigV4 credential and looking that access key up. The header is not what authentication verified: a request can be authenticated by one mechanism while carrying a header that names another user's access key, and the action then ran against that other user. The auth middleware already records the verified identity in the request context, so use it, on both the standalone iam server and the embedded IAM surface. When no verified identity is present the name is left unset rather than guessed. * iam: test implicit user name resolution Cover a request authenticated by presigned credentials that also carries a junk SigV4 header naming another user: the self-service actions must run as the verified signer. Update the unit tests for the context-based lookup. * iam: resolve the implicit user for standalone GetUser too The embedded surface fills UserName for GetUser; the standalone server skipped it, so an implicit GetUser on `weed iam` answered NoSuchEntity. Same verified-identity source as the other self-service actions. * iam: cover all implicit-username actions in the regression test
This commit is contained in:
1 parent
593cfee85b
commit
bb1ba8b247
5 files changed
+123
-149
No files matched your search
@@ -2594,53 +2594,19 @@ func (e *EmbeddedIamApi) ListGroupPolicies(s3cfg *iam_pb.S3ApiConfiguration, val
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// handleImplicitUsername adds username who signs the request to values if 'username' is not specified.
|
||||
// According to AWS documentation: "If you do not specify a user name, IAM determines the user name
|
||||
// implicitly based on the Amazon Web Services access key ID signing the request."
|
||||
// This function extracts the AccessKeyId from the SigV4 credential and looks up the corresponding
|
||||
// identity name in the credential store.
|
||||
// handleImplicitUsername adds the name of the authenticated caller to values
|
||||
// when 'UserName' is not specified, per AWS: "If you do not specify a user name,
|
||||
// IAM determines the user name implicitly based on the Amazon Web Services
|
||||
// access key ID signing the request." The name comes from the identity the
|
||||
// authentication middleware verified, which the request context carries; the
|
||||
// Authorization header itself is attacker-controlled and is never re-parsed.
|
||||
func (e *EmbeddedIamApi) handleImplicitUsername(r *http.Request, values url.Values) {
|
||||
if len(r.Header["Authorization"]) == 0 || values.Get("UserName") != "" {
|
||||
if values.Get("UserName") != "" {
|
||||
return
|
||||
}
|
||||
// Log presence of auth header without exposing sensitive signature material
|
||||
glog.V(4).Infof("Authorization header present, extracting access key")
|
||||
// Parse AWS SigV4 Authorization header format:
|
||||
// "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/iam/aws4_request, ..."
|
||||
s := strings.Split(r.Header["Authorization"][0], "Credential=")
|
||||
if len(s) < 2 {
|
||||
return
|
||||
if name := s3_constants.GetIdentityNameFromContext(r); name != "" {
|
||||
values.Set("UserName", name)
|
||||
}
|
||||
s = strings.Split(s[1], ",")
|
||||
if len(s) < 1 {
|
||||
return
|
||||
}
|
||||
s = strings.Split(s[0], "/")
|
||||
if len(s) < 1 {
|
||||
return
|
||||
}
|
||||
// s[0] is the AccessKeyId
|
||||
accessKeyId := s[0]
|
||||
if accessKeyId == "" {
|
||||
return
|
||||
}
|
||||
// Nil-guard: ensure iam is initialized before lookup
|
||||
if e.iam == nil {
|
||||
glog.V(4).Infof("IAM not initialized, cannot look up access key")
|
||||
return
|
||||
}
|
||||
// Look up the identity by access key to get the username
|
||||
identity, _, found := e.iam.LookupByAccessKey(accessKeyId)
|
||||
if !found {
|
||||
// Mask access key in logs - show only first 4 chars
|
||||
maskedKey := accessKeyId
|
||||
if len(accessKeyId) > 4 {
|
||||
maskedKey = accessKeyId[:4] + "***"
|
||||
}
|
||||
glog.V(4).Infof("Access key %s not found in credential store", maskedKey)
|
||||
return
|
||||
}
|
||||
values.Set("UserName", identity.Name)
|
||||
}
|
||||
|
||||
// iamSelfServiceActions are actions that users can perform on their own resources without admin rights.
|
||||
|
||||
@@ -372,13 +372,13 @@ func TestEmbeddedIamGetUserImplicitUsername(t *testing.T) {
|
||||
t.Fatalf("failed to load iam config: %v", err)
|
||||
}
|
||||
|
||||
// GetUser request with NO UserName, but signed by accessKey.
|
||||
// GetUser request with NO UserName, carrying the identity authentication
|
||||
// verified for accessKey, the way the middleware records it.
|
||||
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")
|
||||
req = req.WithContext(SetIdentityNameInContext(req.Context(), "TestUser"))
|
||||
|
||||
out := iamGetUserResponse{}
|
||||
rr, err := executeEmbeddedIamRequest(api, req, &out)
|
||||
@@ -1578,29 +1578,15 @@ func TestEmbeddedIamDeleteAccessKey(t *testing.T) {
|
||||
assert.Len(t, api.mockConfig.Identities[0].Credentials, 0)
|
||||
}
|
||||
|
||||
// TestEmbeddedIamHandleImplicitUsername tests implicit username extraction from authorization header
|
||||
// TestEmbeddedIamHandleImplicitUsername checks that the implicit user name is
|
||||
// taken from the authenticated identity in the request context, never from
|
||||
// re-parsing the Authorization header.
|
||||
func TestEmbeddedIamHandleImplicitUsername(t *testing.T) {
|
||||
// Create IAM with test credentials - the handleImplicitUsername function now looks
|
||||
// up the username from the credential store based on AccessKeyId
|
||||
// Note: Using obviously fake access keys to avoid secret scanner false positives
|
||||
iam := &IdentityAccessManagement{}
|
||||
testConfig := &iam_pb.S3ApiConfiguration{
|
||||
Identities: []*iam_pb.Identity{
|
||||
{
|
||||
Name: "testuser1",
|
||||
Credentials: []*iam_pb.Credential{
|
||||
{AccessKey: UserAccessKeyPrefix + "TESTFAKEKEY000001", SecretKey: "testsecretfake"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
err := iam.LoadS3ApiConfigurationFromBytes(mustMarshalJSON(testConfig))
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load test config: %v", err)
|
||||
}
|
||||
embeddedApi := &EmbeddedIamApi{}
|
||||
|
||||
embeddedApi := &EmbeddedIamApi{
|
||||
iam: iam,
|
||||
withIdentity := func(name string) *http.Request {
|
||||
r := &http.Request{Header: http.Header{}}
|
||||
return r.WithContext(SetIdentityNameInContext(r.Context(), name))
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
@@ -1608,14 +1594,14 @@ func TestEmbeddedIamHandleImplicitUsername(t *testing.T) {
|
||||
values url.Values
|
||||
userName string
|
||||
}{
|
||||
// No authorization header - should not set username
|
||||
// No authenticated identity - should not set username
|
||||
{&http.Request{}, url.Values{}, ""},
|
||||
// Valid auth header with known access key - should look up and find "testuser1"
|
||||
{&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + UserAccessKeyPrefix + "TESTFAKEKEY000001/20220420/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=fakesignature0123456789abcdef"}}}, url.Values{}, "testuser1"},
|
||||
// Malformed auth header (no Credential=) - should not set username
|
||||
{&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 =" + UserAccessKeyPrefix + "TESTFAKEKEY000001/20220420/test1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=fakesignature0123456789abcdef"}}}, url.Values{}, ""},
|
||||
// Unknown access key - should not set username
|
||||
{&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + UserAccessKeyPrefix + "TESTUNKNOWN000000/20220420/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=fakesignature0123456789abcdef"}}}, url.Values{}, ""},
|
||||
// Authenticated identity in context - sets its name
|
||||
{withIdentity("testuser1"), url.Values{}, "testuser1"},
|
||||
// A client-controlled Authorization header naming another user is ignored
|
||||
{&http.Request{Header: http.Header{"Authorization": []string{"AWS4-HMAC-SHA256 Credential=" + UserAccessKeyPrefix + "TESTFAKEKEY000001/20220420/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=fakesignature0123456789abcdef"}}}, url.Values{}, ""},
|
||||
// An explicit UserName is left alone
|
||||
{withIdentity("testuser1"), url.Values{"UserName": {"other"}}, "other"},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
|
||||
Reference in new issue
Block a user