snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
This commit is contained in:
87
weed/credential/grpc/grpc_group.go
Normal file
87
weed/credential/grpc/grpc_group.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
)
|
||||
|
||||
// NOTE: The gRPC store uses a load-modify-save pattern for all operations,
|
||||
// which is inherently subject to race conditions under concurrent access.
|
||||
// This matches the existing pattern used for identities and policies.
|
||||
// A future improvement would add dedicated gRPC RPCs for atomic group operations.
|
||||
|
||||
func (store *IamGrpcStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error {
|
||||
if group == nil || group.Name == "" {
|
||||
return fmt.Errorf("group name is required")
|
||||
}
|
||||
config, err := store.LoadConfiguration(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, g := range config.Groups {
|
||||
if g.Name == group.Name {
|
||||
return credential.ErrGroupAlreadyExists
|
||||
}
|
||||
}
|
||||
config.Groups = append(config.Groups, group)
|
||||
return store.SaveConfiguration(ctx, config)
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) {
|
||||
config, err := store.LoadConfiguration(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, g := range config.Groups {
|
||||
if g.Name == groupName {
|
||||
return g, nil
|
||||
}
|
||||
}
|
||||
return nil, credential.ErrGroupNotFound
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) DeleteGroup(ctx context.Context, groupName string) error {
|
||||
config, err := store.LoadConfiguration(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, g := range config.Groups {
|
||||
if g.Name == groupName {
|
||||
config.Groups = append(config.Groups[:i], config.Groups[i+1:]...)
|
||||
return store.SaveConfiguration(ctx, config)
|
||||
}
|
||||
}
|
||||
return credential.ErrGroupNotFound
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) ListGroups(ctx context.Context) ([]string, error) {
|
||||
config, err := store.LoadConfiguration(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var names []string
|
||||
for _, g := range config.Groups {
|
||||
names = append(names, g.Name)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) UpdateGroup(ctx context.Context, group *iam_pb.Group) error {
|
||||
if group == nil || group.Name == "" {
|
||||
return fmt.Errorf("group name is required")
|
||||
}
|
||||
config, err := store.LoadConfiguration(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, g := range config.Groups {
|
||||
if g.Name == group.Name {
|
||||
config.Groups[i] = group
|
||||
return store.SaveConfiguration(ctx, config)
|
||||
}
|
||||
}
|
||||
return credential.ErrGroupNotFound
|
||||
}
|
||||
193
weed/credential/grpc/grpc_identity.go
Normal file
193
weed/credential/grpc/grpc_identity.go
Normal file
@@ -0,0 +1,193 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
)
|
||||
|
||||
func (store *IamGrpcStore) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) {
|
||||
var config *iam_pb.S3ApiConfiguration
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetConfiguration(ctx, &iam_pb.GetConfigurationRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config = resp.Configuration
|
||||
return nil
|
||||
})
|
||||
return config, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.PutConfiguration(ctx, &iam_pb.PutConfigurationRequest{
|
||||
Configuration: config,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) CreateUser(ctx context.Context, identity *iam_pb.Identity) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.CreateUser(ctx, &iam_pb.CreateUserRequest{
|
||||
Identity: identity,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetUser(ctx context.Context, username string) (*iam_pb.Identity, error) {
|
||||
var identity *iam_pb.Identity
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetUser(ctx, &iam_pb.GetUserRequest{
|
||||
Username: username,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identity = resp.Identity
|
||||
return nil
|
||||
})
|
||||
// The filer-side handler returns gRPC NotFound when the user is absent;
|
||||
// translate back to the package sentinel so callers can use
|
||||
// errors.Is(err, credential.ErrUserNotFound) uniformly across stores.
|
||||
if err != nil && status.Code(err) == codes.NotFound {
|
||||
return nil, credential.ErrUserNotFound
|
||||
}
|
||||
return identity, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.UpdateUser(ctx, &iam_pb.UpdateUserRequest{
|
||||
Username: username,
|
||||
Identity: identity,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) DeleteUser(ctx context.Context, username string) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.DeleteUser(ctx, &iam_pb.DeleteUserRequest{
|
||||
Username: username,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) ListUsers(ctx context.Context) ([]string, error) {
|
||||
var usernames []string
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.ListUsers(ctx, &iam_pb.ListUsersRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usernames = resp.Usernames
|
||||
return nil
|
||||
})
|
||||
return usernames, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetUserByAccessKey(ctx context.Context, accessKey string) (*iam_pb.Identity, error) {
|
||||
var identity *iam_pb.Identity
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetUserByAccessKey(ctx, &iam_pb.GetUserByAccessKeyRequest{
|
||||
AccessKey: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
identity = resp.Identity
|
||||
return nil
|
||||
})
|
||||
return identity, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) CreateAccessKey(ctx context.Context, username string, credential *iam_pb.Credential) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.CreateAccessKey(ctx, &iam_pb.CreateAccessKeyRequest{
|
||||
Username: username,
|
||||
Credential: credential,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) DeleteAccessKey(ctx context.Context, username string, accessKey string) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.DeleteAccessKey(ctx, &iam_pb.DeleteAccessKeyRequest{
|
||||
Username: username,
|
||||
AccessKey: accessKey,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// AttachUserPolicy attaches a managed policy to a user by policy name
|
||||
func (store *IamGrpcStore) AttachUserPolicy(ctx context.Context, username string, policyName string) error {
|
||||
// Get current user
|
||||
identity, err := store.GetUser(ctx, username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify policy exists
|
||||
policy, err := store.GetPolicy(ctx, policyName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if policy == nil {
|
||||
return credential.ErrPolicyNotFound
|
||||
}
|
||||
|
||||
// Check if already attached
|
||||
for _, p := range identity.PolicyNames {
|
||||
if p == policyName {
|
||||
// Already attached - return success (idempotent)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
identity.PolicyNames = append(identity.PolicyNames, policyName)
|
||||
return store.UpdateUser(ctx, username, identity)
|
||||
}
|
||||
|
||||
// DetachUserPolicy detaches a managed policy from a user
|
||||
func (store *IamGrpcStore) DetachUserPolicy(ctx context.Context, username string, policyName string) error {
|
||||
identity, err := store.GetUser(ctx, username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
found := false
|
||||
var newPolicies []string
|
||||
for _, p := range identity.PolicyNames {
|
||||
if p == policyName {
|
||||
found = true
|
||||
} else {
|
||||
newPolicies = append(newPolicies, p)
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return credential.ErrPolicyNotAttached
|
||||
}
|
||||
|
||||
identity.PolicyNames = newPolicies
|
||||
return store.UpdateUser(ctx, username, identity)
|
||||
}
|
||||
|
||||
// ListAttachedUserPolicies returns the list of policy names attached to a user
|
||||
func (store *IamGrpcStore) ListAttachedUserPolicies(ctx context.Context, username string) ([]string, error) {
|
||||
identity, err := store.GetUser(ctx, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return identity.PolicyNames, nil
|
||||
}
|
||||
101
weed/credential/grpc/grpc_policy.go
Normal file
101
weed/credential/grpc/grpc_policy.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func (store *IamGrpcStore) GetPolicies(ctx context.Context) (map[string]policy_engine.PolicyDocument, error) {
|
||||
policies := make(map[string]policy_engine.PolicyDocument)
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.ListPolicies(ctx, &iam_pb.ListPoliciesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range resp.Policies {
|
||||
var doc policy_engine.PolicyDocument
|
||||
if err := json.Unmarshal([]byte(p.Content), &doc); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal policy %s: %v", p.Name, err)
|
||||
}
|
||||
policies[p.Name] = doc
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return policies, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) PutPolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
||||
content, err := json.Marshal(document)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.PutPolicy(ctx, &iam_pb.PutPolicyRequest{
|
||||
Name: name,
|
||||
Content: string(content),
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) DeletePolicy(ctx context.Context, name string) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.DeletePolicy(ctx, &iam_pb.DeletePolicyRequest{
|
||||
Name: name,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetPolicy(ctx context.Context, name string) (*policy_engine.PolicyDocument, error) {
|
||||
var doc policy_engine.PolicyDocument
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetPolicy(ctx, &iam_pb.GetPolicyRequest{
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.Unmarshal([]byte(resp.Content), &doc)
|
||||
})
|
||||
if err != nil {
|
||||
// If policy not found, return nil instead of error (consistent with other stores)
|
||||
if st, ok := status.FromError(err); ok && st.Code() == codes.NotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &doc, nil
|
||||
}
|
||||
|
||||
// CreatePolicy creates a new policy (delegates to PutPolicy)
|
||||
func (store *IamGrpcStore) CreatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
||||
return store.PutPolicy(ctx, name, document)
|
||||
}
|
||||
|
||||
// ListPolicyNames retrieves names of all IAM policies via gRPC.
|
||||
func (store *IamGrpcStore) ListPolicyNames(ctx context.Context) ([]string, error) {
|
||||
var names []string
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.ListPolicies(ctx, &iam_pb.ListPoliciesRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, policy := range resp.Policies {
|
||||
names = append(names, policy.Name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return names, err
|
||||
}
|
||||
|
||||
// UpdatePolicy updates an existing policy (delegates to PutPolicy)
|
||||
func (store *IamGrpcStore) UpdatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
||||
return store.PutPolicy(ctx, name, document)
|
||||
}
|
||||
78
weed/credential/grpc/grpc_service_account.go
Normal file
78
weed/credential/grpc/grpc_service_account.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
)
|
||||
|
||||
func (store *IamGrpcStore) CreateServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.CreateServiceAccount(ctx, &iam_pb.CreateServiceAccountRequest{
|
||||
ServiceAccount: sa,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) UpdateServiceAccount(ctx context.Context, id string, sa *iam_pb.ServiceAccount) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.UpdateServiceAccount(ctx, &iam_pb.UpdateServiceAccountRequest{
|
||||
Id: id,
|
||||
ServiceAccount: sa,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) DeleteServiceAccount(ctx context.Context, id string) error {
|
||||
return store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
_, err := client.DeleteServiceAccount(ctx, &iam_pb.DeleteServiceAccountRequest{
|
||||
Id: id,
|
||||
})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetServiceAccount(ctx context.Context, id string) (*iam_pb.ServiceAccount, error) {
|
||||
var sa *iam_pb.ServiceAccount
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetServiceAccount(ctx, &iam_pb.GetServiceAccountRequest{
|
||||
Id: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sa = resp.ServiceAccount
|
||||
return nil
|
||||
})
|
||||
return sa, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) ListServiceAccounts(ctx context.Context) ([]*iam_pb.ServiceAccount, error) {
|
||||
var accounts []*iam_pb.ServiceAccount
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.ListServiceAccounts(ctx, &iam_pb.ListServiceAccountsRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
accounts = resp.ServiceAccounts
|
||||
return nil
|
||||
})
|
||||
return accounts, err
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetServiceAccountByAccessKey(ctx context.Context, accessKey string) (*iam_pb.ServiceAccount, error) {
|
||||
var sa *iam_pb.ServiceAccount
|
||||
err := store.withIamClient(ctx, func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error {
|
||||
resp, err := client.GetServiceAccountByAccessKey(ctx, &iam_pb.GetServiceAccountByAccessKeyRequest{
|
||||
AccessKey: accessKey,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sa = resp.ServiceAccount
|
||||
return nil
|
||||
})
|
||||
return sa, err
|
||||
}
|
||||
108
weed/credential/grpc/grpc_store.go
Normal file
108
weed/credential/grpc/grpc_store.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
credential.Stores = append(credential.Stores, &IamGrpcStore{})
|
||||
}
|
||||
|
||||
// IamGrpcStore implements CredentialStore by calling the filer's IAM gRPC
|
||||
// service. The filer requires an admin-signed Bearer token on every RPC
|
||||
// (see weed/server/filer_server_handlers_iam_grpc.go); SetAdminSigning must
|
||||
// be called with the same jwt.filer_signing.key value that the filer reads
|
||||
// from security.toml, or every call will fail with Unauthenticated.
|
||||
type IamGrpcStore struct {
|
||||
filerAddressFunc func() pb.ServerAddress // Function to get current active filer
|
||||
grpcDialOption grpc.DialOption
|
||||
// adminSigningKey is the HS256 secret used to mint Bearer tokens that the
|
||||
// filer's IAM gRPC service validates. Must match jwt.filer_signing.key on
|
||||
// the filer side. Empty means no token is sent (the filer will reject).
|
||||
adminSigningKey security.SigningKey
|
||||
adminSigningExpiresAfterSec int
|
||||
mu sync.RWMutex // Protects filerAddressFunc, grpcDialOption, adminSigningKey, and adminSigningExpiresAfterSec
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) GetName() credential.CredentialStoreTypeName {
|
||||
return credential.StoreTypeGrpc
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) Initialize(configuration util.Configuration, prefix string) error {
|
||||
if configuration != nil {
|
||||
filerAddr := configuration.GetString(prefix + "filer")
|
||||
if filerAddr != "" {
|
||||
store.mu.Lock()
|
||||
store.filerAddressFunc = func() pb.ServerAddress {
|
||||
return pb.ServerAddress(filerAddr)
|
||||
}
|
||||
store.mu.Unlock()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) SetFilerAddressFunc(getFiler func() pb.ServerAddress, grpcDialOption grpc.DialOption) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
store.filerAddressFunc = getFiler
|
||||
store.grpcDialOption = grpcDialOption
|
||||
}
|
||||
|
||||
// SetAdminSigning configures the HS256 secret used to mint Bearer tokens for
|
||||
// the filer's IAM gRPC service. The key must match jwt.filer_signing.key in
|
||||
// the filer's security.toml. If expiresAfterSec is 0, tokens are minted
|
||||
// without an exp claim.
|
||||
func (store *IamGrpcStore) SetAdminSigning(key security.SigningKey, expiresAfterSec int) {
|
||||
store.mu.Lock()
|
||||
defer store.mu.Unlock()
|
||||
store.adminSigningKey = key
|
||||
store.adminSigningExpiresAfterSec = expiresAfterSec
|
||||
}
|
||||
|
||||
// withIamClient invokes fn against a (possibly cached) gRPC client to the
|
||||
// filer's IAM service. If an admin signing key is configured the call attaches
|
||||
// a freshly minted Bearer token via outgoing metadata; otherwise no auth
|
||||
// header is sent and the filer will return Unauthenticated.
|
||||
func (store *IamGrpcStore) withIamClient(ctx context.Context, fn func(ctx context.Context, client iam_pb.SeaweedIdentityAccessManagementClient) error) error {
|
||||
store.mu.RLock()
|
||||
if store.filerAddressFunc == nil {
|
||||
store.mu.RUnlock()
|
||||
return fmt.Errorf("iam_grpc: filer not yet available")
|
||||
}
|
||||
|
||||
filerAddress := store.filerAddressFunc()
|
||||
dialOption := store.grpcDialOption
|
||||
signingKey := store.adminSigningKey
|
||||
expiresAfterSec := store.adminSigningExpiresAfterSec
|
||||
store.mu.RUnlock()
|
||||
|
||||
if filerAddress == "" {
|
||||
return fmt.Errorf("iam_grpc: no filer discovered yet")
|
||||
}
|
||||
|
||||
if len(signingKey) > 0 {
|
||||
token := security.GenJwtForFilerAdmin(signingKey, expiresAfterSec)
|
||||
if token != "" {
|
||||
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", security.BearerPrefix+string(token))
|
||||
}
|
||||
}
|
||||
|
||||
return pb.WithGrpcClient(context.Background(), false, 0, func(conn *grpc.ClientConn) error {
|
||||
client := iam_pb.NewSeaweedIdentityAccessManagementClient(conn)
|
||||
return fn(ctx, client)
|
||||
}, filerAddress.ToGrpcAddress(), false, dialOption)
|
||||
}
|
||||
|
||||
func (store *IamGrpcStore) Shutdown() {
|
||||
}
|
||||
108
weed/credential/grpc/grpc_store_test.go
Normal file
108
weed/credential/grpc/grpc_store_test.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
_ "github.com/seaweedfs/seaweedfs/weed/credential/memory"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
||||
"github.com/seaweedfs/seaweedfs/weed/security"
|
||||
weed_server "github.com/seaweedfs/seaweedfs/weed/server"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// TestIamGrpcStore_AdminBearerToken pins the contract that broke in 4.24:
|
||||
// after PR #9442 the filer's IAM gRPC service requires an admin-signed
|
||||
// Bearer token on every RPC, and IamGrpcStore is the only production
|
||||
// client that talks to it (from the admin server). If the store doesn't
|
||||
// mint and attach a token the admin UI Users/Groups pages fail with
|
||||
// Unauthenticated; that's what issues #9495/#9496 reported.
|
||||
func TestIamGrpcStore_AdminBearerToken(t *testing.T) {
|
||||
const goodKey = "iam-admin-itest-key"
|
||||
const wrongKey = "iam-admin-itest-key-different"
|
||||
|
||||
// Real IamGrpcServer backed by an in-memory credential manager so we
|
||||
// exercise the full handler (auth check + business logic), not a stub.
|
||||
cm, err := credential.NewCredentialManager(credential.StoreTypeMemory, nil, "")
|
||||
if err != nil {
|
||||
t.Fatalf("NewCredentialManager: %v", err)
|
||||
}
|
||||
defer cm.Shutdown()
|
||||
|
||||
iamSrv := weed_server.NewIamGrpcServer(cm, security.SigningKey(goodKey))
|
||||
|
||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
grpcSrv := grpc.NewServer()
|
||||
iam_pb.RegisterSeaweedIdentityAccessManagementServer(grpcSrv, iamSrv)
|
||||
go func() { _ = grpcSrv.Serve(lis) }()
|
||||
t.Cleanup(grpcSrv.Stop)
|
||||
|
||||
// pb.ServerAddress encodes the grpc port in the trailing ".N" segment.
|
||||
// host:0 is the unused HTTP port; the listener's actual port is the
|
||||
// gRPC one that ToGrpcAddress() unpacks for dialing.
|
||||
_, portStr, err := net.SplitHostPort(lis.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatalf("split listener addr: %v", err)
|
||||
}
|
||||
serverAddr := pb.ServerAddress(fmt.Sprintf("127.0.0.1:0.%s", portStr))
|
||||
|
||||
newStore := func(key string) *IamGrpcStore {
|
||||
s := &IamGrpcStore{}
|
||||
s.SetFilerAddressFunc(func() pb.ServerAddress { return serverAddr }, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if key != "" {
|
||||
s.SetAdminSigning(security.SigningKey(key), 0)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("matching_key_succeeds", func(t *testing.T) {
|
||||
store := newStore(goodKey)
|
||||
if _, err := store.ListUsers(ctx); err != nil {
|
||||
t.Fatalf("ListUsers with matching key: %v", err)
|
||||
}
|
||||
if err := store.CreateUser(ctx, &iam_pb.Identity{Name: "alice"}); err != nil {
|
||||
t.Fatalf("CreateUser with matching key: %v", err)
|
||||
}
|
||||
got, err := store.GetUser(ctx, "alice")
|
||||
if err != nil {
|
||||
t.Fatalf("GetUser with matching key: %v", err)
|
||||
}
|
||||
if got == nil || got.Name != "alice" {
|
||||
t.Fatalf("GetUser returned %+v, want name=alice", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("wrong_key_unauthenticated", func(t *testing.T) {
|
||||
store := newStore(wrongKey)
|
||||
_, err := store.ListUsers(ctx)
|
||||
if got := status.Code(err); got != codes.Unauthenticated {
|
||||
t.Fatalf("ListUsers with wrong key: got code %v err=%v, want Unauthenticated", got, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no_key_unauthenticated", func(t *testing.T) {
|
||||
store := newStore("")
|
||||
_, err := store.ListUsers(ctx)
|
||||
// Server reports "missing authorization metadata" when no token is
|
||||
// attached. Either way the gRPC status code is Unauthenticated.
|
||||
if got := status.Code(err); got != codes.Unauthenticated {
|
||||
t.Fatalf("ListUsers with no key: got code %v err=%v, want Unauthenticated", got, err)
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "authorization") {
|
||||
t.Fatalf("ListUsers with no key: error message %q does not mention authorization", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user