Browse Source
iam: add group CRUD to CredentialStore interface and all backends
iam: add group CRUD to CredentialStore interface and all backends
Add group management methods (CreateGroup, GetGroup, DeleteGroup, ListGroups, UpdateGroup) to the CredentialStore interface with implementations for memory, filer_etc, postgres, and grpc stores. Wire group loading/saving into filer_etc LoadConfiguration and SaveConfiguration.pull/8560/head
8 changed files with 484 additions and 0 deletions
-
10weed/credential/credential_store.go
-
158weed/credential/filer_etc/filer_etc_group.go
-
43weed/credential/filer_etc/filer_etc_identity.go
-
75weed/credential/grpc/grpc_group.go
-
62weed/credential/memory/memory_group.go
-
4weed/credential/memory/memory_store.go
-
116weed/credential/postgres/postgres_group.go
-
16weed/credential/postgres/postgres_store.go
@ -0,0 +1,158 @@ |
|||||
|
package filer_etc |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
"errors" |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/credential" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/filer" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/glog" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" |
||||
|
) |
||||
|
|
||||
|
const IamGroupsDirectory = "groups" |
||||
|
|
||||
|
func (store *FilerEtcStore) loadGroupsFromMultiFile(ctx context.Context, s3cfg *iam_pb.S3ApiConfiguration) error { |
||||
|
return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
||||
|
dir := filer.IamConfigDirectory + "/" + IamGroupsDirectory |
||||
|
entries, err := listEntries(ctx, client, dir) |
||||
|
if err != nil { |
||||
|
if err == filer_pb.ErrNotFound { |
||||
|
return nil |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
|
||||
|
for _, entry := range entries { |
||||
|
if entry.IsDirectory { |
||||
|
continue |
||||
|
} |
||||
|
|
||||
|
var content []byte |
||||
|
if len(entry.Content) > 0 { |
||||
|
content = entry.Content |
||||
|
} else { |
||||
|
c, err := filer.ReadInsideFiler(ctx, client, dir, entry.Name) |
||||
|
if err != nil { |
||||
|
glog.Warningf("Failed to read group file %s: %v", entry.Name, err) |
||||
|
continue |
||||
|
} |
||||
|
content = c |
||||
|
} |
||||
|
|
||||
|
if len(content) > 0 { |
||||
|
g := &iam_pb.Group{} |
||||
|
if err := json.Unmarshal(content, g); err != nil { |
||||
|
glog.Warningf("Failed to unmarshal group %s: %v", entry.Name, err) |
||||
|
continue |
||||
|
} |
||||
|
s3cfg.Groups = append(s3cfg.Groups, g) |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) saveGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
if group == nil { |
||||
|
return fmt.Errorf("group is nil") |
||||
|
} |
||||
|
return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
||||
|
data, err := json.MarshalIndent(group, "", " ") |
||||
|
if err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return filer.SaveInsideFiler(client, filer.IamConfigDirectory+"/"+IamGroupsDirectory, group.Name+".json", data) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) deleteGroupFile(ctx context.Context, groupName string) error { |
||||
|
return store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
||||
|
resp, err := client.DeleteEntry(ctx, &filer_pb.DeleteEntryRequest{ |
||||
|
Directory: filer.IamConfigDirectory + "/" + IamGroupsDirectory, |
||||
|
Name: groupName + ".json", |
||||
|
}) |
||||
|
if err != nil { |
||||
|
if strings.Contains(err.Error(), filer_pb.ErrNotFound.Error()) { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
if resp != nil && resp.Error != "" { |
||||
|
if strings.Contains(resp.Error, filer_pb.ErrNotFound.Error()) { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
return fmt.Errorf("delete group %s: %s", groupName, resp.Error) |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
existing, err := store.GetGroup(ctx, group.Name) |
||||
|
if err != nil { |
||||
|
if !errors.Is(err, credential.ErrGroupNotFound) { |
||||
|
return err |
||||
|
} |
||||
|
} else if existing != nil { |
||||
|
return credential.ErrGroupAlreadyExists |
||||
|
} |
||||
|
return store.saveGroup(ctx, group) |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) { |
||||
|
var group *iam_pb.Group |
||||
|
err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
||||
|
data, err := filer.ReadInsideFiler(ctx, client, filer.IamConfigDirectory+"/"+IamGroupsDirectory, groupName+".json") |
||||
|
if err != nil { |
||||
|
if err == filer_pb.ErrNotFound { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
if len(data) == 0 { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
group = &iam_pb.Group{} |
||||
|
return json.Unmarshal(data, group) |
||||
|
}) |
||||
|
return group, err |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) DeleteGroup(ctx context.Context, groupName string) error { |
||||
|
if _, err := store.GetGroup(ctx, groupName); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return store.deleteGroupFile(ctx, groupName) |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) ListGroups(ctx context.Context) ([]string, error) { |
||||
|
var names []string |
||||
|
err := store.withFilerClient(func(client filer_pb.SeaweedFilerClient) error { |
||||
|
entries, err := listEntries(ctx, client, filer.IamConfigDirectory+"/"+IamGroupsDirectory) |
||||
|
if err != nil { |
||||
|
if err == filer_pb.ErrNotFound { |
||||
|
return nil |
||||
|
} |
||||
|
return err |
||||
|
} |
||||
|
for _, entry := range entries { |
||||
|
if !entry.IsDirectory && strings.HasSuffix(entry.Name, ".json") { |
||||
|
names = append(names, strings.TrimSuffix(entry.Name, ".json")) |
||||
|
} |
||||
|
} |
||||
|
return nil |
||||
|
}) |
||||
|
return names, err |
||||
|
} |
||||
|
|
||||
|
func (store *FilerEtcStore) UpdateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
if _, err := store.GetGroup(ctx, group.Name); err != nil { |
||||
|
return err |
||||
|
} |
||||
|
return store.saveGroup(ctx, group) |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
package grpc |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/credential" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" |
||||
|
) |
||||
|
|
||||
|
func (store *IamGrpcStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
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 { |
||||
|
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 |
||||
|
} |
||||
@ -0,0 +1,62 @@ |
|||||
|
package memory |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/credential" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" |
||||
|
) |
||||
|
|
||||
|
func (store *MemoryStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
store.mu.Lock() |
||||
|
defer store.mu.Unlock() |
||||
|
|
||||
|
if _, exists := store.groups[group.Name]; exists { |
||||
|
return credential.ErrGroupAlreadyExists |
||||
|
} |
||||
|
store.groups[group.Name] = group |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (store *MemoryStore) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) { |
||||
|
store.mu.RLock() |
||||
|
defer store.mu.RUnlock() |
||||
|
|
||||
|
if g, exists := store.groups[groupName]; exists { |
||||
|
return g, nil |
||||
|
} |
||||
|
return nil, credential.ErrGroupNotFound |
||||
|
} |
||||
|
|
||||
|
func (store *MemoryStore) DeleteGroup(ctx context.Context, groupName string) error { |
||||
|
store.mu.Lock() |
||||
|
defer store.mu.Unlock() |
||||
|
|
||||
|
if _, exists := store.groups[groupName]; !exists { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
delete(store.groups, groupName) |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (store *MemoryStore) ListGroups(ctx context.Context) ([]string, error) { |
||||
|
store.mu.RLock() |
||||
|
defer store.mu.RUnlock() |
||||
|
|
||||
|
var names []string |
||||
|
for name := range store.groups { |
||||
|
names = append(names, name) |
||||
|
} |
||||
|
return names, nil |
||||
|
} |
||||
|
|
||||
|
func (store *MemoryStore) UpdateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
store.mu.Lock() |
||||
|
defer store.mu.Unlock() |
||||
|
|
||||
|
if _, exists := store.groups[group.Name]; !exists { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
store.groups[group.Name] = group |
||||
|
return nil |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
package postgres |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"database/sql" |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
|
||||
|
"github.com/seaweedfs/seaweedfs/weed/credential" |
||||
|
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" |
||||
|
) |
||||
|
|
||||
|
func (store *PostgresStore) CreateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
membersJSON, err := json.Marshal(group.Members) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal members: %w", err) |
||||
|
} |
||||
|
policyNamesJSON, err := json.Marshal(group.PolicyNames) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal policy_names: %w", err) |
||||
|
} |
||||
|
|
||||
|
_, err = store.db.ExecContext(ctx, |
||||
|
`INSERT INTO groups (name, members, policy_names, disabled) VALUES ($1, $2, $3, $4)`, |
||||
|
group.Name, membersJSON, policyNamesJSON, group.Disabled) |
||||
|
if err != nil { |
||||
|
// Check for unique constraint violation
|
||||
|
return fmt.Errorf("failed to create group: %w", err) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (store *PostgresStore) GetGroup(ctx context.Context, groupName string) (*iam_pb.Group, error) { |
||||
|
var membersJSON, policyNamesJSON []byte |
||||
|
var disabled bool |
||||
|
err := store.db.QueryRowContext(ctx, |
||||
|
`SELECT members, policy_names, disabled FROM groups WHERE name = $1`, groupName). |
||||
|
Scan(&membersJSON, &policyNamesJSON, &disabled) |
||||
|
if err != nil { |
||||
|
if err == sql.ErrNoRows { |
||||
|
return nil, credential.ErrGroupNotFound |
||||
|
} |
||||
|
return nil, fmt.Errorf("failed to get group: %w", err) |
||||
|
} |
||||
|
|
||||
|
group := &iam_pb.Group{ |
||||
|
Name: groupName, |
||||
|
Disabled: disabled, |
||||
|
} |
||||
|
if err := json.Unmarshal(membersJSON, &group.Members); err != nil { |
||||
|
return nil, fmt.Errorf("failed to unmarshal members: %w", err) |
||||
|
} |
||||
|
if err := json.Unmarshal(policyNamesJSON, &group.PolicyNames); err != nil { |
||||
|
return nil, fmt.Errorf("failed to unmarshal policy_names: %w", err) |
||||
|
} |
||||
|
return group, nil |
||||
|
} |
||||
|
|
||||
|
func (store *PostgresStore) DeleteGroup(ctx context.Context, groupName string) error { |
||||
|
result, err := store.db.ExecContext(ctx, `DELETE FROM groups WHERE name = $1`, groupName) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to delete group: %w", err) |
||||
|
} |
||||
|
rows, err := result.RowsAffected() |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to get rows affected: %w", err) |
||||
|
} |
||||
|
if rows == 0 { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
|
||||
|
func (store *PostgresStore) ListGroups(ctx context.Context) ([]string, error) { |
||||
|
rows, err := store.db.QueryContext(ctx, `SELECT name FROM groups ORDER BY name`) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("failed to list groups: %w", err) |
||||
|
} |
||||
|
defer rows.Close() |
||||
|
|
||||
|
var names []string |
||||
|
for rows.Next() { |
||||
|
var name string |
||||
|
if err := rows.Scan(&name); err != nil { |
||||
|
return nil, fmt.Errorf("failed to scan group name: %w", err) |
||||
|
} |
||||
|
names = append(names, name) |
||||
|
} |
||||
|
return names, rows.Err() |
||||
|
} |
||||
|
|
||||
|
func (store *PostgresStore) UpdateGroup(ctx context.Context, group *iam_pb.Group) error { |
||||
|
membersJSON, err := json.Marshal(group.Members) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal members: %w", err) |
||||
|
} |
||||
|
policyNamesJSON, err := json.Marshal(group.PolicyNames) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to marshal policy_names: %w", err) |
||||
|
} |
||||
|
|
||||
|
result, err := store.db.ExecContext(ctx, |
||||
|
`UPDATE groups SET members = $1, policy_names = $2, disabled = $3, updated_at = CURRENT_TIMESTAMP WHERE name = $4`, |
||||
|
membersJSON, policyNamesJSON, group.Disabled, group.Name) |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to update group: %w", err) |
||||
|
} |
||||
|
rows, err := result.RowsAffected() |
||||
|
if err != nil { |
||||
|
return fmt.Errorf("failed to get rows affected: %w", err) |
||||
|
} |
||||
|
if rows == 0 { |
||||
|
return credential.ErrGroupNotFound |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue