mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-21 06:36:54 +00:00
* iamapi: route managed policies through credential manager (fixes #9518) CreatePolicy via the IAM API wrote straight to the filer /etc/iam/policies.json, ignoring any non-filer credential store. When credential.postgres was configured, policies created via the IAM API landed only in the filer while the Admin UI wrote to postgres, producing a split-brain where ListPolicies/GetPolicy never saw the Admin UI's policies and vice versa. GetPolicies/PutPolicies on IamS3ApiConfigure now load managed policies from credentialManager and persist Create/Update/Delete as a delta against the store. Inline user/group policies still live in the legacy policies.json file (no credential-store API for them yet). Pre-existing managed policies in the legacy file are merged on read so deployments don't lose data, and re-persisted to the store on the next write so the legacy file is drained over time. * credential: route IAM API inline policies through credential manager Extends the #9518 fix to user-inline and group-inline policies so the IAM API never writes the legacy /etc/iam/policies.json bundle directly. The previous patch only routed managed policies; this one finishes the job for the other two policy types. - Add GroupInlinePolicyStore + GroupInlinePoliciesLoader optional interfaces, mirroring the existing user-inline ones, and matching Put/Get/Delete/List/LoadAll wrappers on CredentialManager. - Implement group-inline storage in memory (new map), filer_etc (new field on PoliciesCollection, reusing the legacy file under policyMu), and postgres (new group_inline_policies table with ON DELETE CASCADE off the groups FK). - Wire the new methods through PropagatingCredentialStore so wrapped stores still delegate correctly. - IamS3ApiConfigure.PutPolicies now applies managed + user-inline + group-inline as deltas through the credential manager; the legacy /etc/iam/policies.json file is never written when a credential manager is wired up. GetPolicies still reads the legacy bundle once as a fallback so unmigrated data is picked up and re-persisted into the store on the next write. * credential: propagate SaveConfiguration writes to running S3 caches Postgres (and any non-filer) credential stores never fired the S3 IAM cache invalidation path on bulk identity / group updates. The PropagatingCredentialStore had explicit Put/Remove handlers for single-entity calls (CreateUser, PutPolicy, etc.) but inherited SaveConfiguration unchanged from the embedded store, so the bulk path the IAM API takes at the end of every handler was silent. Inline-policy changes recompute identity.Actions and persist via SaveConfiguration, so until restart the cached Actions on each S3 server stayed stale and authorization decisions used the pre-change view. Override SaveConfiguration to snapshot the prior user / group lists, delegate the save, then fan out PutIdentity / PutGroup for what's in the new config and RemoveIdentity / RemoveGroup for what got pruned. Reuses the existing SeaweedS3IamCache RPCs, no protobuf changes. * iamapi: drain legacy policies.json after authoritative credential-store writes Review pointed out a resurrection bug: GetPolicies still reads /etc/iam/policies.json as a one-way migration fallback, but PutPolicies in the credential-manager path never wrote that file, so legacy-only entries reappeared on the next read even after the IAM API "deleted" them. PutPolicies now overwrites the bundle with an empty {} after a successful credential-store write, unless the store is filer_etc (which owns the bundle as its own inline-policy backing — clearing it would wipe filer_etc's data). Also wraps the filer read, JSON unmarshal, and marshal errors with context per the other review comments.
188 lines
8.6 KiB
Go
188 lines
8.6 KiB
Go
package credential
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
)
|
|
|
|
var (
|
|
ErrUserNotFound = errors.New("user not found")
|
|
ErrUserAlreadyExists = errors.New("user already exists")
|
|
ErrAccessKeyNotFound = errors.New("access key not found")
|
|
ErrServiceAccountNotFound = errors.New("service account not found")
|
|
ErrPolicyNotFound = errors.New("policy not found")
|
|
ErrPolicyAlreadyAttached = errors.New("policy already attached")
|
|
ErrPolicyNotAttached = errors.New("policy not attached to user")
|
|
ErrGroupNotFound = errors.New("group not found")
|
|
ErrGroupAlreadyExists = errors.New("group already exists")
|
|
ErrGroupNotEmpty = errors.New("group is not empty")
|
|
ErrUserNotInGroup = errors.New("user is not a member of the group")
|
|
)
|
|
|
|
// CredentialStoreTypeName represents the type name of a credential store
|
|
type CredentialStoreTypeName string
|
|
|
|
// Credential store name constants
|
|
const (
|
|
StoreTypeMemory CredentialStoreTypeName = "memory"
|
|
StoreTypeFilerEtc CredentialStoreTypeName = "filer_etc"
|
|
StoreTypePostgres CredentialStoreTypeName = "postgres"
|
|
StoreTypeGrpc CredentialStoreTypeName = "grpc"
|
|
)
|
|
|
|
// CredentialStore defines the interface for user credential storage and retrieval
|
|
type CredentialStore interface {
|
|
// GetName returns the name of the credential store implementation
|
|
GetName() CredentialStoreTypeName
|
|
|
|
// Initialize initializes the credential store with configuration
|
|
Initialize(configuration util.Configuration, prefix string) error
|
|
|
|
// LoadConfiguration loads the entire S3 API configuration
|
|
LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error)
|
|
|
|
// SaveConfiguration saves the entire S3 API configuration
|
|
SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error
|
|
|
|
// CreateUser creates a new user with the given identity
|
|
CreateUser(ctx context.Context, identity *iam_pb.Identity) error
|
|
|
|
// GetUser retrieves a user by username
|
|
GetUser(ctx context.Context, username string) (*iam_pb.Identity, error)
|
|
|
|
// UpdateUser updates an existing user
|
|
UpdateUser(ctx context.Context, username string, identity *iam_pb.Identity) error
|
|
|
|
// DeleteUser removes a user by username
|
|
DeleteUser(ctx context.Context, username string) error
|
|
|
|
// ListUsers returns all usernames
|
|
ListUsers(ctx context.Context) ([]string, error)
|
|
|
|
// GetUserByAccessKey retrieves a user by access key
|
|
GetUserByAccessKey(ctx context.Context, accessKey string) (*iam_pb.Identity, error)
|
|
|
|
// CreateAccessKey creates a new access key for a user
|
|
CreateAccessKey(ctx context.Context, username string, credential *iam_pb.Credential) error
|
|
|
|
// DeleteAccessKey removes an access key for a user
|
|
DeleteAccessKey(ctx context.Context, username string, accessKey string) error
|
|
|
|
// Policy Management
|
|
GetPolicies(ctx context.Context) (map[string]policy_engine.PolicyDocument, error)
|
|
// ListPolicyNames returns the names of all policies
|
|
ListPolicyNames(ctx context.Context) ([]string, error)
|
|
// PutPolicy creates or replaces a policy document.
|
|
PutPolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error
|
|
DeletePolicy(ctx context.Context, name string) error
|
|
GetPolicy(ctx context.Context, name string) (*policy_engine.PolicyDocument, error)
|
|
|
|
// Service Account Management
|
|
CreateServiceAccount(ctx context.Context, sa *iam_pb.ServiceAccount) error
|
|
UpdateServiceAccount(ctx context.Context, id string, sa *iam_pb.ServiceAccount) error
|
|
DeleteServiceAccount(ctx context.Context, id string) error
|
|
GetServiceAccount(ctx context.Context, id string) (*iam_pb.ServiceAccount, error)
|
|
ListServiceAccounts(ctx context.Context) ([]*iam_pb.ServiceAccount, error)
|
|
GetServiceAccountByAccessKey(ctx context.Context, accessKey string) (*iam_pb.ServiceAccount, error)
|
|
|
|
// User Policy Attachment Management
|
|
// AttachUserPolicy attaches a managed policy to a user by policy name
|
|
AttachUserPolicy(ctx context.Context, username string, policyName string) error
|
|
// DetachUserPolicy detaches a managed policy from a user
|
|
DetachUserPolicy(ctx context.Context, username string, policyName string) error
|
|
// ListAttachedUserPolicies returns the list of policy names attached to a user
|
|
ListAttachedUserPolicies(ctx context.Context, username string) ([]string, error)
|
|
|
|
// Group Management
|
|
CreateGroup(ctx context.Context, group *iam_pb.Group) error
|
|
GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error)
|
|
DeleteGroup(ctx context.Context, groupName string) error
|
|
ListGroups(ctx context.Context) ([]string, error)
|
|
UpdateGroup(ctx context.Context, group *iam_pb.Group) error
|
|
|
|
// Shutdown performs cleanup when the store is being shut down
|
|
Shutdown()
|
|
}
|
|
|
|
// AccessKeyInfo represents access key information with metadata
|
|
type AccessKeyInfo struct {
|
|
AccessKey string `json:"accessKey"`
|
|
SecretKey string `json:"secretKey"`
|
|
Username string `json:"username"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
// UserCredentials represents a user's credentials and metadata
|
|
type UserCredentials struct {
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
Account *iam_pb.Account `json:"account,omitempty"`
|
|
Credentials []*iam_pb.Credential `json:"credentials"`
|
|
Actions []string `json:"actions"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
UpdatedAt time.Time `json:"updatedAt"`
|
|
}
|
|
|
|
// PolicyManager interface for managing IAM policies
|
|
type PolicyManager interface {
|
|
GetPolicies(ctx context.Context) (map[string]policy_engine.PolicyDocument, error)
|
|
CreatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error
|
|
UpdatePolicy(ctx context.Context, name string, document policy_engine.PolicyDocument) error
|
|
DeletePolicy(ctx context.Context, name string) error
|
|
GetPolicy(ctx context.Context, name string) (*policy_engine.PolicyDocument, error)
|
|
}
|
|
|
|
// InlinePolicyStore is an optional interface for credential stores that support
|
|
// per-user inline policy storage. Stores that implement this interface preserve
|
|
// the exact policy document submitted via PutUserPolicy, enabling lossless
|
|
// round-trips through GetUserPolicy.
|
|
type InlinePolicyStore interface {
|
|
PutUserInlinePolicy(ctx context.Context, userName, policyName string, document policy_engine.PolicyDocument) error
|
|
GetUserInlinePolicy(ctx context.Context, userName, policyName string) (*policy_engine.PolicyDocument, error)
|
|
DeleteUserInlinePolicy(ctx context.Context, userName, policyName string) error
|
|
ListUserInlinePolicies(ctx context.Context, userName string) ([]string, error)
|
|
}
|
|
|
|
// GroupInlinePolicyStore is an optional interface for credential stores that
|
|
// support per-group inline policy storage. Mirrors InlinePolicyStore so the
|
|
// IAM API can persist group-attached inline policies without writing a
|
|
// shared filer file.
|
|
type GroupInlinePolicyStore interface {
|
|
PutGroupInlinePolicy(ctx context.Context, groupName, policyName string, document policy_engine.PolicyDocument) error
|
|
GetGroupInlinePolicy(ctx context.Context, groupName, policyName string) (*policy_engine.PolicyDocument, error)
|
|
DeleteGroupInlinePolicy(ctx context.Context, groupName, policyName string) error
|
|
ListGroupInlinePolicies(ctx context.Context, groupName string) ([]string, error)
|
|
}
|
|
|
|
// InlinePoliciesLoader is an optional interface for credential stores that can
|
|
// bulk-load every user inline policy in a single round trip.
|
|
type InlinePoliciesLoader interface {
|
|
LoadInlinePolicies(ctx context.Context) (map[string]map[string]policy_engine.PolicyDocument, error)
|
|
}
|
|
|
|
// GroupInlinePoliciesLoader is an optional interface for credential stores
|
|
// that can bulk-load every group inline policy in a single round trip.
|
|
type GroupInlinePoliciesLoader interface {
|
|
LoadGroupInlinePolicies(ctx context.Context) (map[string]map[string]policy_engine.PolicyDocument, error)
|
|
}
|
|
|
|
// UserRenamer is an optional interface for credential stores that can
|
|
// atomically rename a user along with all rows that reference the old
|
|
// username (credentials, inline policies, etc). Backends with referential
|
|
// integrity (e.g. PostgreSQL with ON DELETE CASCADE foreign keys on
|
|
// user_inline_policies) MUST implement this so the IAM rename path can
|
|
// move the dependents within a single transaction; copying them via
|
|
// per-row Put / Get / Delete calls would violate the FK at statement
|
|
// time because the new user row does not yet exist.
|
|
type UserRenamer interface {
|
|
RenameUser(ctx context.Context, oldName, newName string) error
|
|
}
|
|
|
|
// Stores holds all available credential store implementations
|
|
var Stores []CredentialStore
|