mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-06-13 23:36:45 +03:00
PR #9442 made the filer refuse to register the IAM gRPC service unless jwt.filer_signing.key was set in security.toml, which broke the admin UI Users/Groups/Policies pages for every deployment that ships without a security.toml — weed mini, plain Helm, vanilla weed filer. The Users tab returns Unimplemented and the page is unusable. Issues #9504, #9505 and #9509 all trace to this gap. The rest of the filer's gRPC surface is unauthenticated by default; treat IAM the same way. The service now always registers, and the auth gate is a no-op when no signing key is configured. When the key is set, every RPC still requires an admin-signed Bearer token, matching the post-#9442 behaviour. Operators who expose the filer gRPC port beyond a trusted network should set the key on both filer and admin. The admin client (IamGrpcStore.withIamClient) already skips attaching the authorization metadata when its key is empty, so no changes there.
This commit is contained in:
@@ -194,12 +194,10 @@ func NewAdminServer(masters string, templateFS http.FileSystem, dataDir string,
|
||||
glog.V(0).Infof("Credential store %s does not support filer address function", store.GetName())
|
||||
}
|
||||
|
||||
// The filer's IAM gRPC service rejects every RPC without an
|
||||
// admin-signed Bearer token (PR #9442). Mirror the filer's
|
||||
// jwt.filer_signing.key here so the admin UI's Users/Groups
|
||||
// pages can talk to it; without this they fail with either
|
||||
// Unimplemented (filer refuses to register the service) or
|
||||
// Unauthenticated (missing authorization metadata).
|
||||
// Mirror the filer's jwt.filer_signing.key so the admin UI's
|
||||
// Users/Groups pages can present a valid Bearer token when the
|
||||
// filer enforces IAM gRPC auth. When the key is empty, both
|
||||
// sides run unauthenticated and no token is sent.
|
||||
if signer, ok := store.(interface {
|
||||
SetAdminSigning(security.SigningKey, int)
|
||||
}); ok {
|
||||
@@ -207,9 +205,7 @@ func NewAdminServer(masters string, templateFS http.FileSystem, dataDir string,
|
||||
key := security.SigningKey(viper.GetString("jwt.filer_signing.key"))
|
||||
expires := viper.GetInt("jwt.filer_signing.expires_after_seconds")
|
||||
signer.SetAdminSigning(key, expires)
|
||||
if len(key) == 0 {
|
||||
glog.Warningf("jwt.filer_signing.key is empty in security.toml; the admin UI Users/Groups pages will fail until this is set on both the filer and the admin server")
|
||||
} else {
|
||||
if len(key) > 0 {
|
||||
glog.V(0).Infof("Credential store configured with admin Bearer token signing")
|
||||
}
|
||||
}
|
||||
|
||||
+9
-10
@@ -431,20 +431,19 @@ func (fo *FilerOptions) startFiler() {
|
||||
grpcS := pb.NewGrpcServer(security.LoadServerTLS(util.GetViper(), "grpc.filer"))
|
||||
filer_pb.RegisterSeaweedFilerServer(grpcS, fs)
|
||||
|
||||
// Register IAM gRPC service only when both a credential manager and an
|
||||
// admin signing key are configured. The IAM RPCs can create users and
|
||||
// mint access keys; mounting them on an unauthenticated listener would
|
||||
// hand any caller that can reach the gRPC port S3-admin equivalent power.
|
||||
// Operators who relied on the unauthenticated path must now set
|
||||
// jwt.filer_signing.key in security.toml and attach a Bearer token signed
|
||||
// with that key on every IAM call.
|
||||
// Register the IAM gRPC service. Auth is opt-in: when
|
||||
// jwt.filer_signing.key is configured the service requires a Bearer token
|
||||
// signed with that key; otherwise it runs unauthenticated, matching the
|
||||
// rest of the filer's gRPC surface. Operators who expose the filer gRPC
|
||||
// port beyond a trusted network should set jwt.filer_signing.key on both
|
||||
// the filer and the admin server.
|
||||
if credentialManager != nil {
|
||||
adminSigningKey := security.SigningKey(util.GetViper().GetString("jwt.filer_signing.key"))
|
||||
iamGrpcServer := weed_server.NewIamGrpcServer(credentialManager, adminSigningKey)
|
||||
iam_pb.RegisterSeaweedIdentityAccessManagementServer(grpcS, iamGrpcServer)
|
||||
if len(adminSigningKey) == 0 {
|
||||
glog.Warningf("IAM gRPC service NOT registered on filer: jwt.filer_signing.key is empty in security.toml; configure it to enable IAM administration")
|
||||
glog.V(0).Info("Registered IAM gRPC service on filer (unauthenticated; set jwt.filer_signing.key in security.toml to require admin Bearer token)")
|
||||
} else {
|
||||
iamGrpcServer := weed_server.NewIamGrpcServer(credentialManager, adminSigningKey)
|
||||
iam_pb.RegisterSeaweedIdentityAccessManagementServer(grpcS, iamGrpcServer)
|
||||
glog.V(0).Info("Registered IAM gRPC service on filer (admin Bearer token required)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,11 +44,13 @@ expires_after_seconds = 10 # seconds
|
||||
# - f.e. the S3 API Shim generates the JWT
|
||||
# - the Filer server validates the JWT on writing
|
||||
# NOTE: This key is ALSO used as a fallback signing key for S3 STS if s3.iam.config does not specify a signingKey.
|
||||
# NOTE: This key is ALSO required to mount the IAM gRPC service (CreateUser,
|
||||
# PutPolicy, CreateAccessKey, ...) on the filer. The filer refuses to
|
||||
# register that service when the key is empty, and every IAM RPC must
|
||||
# carry a Bearer token signed with this key in its "authorization"
|
||||
# gRPC metadata. Mint such a token with security.GenJwtForFilerAdmin.
|
||||
# NOTE: This key also gates the filer IAM gRPC service (CreateUser, PutPolicy,
|
||||
# CreateAccessKey, ...). When set, every IAM RPC must carry a Bearer
|
||||
# token signed with this key in its "authorization" gRPC metadata; mint
|
||||
# such a token with security.GenJwtForFilerAdmin. When empty, the IAM
|
||||
# gRPC service runs unauthenticated, like the rest of the filer's gRPC
|
||||
# surface — set the key on both filer and admin if the gRPC port is
|
||||
# reachable beyond a trusted network.
|
||||
# the jwt defaults to expire after 10 seconds.
|
||||
[jwt.filer_signing]
|
||||
key = ""
|
||||
|
||||
@@ -16,18 +16,20 @@ import (
|
||||
)
|
||||
|
||||
// IamGrpcServer implements the IAM gRPC service on the filer.
|
||||
// Every RPC requires a Bearer token in the "authorization" metadata, signed
|
||||
// with the filer write-signing key (jwt.filer_signing.key in security.toml).
|
||||
// If no signing key is configured the service refuses to register at all; the
|
||||
// adminSigningKey check below is defensive in case that wiring is bypassed.
|
||||
// Auth is opt-in: when jwt.filer_signing.key is set in security.toml the
|
||||
// service requires a Bearer token in the "authorization" metadata signed with
|
||||
// that key; when it is empty every RPC is accepted unauthenticated, matching
|
||||
// the rest of SeaweedFS's gRPC surface. Operators who expose the filer gRPC
|
||||
// port beyond a trusted network should configure the key.
|
||||
type IamGrpcServer struct {
|
||||
iam_pb.UnimplementedSeaweedIdentityAccessManagementServer
|
||||
credentialManager *credential.CredentialManager
|
||||
adminSigningKey security.SigningKey
|
||||
}
|
||||
|
||||
// NewIamGrpcServer creates a new IAM gRPC server. adminSigningKey is required:
|
||||
// callers without a Bearer token signed by this key are rejected.
|
||||
// NewIamGrpcServer creates a new IAM gRPC server. If adminSigningKey is empty
|
||||
// the service runs unauthenticated; otherwise every RPC requires a Bearer
|
||||
// token signed with the key.
|
||||
func NewIamGrpcServer(credentialManager *credential.CredentialManager, adminSigningKey security.SigningKey) *IamGrpcServer {
|
||||
return &IamGrpcServer{
|
||||
credentialManager: credentialManager,
|
||||
@@ -37,10 +39,11 @@ func NewIamGrpcServer(credentialManager *credential.CredentialManager, adminSign
|
||||
|
||||
// checkAdminAuth verifies the caller presented a Bearer token signed by the
|
||||
// filer's write-signing key. It is invoked at the top of every IAM RPC.
|
||||
// When no signing key is configured the service runs unauthenticated and this
|
||||
// check is a no-op.
|
||||
func (s *IamGrpcServer) checkAdminAuth(ctx context.Context) error {
|
||||
if len(s.adminSigningKey) == 0 {
|
||||
// Service should not be registered without a key; fail closed.
|
||||
return status.Error(codes.PermissionDenied, "iam admin auth not configured")
|
||||
return nil
|
||||
}
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
|
||||
@@ -117,18 +117,31 @@ func TestIamGrpc_ValidToken_ReachesHandler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIamGrpc_NoSigningKey_PermissionDenied(t *testing.T) {
|
||||
// Defensive path: even if the service is somehow registered without a
|
||||
// key, every RPC must refuse.
|
||||
func TestIamGrpc_NoSigningKey_Unauthenticated_Allowed(t *testing.T) {
|
||||
// Auth is opt-in: when the server is built without a signing key, every
|
||||
// RPC is accepted regardless of (or in the absence of) metadata so the
|
||||
// admin UI works against a filer that has no jwt.filer_signing.key set.
|
||||
cm, err := credential.NewCredentialManager(credential.StoreTypeMemory, nil, "")
|
||||
if err != nil {
|
||||
t.Fatalf("NewCredentialManager: %v", err)
|
||||
}
|
||||
s := NewIamGrpcServer(cm, nil)
|
||||
resp, err := s.ListUsers(context.Background(), &iam_pb.ListUsersRequest{})
|
||||
if err != nil {
|
||||
t.Fatalf("ListUsers without key: unexpected error %v", err)
|
||||
}
|
||||
if resp == nil {
|
||||
t.Fatal("ListUsers without key: nil response")
|
||||
}
|
||||
if len(resp.Usernames) != 0 {
|
||||
t.Fatalf("ListUsers: expected empty user list from fresh memory store, got %v", resp.Usernames)
|
||||
}
|
||||
|
||||
// A token sent by a client that does configure a key is also accepted —
|
||||
// the server just ignores it rather than rejecting on signature mismatch.
|
||||
good := security.GenJwtForFilerAdmin(security.SigningKey(testIamSigningKey), 60)
|
||||
_, err = s.ListUsers(ctxWithBearer(string(good)), &iam_pb.ListUsersRequest{})
|
||||
if got, want := status.Code(err), codes.PermissionDenied; got != want {
|
||||
t.Fatalf("ListUsers with no signing key: got code %v, want %v (err=%v)", got, want, err)
|
||||
if _, err := s.ListUsers(ctxWithBearer(string(good)), &iam_pb.ListUsersRequest{}); err != nil {
|
||||
t.Fatalf("ListUsers with stray token but no server key: unexpected error %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user