mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-12 01:20:45 +02:00
* refactor(util): extract pgx OpenDB + DSN builder into shared pgxutil
The postgres filer store had OpenPGXDB plus duplicated key=value DSN
assembly across postgres/ and postgres2/. Move the connection helper to
weed/util/pgxutil and add BuildDSN so the credential postgres store can
land on the same code path.
filer/postgres/pgx_conn.go keeps OpenPGXDB as a thin alias so postgres2
keeps building unchanged.
* refactor(credential/postgres): use shared pgxutil for connection setup
Replace the bespoke fmt.Sprintf DSN + sql.Open("pgx", ...) path with
pgxutil.BuildDSN + pgxutil.OpenDB so the credential store mirrors the
postgres filer store. This also drops the leaky RegisterConnConfig-style
init in favor of stdlib.OpenDB(*config), which doesn't accumulate
entries in the global pgx config map.
Adds parity knobs the filer store already exposes: sslcrl, and
configurable connection_max_idle / connection_max_open /
connection_max_lifetime_seconds (with the previous hardcoded 25/5/5min
as defaults). Also moves the jsonbParam helper here so other store
files can reuse it. (Helper is also referenced by postgres_identity.go,
which is migrated to it in the next commit.)
* refactor(credential/postgres): use jsonbParam helper across all writers
Consolidate JSONB write handling on the new pgxutil-adjacent helper
jsonbParam(b []byte) interface{}, which returns nil (driver writes SQL
NULL) when the marshaled JSON is empty and string(b) otherwise.
postgres_identity.go: replace the inline 'var fooParam any' /
'fooParam = string(b)' pattern with the helper. Same in CreateUser
and UpdateUser.
postgres_inline_policy.go, postgres_policy.go, postgres_service_account.go,
postgres_group.go: every JSONB writer was still passing []byte. Under
pgx simple_protocol (pgbouncer_compatible=true), []byte is encoded as
bytea and Postgres rejects that against a JSONB column with "invalid
input syntax for type json". Route them through jsonbParam too.
* fix(credential/postgres): rework SaveConfiguration to handle rename + UNIQUE access keys
The IAM rename path (s3api UpdateUser) renames an identity in place
and keeps its access keys. With the previous flow — upsert each user,
then per-user delete-and-insert credentials, then prune absent users —
the renamed user's access keys were still owned by the old row when
the INSERT for the new name ran, tripping credentials.access_key's
global UNIQUE constraint and failing every rename of a user with
credentials.
Reorder the SaveConfiguration body so the prune step runs BEFORE the
credential replace. CASCADE on the old user releases its access keys
in the same transaction, and the new name can then claim them.
While here:
- Replace the per-user loop DELETE FROM users WHERE username = $1 with
a single DELETE ... WHERE username = ANY($1), one round trip instead
of N inside the transaction.
- Surface inline-policy CASCADE losses: count user_inline_policies for
the prune set and emit a Warningf when the count is non-zero so
rename-driven drops are visible in operator logs (the structural
fix for renames lives at the IAM layer in a follow-up commit).
- Two-pass credential replace: clear credentials for every user we are
about to rewrite first, then insert, so an access key can be moved
between two users in the same SaveConfiguration call.
- credErr := credRows.Err() before credRows.Close() in
LoadConfiguration — Err() is documented as safe after Close, but
the leading-capture pattern matches the rest of the file.
* fix(s3api/iam): preserve inline policies when renaming a user
EmbeddedIamApi.UpdateUser renames an identity in place and the caller
persists via SaveConfiguration, which prunes the old username and
CASCADE-drops its rows from user_inline_policies. GetUserPolicy and
ListUserPolicies then return nothing under the new name even though
the API reported success — silent data loss.
Before flipping sourceIdent.Name, list the user's stored inline
policies and re-attach each one under the new name. The subsequent
SaveConfiguration prune still CASCADE-removes the old-name rows; only
the duplicates we just wrote under the new name survive. Adds a
regression test that puts a policy on the old name, renames, and
asserts the policy is readable under the new name.
* perf(credential/postgres): batch the credential clear in SaveConfiguration
The two-pass credential replace was clearing each incoming user's
credentials with its own DELETE statement — N round-trips inside the
transaction. Match the pattern already used for the user prune and
issue a single DELETE FROM credentials WHERE username = ANY($1)
instead.
* refactor(s3api/iam): plumb context through UpdateUser
UpdateUser was synthesizing a fresh context.Background() inside the
inline-policy migration block, which discards the request deadline,
cancellation, and tracing carried by the caller. Add ctx as the first
parameter and pass r.Context() in via the ExecuteAction dispatcher,
mirroring the signature already used by CreatePolicy /
AttachUserPolicy / DetachUserPolicy.
* fix(util/pgxutil): quote DSN values per libpq rules
BuildDSN was concatenating values directly, so any password / cert path
/ database name with a space, single quote, or backslash produced a
malformed connection string and pgx.ParseConfig either errored or
mis-parsed the remainder. Critical now that the helper is shared with
the credential store: mTLS deployments routinely sourcing passwords or
secret-mounted cert paths from a vault are exactly the case where
spaces and quotes show up.
Add quoteDSNValue: empty values and values containing whitespace, `'`,
or `\` are wrapped in single quotes with `'` and `\` escaped per
PostgreSQL libpq rules; plain alphanumeric values pass through
unchanged. Apply it to every variable field in BuildDSN.
Adds a test that round-trips a password containing spaces, quotes and
backslashes through pgx.ParseConfig and confirms the parsed Config
matches the input.
* fix(credential,s3api/iam): atomic UserRenamer to avoid FK violation on rename
The previous IAM rename path called PutUserInlinePolicy(newName, ...)
before SaveConfiguration created the new users row. user_inline_policies
has a non-deferrable FOREIGN KEY (username) REFERENCES users(username),
which Postgres validates at statement time, so every rename of a user
that owned at least one inline policy failed with an FK violation. The
existing memory-store regression test missed it because the memory
backend has no FK enforcement.
Add an optional credential.UserRenamer interface plus a
CredentialManager.RenameUser thin shim that returns (supported, err).
Implement it on PostgresStore as an atomic in-transaction migration:
INSERT the new users row by SELECT-copying from the old, UPDATE
credentials.username and user_inline_policies.username to the new
name (FK satisfied because both rows now exist), then DELETE the old
row. ErrUserNotFound / ErrUserAlreadyExists are surfaced cleanly.
Implement it on MemoryStore by re-binding store.users / store.accessKeys
/ store.inlinePolicies under the new name. Also fixes a small leak in
DeleteUser, which was forgetting to drop the user's inline-policy
bucket.
EmbeddedIamApi.UpdateUser now calls RenameUser first; if the store
implements the interface, that's the whole migration. If it doesn't
(stores without FK enforcement), fall back to the previous
list / get / put copy.
Adds a focused test for MemoryStore.RenameUser that asserts the
identity, the access-key index, and the inline policies all land
under the new name.
431 lines
16 KiB
Go
431 lines
16 KiB
Go
package credential
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"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"
|
|
"google.golang.org/protobuf/encoding/protojson"
|
|
)
|
|
|
|
// FilerAddressSetter is an interface for credential stores that need a dynamic filer address
|
|
type FilerAddressSetter interface {
|
|
SetFilerAddressFunc(getFiler func() pb.ServerAddress, grpcDialOption grpc.DialOption)
|
|
}
|
|
|
|
// CredentialManager manages user credentials using a configurable store
|
|
type CredentialManager struct {
|
|
Store CredentialStore
|
|
// staticMu protects staticIdentities and staticNames, which are written
|
|
// by SetStaticIdentities (startup + config reload) and read concurrently
|
|
// by LoadConfiguration, SaveConfiguration, GetStaticUsernames, and IsStaticIdentity.
|
|
staticMu sync.RWMutex
|
|
// staticIdentities holds identities loaded from a static config file (-s3.config).
|
|
// These are included in LoadConfiguration so that listing operations
|
|
// return all configured identities, not just dynamic ones from the store.
|
|
staticIdentities []*iam_pb.Identity
|
|
staticNames map[string]bool
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
func (cm *CredentialManager) SetMasterClient(masterClient *wdclient.MasterClient, grpcDialOption grpc.DialOption) {
|
|
cm.Store = NewPropagatingCredentialStore(cm.Store, masterClient, grpcDialOption)
|
|
}
|
|
|
|
// SetFilerAddressFunc sets the function to get the current filer address
|
|
func (cm *CredentialManager) SetFilerAddressFunc(getFiler func() pb.ServerAddress, grpcDialOption grpc.DialOption) {
|
|
if s, ok := cm.Store.(FilerAddressSetter); ok {
|
|
s.SetFilerAddressFunc(getFiler, 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 ""
|
|
}
|
|
|
|
// SetStaticIdentities registers identities loaded from a static config file.
|
|
// These identities are included in LoadConfiguration and ListUsers results
|
|
// but are never persisted to the dynamic store.
|
|
func (cm *CredentialManager) SetStaticIdentities(identities []*iam_pb.Identity) {
|
|
filtered := make([]*iam_pb.Identity, 0, len(identities))
|
|
names := make(map[string]bool, len(identities))
|
|
for _, ident := range identities {
|
|
if ident != nil {
|
|
filtered = append(filtered, ident)
|
|
names[ident.Name] = true
|
|
}
|
|
}
|
|
cm.staticMu.Lock()
|
|
cm.staticIdentities = filtered
|
|
cm.staticNames = names
|
|
cm.staticMu.Unlock()
|
|
}
|
|
|
|
// IsStaticIdentity returns true if the named identity was loaded from static config.
|
|
func (cm *CredentialManager) IsStaticIdentity(name string) bool {
|
|
cm.staticMu.RLock()
|
|
defer cm.staticMu.RUnlock()
|
|
return cm.staticNames[name]
|
|
}
|
|
|
|
// GetStaticIdentity returns the protobuf identity for a static user, or nil.
|
|
func (cm *CredentialManager) GetStaticIdentity(name string) *iam_pb.Identity {
|
|
cm.staticMu.RLock()
|
|
defer cm.staticMu.RUnlock()
|
|
for _, ident := range cm.staticIdentities {
|
|
if ident.Name == name {
|
|
return ident
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetStaticUsernames returns the names of all static identities.
|
|
func (cm *CredentialManager) GetStaticUsernames() []string {
|
|
cm.staticMu.RLock()
|
|
defer cm.staticMu.RUnlock()
|
|
names := make([]string, 0, len(cm.staticIdentities))
|
|
for _, ident := range cm.staticIdentities {
|
|
names = append(names, ident.Name)
|
|
}
|
|
return names
|
|
}
|
|
|
|
// LoadConfiguration loads the S3 API configuration from the store and merges
|
|
// in any static identities so that listing operations show all users.
|
|
func (cm *CredentialManager) LoadConfiguration(ctx context.Context) (*iam_pb.S3ApiConfiguration, error) {
|
|
config, err := cm.Store.LoadConfiguration(ctx)
|
|
if err != nil {
|
|
return config, err
|
|
}
|
|
// Merge static identities that are not already in the dynamic config
|
|
cm.staticMu.RLock()
|
|
staticIdents := cm.staticIdentities
|
|
cm.staticMu.RUnlock()
|
|
if len(staticIdents) > 0 {
|
|
dynamicNames := make(map[string]bool, len(config.Identities))
|
|
for _, ident := range config.Identities {
|
|
dynamicNames[ident.Name] = true
|
|
}
|
|
for _, si := range staticIdents {
|
|
if !dynamicNames[si.Name] {
|
|
config.Identities = append(config.Identities, si)
|
|
}
|
|
}
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
// SaveConfiguration saves the S3 API configuration.
|
|
// Static identities are filtered out before saving to the store.
|
|
// The caller's config is not mutated.
|
|
func (cm *CredentialManager) SaveConfiguration(ctx context.Context, config *iam_pb.S3ApiConfiguration) error {
|
|
cm.staticMu.RLock()
|
|
staticNames := cm.staticNames
|
|
cm.staticMu.RUnlock()
|
|
if len(staticNames) > 0 {
|
|
var dynamicOnly []*iam_pb.Identity
|
|
for _, ident := range config.Identities {
|
|
if !staticNames[ident.Name] {
|
|
dynamicOnly = append(dynamicOnly, ident)
|
|
}
|
|
}
|
|
configCopy := *config
|
|
configCopy.Identities = dynamicOnly
|
|
return cm.Store.SaveConfiguration(ctx, &configCopy)
|
|
}
|
|
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 usernames from the dynamic store via cm.Store.ListUsers.
|
|
// On store error the error is returned directly without merging static entries.
|
|
// Static identities (cm.staticIdentities) are NOT included here because
|
|
// internal callers (e.g. DeletePolicy) look up each user in the store and
|
|
// would fail on non-existent static entries. External callers that need the
|
|
// full list should merge GetStaticUsernames separately.
|
|
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)
|
|
}
|
|
|
|
// ListPolicyNames returns the names of all policies
|
|
func (cm *CredentialManager) ListPolicyNames(ctx context.Context) ([]string, error) {
|
|
return cm.Store.ListPolicyNames(ctx)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// PutUserInlinePolicy stores a per-user inline policy document.
|
|
// Returns nil without error if the underlying store does not support inline policies.
|
|
func (cm *CredentialManager) PutUserInlinePolicy(ctx context.Context, userName, policyName string, document policy_engine.PolicyDocument) error {
|
|
if store, ok := cm.Store.(InlinePolicyStore); ok {
|
|
return store.PutUserInlinePolicy(ctx, userName, policyName, document)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetUserInlinePolicy retrieves a per-user inline policy document.
|
|
// Returns nil without error if the underlying store does not support inline policies.
|
|
func (cm *CredentialManager) GetUserInlinePolicy(ctx context.Context, userName, policyName string) (*policy_engine.PolicyDocument, error) {
|
|
if store, ok := cm.Store.(InlinePolicyStore); ok {
|
|
return store.GetUserInlinePolicy(ctx, userName, policyName)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// DeleteUserInlinePolicy removes a per-user inline policy document.
|
|
func (cm *CredentialManager) DeleteUserInlinePolicy(ctx context.Context, userName, policyName string) error {
|
|
if store, ok := cm.Store.(InlinePolicyStore); ok {
|
|
return store.DeleteUserInlinePolicy(ctx, userName, policyName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ListUserInlinePolicies returns the names of all inline policies for a user.
|
|
func (cm *CredentialManager) ListUserInlinePolicies(ctx context.Context, userName string) ([]string, error) {
|
|
if store, ok := cm.Store.(InlinePolicyStore); ok {
|
|
return store.ListUserInlinePolicies(ctx, userName)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// RenameUser atomically renames a user along with all FK-backed
|
|
// dependents (credentials, inline policies, ...) when the underlying
|
|
// store implements UserRenamer. Returns false if the store does not
|
|
// support an atomic rename, in which case the caller is expected to
|
|
// fall back to its own best-effort path. A returned non-nil error
|
|
// always means the rename was attempted and failed.
|
|
func (cm *CredentialManager) RenameUser(ctx context.Context, oldName, newName string) (bool, error) {
|
|
store, ok := cm.Store.(UserRenamer)
|
|
if !ok {
|
|
return false, nil
|
|
}
|
|
return true, store.RenameUser(ctx, oldName, newName)
|
|
}
|
|
|
|
// LoadS3ConfigFile reads a static S3 identity config file and registers
|
|
// the identities so they appear in LoadConfiguration and listing results.
|
|
func (cm *CredentialManager) LoadS3ConfigFile(path string) error {
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return fmt.Errorf("read %s: %w", path, err)
|
|
}
|
|
config := &iam_pb.S3ApiConfiguration{}
|
|
opts := protojson.UnmarshalOptions{DiscardUnknown: true, AllowPartial: true}
|
|
if err := opts.Unmarshal(content, config); err != nil {
|
|
return fmt.Errorf("parse %s: %w", path, err)
|
|
}
|
|
for _, ident := range config.Identities {
|
|
ident.IsStatic = true
|
|
}
|
|
cm.SetStaticIdentities(config.Identities)
|
|
glog.V(1).Infof("Loaded %d static identities from %s", len(config.Identities), path)
|
|
return nil
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// AttachUserPolicy attaches a managed policy to a user
|
|
func (cm *CredentialManager) AttachUserPolicy(ctx context.Context, username string, policyName string) error {
|
|
return cm.Store.AttachUserPolicy(ctx, username, policyName)
|
|
}
|
|
|
|
// DetachUserPolicy detaches a managed policy from a user
|
|
func (cm *CredentialManager) DetachUserPolicy(ctx context.Context, username string, policyName string) error {
|
|
return cm.Store.DetachUserPolicy(ctx, username, policyName)
|
|
}
|
|
|
|
// ListAttachedUserPolicies returns the list of policy names attached to a user
|
|
func (cm *CredentialManager) ListAttachedUserPolicies(ctx context.Context, username string) ([]string, error) {
|
|
return cm.Store.ListAttachedUserPolicies(ctx, username)
|
|
}
|
|
|
|
// Group Management
|
|
|
|
func (cm *CredentialManager) CreateGroup(ctx context.Context, group *iam_pb.Group) error {
|
|
return cm.Store.CreateGroup(ctx, group)
|
|
}
|
|
|
|
func (cm *CredentialManager) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) {
|
|
return cm.Store.GetGroup(ctx, groupName)
|
|
}
|
|
|
|
func (cm *CredentialManager) DeleteGroup(ctx context.Context, groupName string) error {
|
|
return cm.Store.DeleteGroup(ctx, groupName)
|
|
}
|
|
|
|
func (cm *CredentialManager) ListGroups(ctx context.Context) ([]string, error) {
|
|
return cm.Store.ListGroups(ctx)
|
|
}
|
|
|
|
func (cm *CredentialManager) UpdateGroup(ctx context.Context, group *iam_pb.Group) error {
|
|
return cm.Store.UpdateGroup(ctx, group)
|
|
}
|