* Implement IAM propagation to S3 servers - Add PropagatingCredentialStore to propagate IAM changes to S3 servers via gRPC - Add Policy management RPCs to S3 proto and S3ApiServer - Update CredentialManager to use PropagatingCredentialStore when MasterClient is available - Wire FilerServer to enable propagation * Implement parallel IAM propagation and fix S3 cluster registration - Parallelized IAM change propagation with 10s timeout. - Refined context usage in PropagatingCredentialStore. - Added S3Type support to cluster node management. - Enabled S3 servers to register with gRPC address to the master. - Ensured IAM configuration reload after policy updates via gRPC. * Optimize IAM propagation with direct in-memory cache updates * Secure IAM propagation: Use metadata to skip persistence only on propagation * pb: refactor IAM and S3 services for unidirectional IAM propagation - Move SeaweedS3IamCache service from iam.proto to s3.proto. - Remove legacy IAM management RPCs and empty SeaweedS3 service from s3.proto. - Enforce that S3 servers only use the synchronization interface. * pb: regenerate Go code for IAM and S3 services Updated generated code following the proto refactoring of IAM synchronization services. * s3api: implement read-only mode for Embedded IAM API - Add readOnly flag to EmbeddedIamApi to reject write operations via HTTP. - Enable read-only mode by default in S3ApiServer. - Handle AccessDenied error in writeIamErrorResponse. - Embed SeaweedS3IamCacheServer in S3ApiServer. * credential: refactor PropagatingCredentialStore for unidirectional IAM flow - Update to use s3_pb.SeaweedS3IamCacheClient for propagation to S3 servers. - Propagate full Identity object via PutIdentity for consistency. - Remove redundant propagation of specific user/account/policy management RPCs. - Add timeout context for propagation calls. * s3api: implement SeaweedS3IamCacheServer for unidirectional sync - Update S3ApiServer to implement the cache synchronization gRPC interface. - Methods (PutIdentity, RemoveIdentity, etc.) now perform direct in-memory cache updates. - Register SeaweedS3IamCacheServer in command/s3.go. - Remove registration for the legacy and now empty SeaweedS3 service. * s3api: update tests for read-only IAM and propagation - Added TestEmbeddedIamReadOnly to verify rejection of write operations in read-only mode. - Update test setup to pass readOnly=false to NewEmbeddedIamApi in routing tests. - Updated EmbeddedIamApiForTest helper with read-only checks matching production behavior. * s3api: add back temporary debug logs for IAM updates Log IAM updates received via: - gRPC propagation (PutIdentity, PutPolicy, etc.) - Metadata configuration reloads (LoadS3ApiConfigurationFromCredentialManager) - Core identity management (UpsertIdentity, RemoveIdentity) * IAM: finalize propagation fix with reduced logging and clarified architecture * Allow configuring IAM read-only mode for S3 server integration tests * s3api: add defensive validation to UpsertIdentity * s3api: fix log message to reference correct IAM read-only flag * test/s3/iam: ensure WaitForS3Service checks for IAM write permissions * test: enable writable IAM in Makefile for integration tests * IAM: add GetPolicy/ListPolicies RPCs to s3.proto * S3: add GetBucketPolicy and ListBucketPolicies helpers * S3: support storing generic IAM policies in IdentityAccessManagement * S3: implement IAM policy RPCs using IdentityAccessManagement * IAM: fix stale user identity on rename propagation
207 lines
7.2 KiB
Go
207 lines
7.2 KiB
Go
package credential
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
// CredentialManager manages user credentials using a configurable store
|
|
type CredentialManager struct {
|
|
store CredentialStore
|
|
}
|
|
|
|
// NewCredentialManager creates a new credential manager with the specified store
|
|
func NewCredentialManager(storeName CredentialStoreTypeName, configuration util.Configuration, prefix string) (*CredentialManager, error) {
|
|
var store CredentialStore
|
|
|
|
// Find the requested store implementation
|
|
for _, s := range Stores {
|
|
if s.GetName() == storeName {
|
|
store = s
|
|
break
|
|
}
|
|
}
|
|
|
|
if store == nil {
|
|
return nil, fmt.Errorf("credential store '%s' not found. Available stores: %s",
|
|
storeName, getAvailableStores())
|
|
}
|
|
|
|
// Initialize the store
|
|
if err := store.Initialize(configuration, prefix); err != nil {
|
|
return nil, fmt.Errorf("failed to initialize credential store '%s': %v", storeName, err)
|
|
}
|
|
|
|
return &CredentialManager{
|
|
store: store,
|
|
}, nil
|
|
}
|
|
|
|
// SetMasterClient sets the master client to enable propagation of changes to S3 servers
|
|
func (cm *CredentialManager) SetMasterClient(masterClient *wdclient.MasterClient, grpcDialOption grpc.DialOption) {
|
|
cm.store = NewPropagatingCredentialStore(cm.store, masterClient, grpcDialOption)
|
|
}
|
|
|
|
// GetStore returns the underlying credential store
|
|
func (cm *CredentialManager) GetStore() CredentialStore {
|
|
return cm.store
|
|
}
|
|
|
|
// GetStoreName returns the name of the underlying credential store
|
|
func (cm *CredentialManager) GetStoreName() string {
|
|
if cm.store != nil {
|
|
return string(cm.store.GetName())
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// LoadConfiguration loads the S3 API configuration
|
|
func (cm *CredentialManager) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) {
|
|
return cm.store.LoadConfiguration(ctx)
|
|
}
|
|
|
|
// SaveConfiguration saves the S3 API configuration
|
|
func (cm *CredentialManager) SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error {
|
|
return cm.store.SaveConfiguration(ctx, config)
|
|
}
|
|
|
|
// CreateUser creates a new user
|
|
func (cm *CredentialManager) CreateUser(ctx context.Context, identity *iam_pb.Identity) error {
|
|
return cm.store.CreateUser(ctx, identity)
|
|
}
|
|
|
|
// GetUser retrieves a user by username
|
|
func (cm *CredentialManager) GetUser(ctx context.Context, username string) (*iam_pb.Identity, error) {
|
|
return cm.store.GetUser(ctx, username)
|
|
}
|
|
|
|
// UpdateUser updates an existing user
|
|
func (cm *CredentialManager) UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error {
|
|
return cm.store.UpdateUser(ctx, username, identity)
|
|
}
|
|
|
|
// DeleteUser removes a user
|
|
func (cm *CredentialManager) DeleteUser(ctx context.Context, username string) error {
|
|
return cm.store.DeleteUser(ctx, username)
|
|
}
|
|
|
|
// ListUsers returns all usernames
|
|
func (cm *CredentialManager) ListUsers(ctx context.Context) ([]string, error) {
|
|
return cm.store.ListUsers(ctx)
|
|
}
|
|
|
|
// GetUserByAccessKey retrieves a user by access key
|
|
func (cm *CredentialManager) GetUserByAccessKey(ctx context.Context, accessKey string) (*iam_pb.Identity, error) {
|
|
return cm.store.GetUserByAccessKey(ctx, accessKey)
|
|
}
|
|
|
|
// CreateAccessKey creates a new access key for a user
|
|
func (cm *CredentialManager) CreateAccessKey(ctx context.Context, username string, credential *iam_pb.Credential) error {
|
|
return cm.store.CreateAccessKey(ctx, username, credential)
|
|
}
|
|
|
|
// DeleteAccessKey removes an access key for a user
|
|
func (cm *CredentialManager) DeleteAccessKey(ctx context.Context, username string, accessKey string) error {
|
|
return cm.store.DeleteAccessKey(ctx, username, accessKey)
|
|
}
|
|
|
|
// GetPolicies returns all policies
|
|
func (cm *CredentialManager) GetPolicies(ctx context.Context) (map[string]policy_engine.PolicyDocument, error) {
|
|
return cm.store.GetPolicies(ctx)
|
|
}
|
|
|
|
// PutPolicy creates or updates a policy
|
|
func (cm *CredentialManager) PutPolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
|
return cm.store.PutPolicy(ctx, name, document)
|
|
}
|
|
|
|
// DeletePolicy removes a policy
|
|
func (cm *CredentialManager) DeletePolicy(ctx context.Context, name string) error {
|
|
return cm.store.DeletePolicy(ctx, name)
|
|
}
|
|
|
|
// GetPolicy retrieves a policy by name
|
|
func (cm *CredentialManager) GetPolicy(ctx context.Context, name string) (*policy_engine.PolicyDocument, error) {
|
|
return cm.store.GetPolicy(ctx, name)
|
|
}
|
|
|
|
// CreatePolicy creates a new policy (if supported by the store)
|
|
func (cm *CredentialManager) CreatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
|
// Check if the store implements PolicyManager interface with CreatePolicy
|
|
if policyStore, ok := cm.store.(PolicyManager); ok {
|
|
return policyStore.CreatePolicy(ctx, name, document)
|
|
}
|
|
// Fallback to PutPolicy for stores that only implement CredentialStore
|
|
return cm.store.PutPolicy(ctx, name, document)
|
|
}
|
|
|
|
// UpdatePolicy updates an existing policy (if supported by the store)
|
|
func (cm *CredentialManager) UpdatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error {
|
|
// Check if the store implements PolicyManager interface with UpdatePolicy
|
|
if policyStore, ok := cm.store.(PolicyManager); ok {
|
|
return policyStore.UpdatePolicy(ctx, name, document)
|
|
}
|
|
// Fallback to PutPolicy for stores that only implement CredentialStore
|
|
return cm.store.PutPolicy(ctx, name, document)
|
|
}
|
|
|
|
// Shutdown performs cleanup
|
|
func (cm *CredentialManager) Shutdown() {
|
|
if cm.store != nil {
|
|
cm.store.Shutdown()
|
|
}
|
|
}
|
|
|
|
// getAvailableStores returns a comma-separated list of available store names
|
|
func getAvailableStores() string {
|
|
var storeNames []string
|
|
for _, store := range Stores {
|
|
storeNames = append(storeNames, string(store.GetName()))
|
|
}
|
|
return strings.Join(storeNames, ", ")
|
|
}
|
|
|
|
// GetAvailableStores returns a list of available credential store names
|
|
func GetAvailableStores() []CredentialStoreTypeName {
|
|
var storeNames []CredentialStoreTypeName
|
|
for _, store := range Stores {
|
|
storeNames = append(storeNames, store.GetName())
|
|
}
|
|
if storeNames == nil {
|
|
return []CredentialStoreTypeName{}
|
|
}
|
|
return storeNames
|
|
}
|
|
|
|
// CreateServiceAccount creates a new service account
|
|
func (cm *CredentialManager) CreateServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error {
|
|
return cm.store.CreateServiceAccount(ctx, sa)
|
|
}
|
|
|
|
// UpdateServiceAccount updates an existing service account
|
|
func (cm *CredentialManager) UpdateServiceAccount(ctx context.Context, id string, sa *iam_pb.ServiceAccount) error {
|
|
return cm.store.UpdateServiceAccount(ctx, id, sa)
|
|
}
|
|
|
|
// DeleteServiceAccount removes a service account
|
|
func (cm *CredentialManager) DeleteServiceAccount(ctx context.Context, id string) error {
|
|
return cm.store.DeleteServiceAccount(ctx, id)
|
|
}
|
|
|
|
// GetServiceAccount retrieves a service account by ID
|
|
func (cm *CredentialManager) GetServiceAccount(ctx context.Context, id string) (*iam_pb.ServiceAccount, error) {
|
|
return cm.store.GetServiceAccount(ctx, id)
|
|
}
|
|
|
|
// ListServiceAccounts returns all service accounts
|
|
func (cm *CredentialManager) ListServiceAccounts(ctx context.Context) ([]*iam_pb.ServiceAccount, error) {
|
|
return cm.store.ListServiceAccounts(ctx)
|
|
}
|