You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

281 lines
8.1 KiB

package shell
import (
"bytes"
"context"
"flag"
"fmt"
"io"
"strings"
"github.com/seaweedfs/seaweedfs/weed/filer"
"github.com/seaweedfs/seaweedfs/weed/pb"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func init() {
Commands = append(Commands, &commandS3Configure{})
}
type commandS3Configure struct {
}
func (c *commandS3Configure) Name() string {
return "s3.configure"
}
func (c *commandS3Configure) Help() string {
return `configure and apply s3 options for each bucket
# see the current configuration file content
s3.configure
# create a new identity with account information
s3.configure -user=username -actions=Read,Write,List,Tagging -buckets=bucket-name -policies=policy1,policy2 -access_key=key -secret_key=secret -account_id=id -account_display_name=name -account_email=email@example.com -apply
`
}
func (c *commandS3Configure) HasTag(CommandTag) bool {
return false
}
func (c *commandS3Configure) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
s3ConfigureCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
actions := s3ConfigureCommand.String("actions", "", "comma separated actions names: Read,Write,List,Tagging,Admin")
user := s3ConfigureCommand.String("user", "", "user name")
buckets := s3ConfigureCommand.String("buckets", "", "bucket name")
accessKey := s3ConfigureCommand.String("access_key", "", "specify the access key")
secretKey := s3ConfigureCommand.String("secret_key", "", "specify the secret key")
accountId := s3ConfigureCommand.String("account_id", "", "specify the account id")
accountDisplayName := s3ConfigureCommand.String("account_display_name", "", "specify the account display name")
accountEmail := s3ConfigureCommand.String("account_email", "", "specify the account email address")
policies := s3ConfigureCommand.String("policies", "", "comma separated policy names")
isDelete := s3ConfigureCommand.Bool("delete", false, "delete users, actions, access keys or policies")
apply := s3ConfigureCommand.Bool("apply", false, "update and apply s3 configuration")
if err = s3ConfigureCommand.Parse(args); err != nil {
return nil
}
// Case 1: List configuration (no user specified)
if *user == "" {
return c.listConfiguration(commandEnv, writer)
}
// Case 2: Modify specific user
var identity *iam_pb.Identity
var isNewUser bool
err = pb.WithGrpcClient(false, 0, func(conn *grpc.ClientConn) error {
client := iam_pb.NewSeaweedIdentityAccessManagementClient(conn)
// Try to get existing user
resp, getErr := client.GetUser(context.Background(), &iam_pb.GetUserRequest{
Username: *user,
})
if getErr == nil {
identity = resp.Identity
if identity == nil {
// Should not happen if err is nil, but handle defensively
isNewUser = true
identity = &iam_pb.Identity{Name: *user}
}
} else {
st, ok := status.FromError(getErr)
if ok && st.Code() == codes.NotFound {
isNewUser = true
identity = &iam_pb.Identity{
Name: *user,
Credentials: []*iam_pb.Credential{},
Actions: []string{},
PolicyNames: []string{},
}
} else {
return fmt.Errorf("failed to get user %s: %v", *user, getErr)
}
}
// Apply changes to identity object
if err := c.applyChanges(identity, isNewUser, actions, buckets, accessKey, secretKey, policies, isDelete, accountId, accountDisplayName, accountEmail); err != nil {
return err
}
// Print changes (Simulation)
var buf bytes.Buffer
filer.ProtoToText(&buf, identity)
fmt.Fprint(writer, buf.String())
fmt.Fprintln(writer)
if !*apply {
infoAboutSimulationMode(writer, *apply, "-apply")
return nil
}
// Apply changes
if *isDelete && *actions == "" && *accessKey == "" && *buckets == "" && *policies == "" {
// Delete User
_, err := client.DeleteUser(context.Background(), &iam_pb.DeleteUserRequest{Username: *user})
return err
} else {
// Create or Update User
if isNewUser {
_, err := client.CreateUser(context.Background(), &iam_pb.CreateUserRequest{Identity: identity})
return err
} else {
_, err := client.UpdateUser(context.Background(), &iam_pb.UpdateUserRequest{Username: *user, Identity: identity})
return err
}
}
}, commandEnv.option.FilerAddress.ToGrpcAddress(), false, commandEnv.option.GrpcDialOption)
return err
}
func (c *commandS3Configure) listConfiguration(commandEnv *CommandEnv, writer io.Writer) error {
return pb.WithGrpcClient(false, 0, func(conn *grpc.ClientConn) error {
client := iam_pb.NewSeaweedIdentityAccessManagementClient(conn)
resp, err := client.GetConfiguration(context.Background(), &iam_pb.GetConfigurationRequest{})
if err != nil {
return err
}
var buf bytes.Buffer
filer.ProtoToText(&buf, resp.Configuration)
fmt.Fprint(writer, buf.String())
fmt.Fprintln(writer)
return nil
}, commandEnv.option.FilerAddress.ToGrpcAddress(), false, commandEnv.option.GrpcDialOption)
}
func (c *commandS3Configure) applyChanges(identity *iam_pb.Identity, isNewUser bool, actions, buckets, accessKey, secretKey, policies *string, isDelete *bool, accountId, accountDisplayName, accountEmail *string) error {
// Helper to update account info
if *accountId != "" || *accountDisplayName != "" || *accountEmail != "" {
if identity.Account == nil {
identity.Account = &iam_pb.Account{}
}
if *accountId != "" {
identity.Account.Id = *accountId
}
if *accountDisplayName != "" {
identity.Account.DisplayName = *accountDisplayName
}
if *accountEmail != "" {
identity.Account.EmailAddress = *accountEmail
}
}
// Prepare lists
var cmdActions []string
if *actions != "" {
for _, action := range strings.Split(*actions, ",") {
if *buckets == "" {
cmdActions = append(cmdActions, action)
} else {
for _, bucket := range strings.Split(*buckets, ",") {
cmdActions = append(cmdActions, fmt.Sprintf("%s:%s", action, bucket))
}
}
}
}
var cmdPolicies []string
if *policies != "" {
for _, policy := range strings.Split(*policies, ",") {
if policy != "" {
cmdPolicies = append(cmdPolicies, policy)
}
}
}
if *isDelete {
// DELETE LOGIC
// Remove Actions
if len(cmdActions) > 0 {
identity.Actions = removeFromSlice(identity.Actions, cmdActions)
}
// Remove Credentials
if *accessKey != "" {
var keepCredentials []*iam_pb.Credential
for _, cred := range identity.Credentials {
if cred.AccessKey != *accessKey {
keepCredentials = append(keepCredentials, cred)
}
}
identity.Credentials = keepCredentials
}
// Remove Policies
if len(cmdPolicies) > 0 {
identity.PolicyNames = removeFromSlice(identity.PolicyNames, cmdPolicies)
}
} else {
// ADD/UPDATE LOGIC
// Add Actions
identity.Actions = addUniqueToSlice(identity.Actions, cmdActions)
// Add/Update Credentials
if *accessKey != "" && identity.Name != "anonymous" {
found := false
for _, cred := range identity.Credentials {
if cred.AccessKey == *accessKey {
found = true
if *secretKey != "" {
cred.SecretKey = *secretKey
}
break
}
}
if !found {
if *secretKey == "" {
return fmt.Errorf("secret_key is required when adding a new access_key")
}
identity.Credentials = append(identity.Credentials, &iam_pb.Credential{
AccessKey: *accessKey,
SecretKey: *secretKey,
})
}
}
// Add Policies
identity.PolicyNames = addUniqueToSlice(identity.PolicyNames, cmdPolicies)
}
return nil
}
// Helper to remove items from a slice
func removeFromSlice(current []string, toRemove []string) []string {
removeSet := make(map[string]struct{}, len(toRemove))
for _, item := range toRemove {
removeSet[item] = struct{}{}
}
var result []string
for _, item := range current {
if _, found := removeSet[item]; !found {
result = append(result, item)
}
}
return result
}
// Helper to add unique items to a slice
func addUniqueToSlice(current []string, toAdd []string) []string {
existingSet := make(map[string]struct{}, len(current))
for _, item := range current {
existingSet[item] = struct{}{}
}
for _, item := range toAdd {
if _, found := existingSet[item]; !found {
current = append(current, item)
}
}
return current
}