admin: surface user inline policies in object store user details (#10013)

GetObjectStoreUserDetails only returned identity.PolicyNames (attached
managed policies) and omitted per-user inline policies. Inline policies are
stored separately from the identity record and are authoritative at S3
enforcement time (they take precedence over the legacy Actions list), so an
operator could not see what actually governed a user's access via the admin
API/UI.

Include inline policy names (via credentialManager.ListUserInlinePolicies) in
the returned PolicyNames. Adds a unit test using the memory credential store.
This commit is contained in:
Minsoo Kim
2026-06-18 22:20:56 -07:00
committed by GitHub
parent 0a45c4d097
commit 638f6ff433
2 changed files with 74 additions and 0 deletions
@@ -0,0 +1,62 @@
package dash
import (
"context"
"testing"
"github.com/seaweedfs/seaweedfs/weed/credential"
_ "github.com/seaweedfs/seaweedfs/weed/credential/memory" // register the memory credential store
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
)
// TestGetObjectStoreUserDetailsIncludesInlinePolicies verifies that inline user
// policies — which are stored separately from the identity record and are
// authoritative at S3 enforcement time (they take precedence over the legacy
// Actions list) — are surfaced in the admin user details. They were previously
// invisible in the admin API/UI, so an operator could not tell what actually
// governed a user's access.
func TestGetObjectStoreUserDetailsIncludesInlinePolicies(t *testing.T) {
cm, err := credential.NewCredentialManagerWithDefaults(credential.StoreTypeMemory)
if err != nil {
t.Fatalf("failed to init credential manager: %v", err)
}
ctx := context.Background()
const username = "svc-app"
if err := cm.CreateUser(ctx, &iam_pb.Identity{Name: username}); err != nil {
t.Fatalf("failed to create user: %v", err)
}
const inlinePolicyName = "svc-app-policy"
doc := policy_engine.PolicyDocument{
Version: "2012-10-17",
Statement: []policy_engine.PolicyStatement{
{
Effect: "Allow",
Action: policy_engine.NewStringOrStringSlice("s3:GetObject"),
Resource: policy_engine.NewStringOrStringSlicePtr("arn:aws:s3:::ml-artifacts/*"),
},
},
}
if err := cm.PutUserInlinePolicy(ctx, username, inlinePolicyName, doc); err != nil {
t.Fatalf("failed to put inline policy: %v", err)
}
s := &AdminServer{credentialManager: cm}
details, err := s.GetObjectStoreUserDetails(username)
if err != nil {
t.Fatalf("GetObjectStoreUserDetails failed: %v", err)
}
found := false
for _, p := range details.PolicyNames {
if p == inlinePolicyName {
found = true
break
}
}
if !found {
t.Errorf("expected inline policy %q in details.PolicyNames, got %v", inlinePolicyName, details.PolicyNames)
}
}
+12
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/seaweedfs/seaweedfs/weed/credential"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/iam"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
)
@@ -190,6 +191,17 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails,
PolicyNames: identity.PolicyNames,
}
// Inline user policies are stored separately from the identity record and
// are authoritative at enforcement time (they take precedence over the
// legacy Actions list). Surface their names alongside attached policies so
// the admin reflects the user's actual permissions rather than only the
// (potentially overridden) Actions.
if inlinePolicyNames, err := s.credentialManager.ListUserInlinePolicies(ctx, username); err != nil {
glog.Warningf("failed to list inline policies for user %s: %v", username, err)
} else if len(inlinePolicyNames) > 0 {
details.PolicyNames = append(details.PolicyNames, inlinePolicyNames...)
}
// Set email from account if available
if identity.Account != nil {
details.Email = identity.Account.EmailAddress