aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/dash/user_management.go
diff options
context:
space:
mode:
Diffstat (limited to 'weed/admin/dash/user_management.go')
-rw-r--r--weed/admin/dash/user_management.go447
1 files changed, 447 insertions, 0 deletions
diff --git a/weed/admin/dash/user_management.go b/weed/admin/dash/user_management.go
new file mode 100644
index 000000000..007faeed8
--- /dev/null
+++ b/weed/admin/dash/user_management.go
@@ -0,0 +1,447 @@
+package dash
+
+import (
+ "bytes"
+ "crypto/rand"
+ "encoding/base64"
+ "fmt"
+ "time"
+
+ "github.com/seaweedfs/seaweedfs/weed/filer"
+ "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
+ "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
+)
+
+// CreateObjectStoreUser creates a new user in identity.json
+func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStoreUser, error) {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ if err != filer_pb.ErrNotFound {
+ return err
+ }
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Check if user already exists
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == req.Username {
+ return nil, fmt.Errorf("user %s already exists", req.Username)
+ }
+ }
+
+ // Create new identity
+ newIdentity := &iam_pb.Identity{
+ Name: req.Username,
+ Actions: req.Actions,
+ }
+
+ // Add account if email is provided
+ if req.Email != "" {
+ newIdentity.Account = &iam_pb.Account{
+ Id: generateAccountId(),
+ DisplayName: req.Username,
+ EmailAddress: req.Email,
+ }
+ }
+
+ // Generate access key if requested
+ var accessKey, secretKey string
+ if req.GenerateKey {
+ accessKey = generateAccessKey()
+ secretKey = generateSecretKey()
+ newIdentity.Credentials = []*iam_pb.Credential{
+ {
+ AccessKey: accessKey,
+ SecretKey: secretKey,
+ },
+ }
+ }
+
+ // Add to configuration
+ s3cfg.Identities = append(s3cfg.Identities, newIdentity)
+
+ // Save configuration
+ err = s.saveS3Configuration(s3cfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to save IAM configuration: %v", err)
+ }
+
+ // Return created user
+ user := &ObjectStoreUser{
+ Username: req.Username,
+ Email: req.Email,
+ AccessKey: accessKey,
+ SecretKey: secretKey,
+ Permissions: req.Actions,
+ }
+
+ return user, nil
+}
+
+// UpdateObjectStoreUser updates an existing user
+func (s *AdminServer) UpdateObjectStoreUser(username string, req UpdateUserRequest) (*ObjectStoreUser, error) {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find and update user
+ var updatedIdentity *iam_pb.Identity
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ updatedIdentity = identity
+ break
+ }
+ }
+
+ if updatedIdentity == nil {
+ return nil, fmt.Errorf("user %s not found", username)
+ }
+
+ // Update actions if provided
+ if len(req.Actions) > 0 {
+ updatedIdentity.Actions = req.Actions
+ }
+
+ // Update email if provided
+ if req.Email != "" {
+ if updatedIdentity.Account == nil {
+ updatedIdentity.Account = &iam_pb.Account{
+ Id: generateAccountId(),
+ DisplayName: username,
+ }
+ }
+ updatedIdentity.Account.EmailAddress = req.Email
+ }
+
+ // Save configuration
+ err = s.saveS3Configuration(s3cfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to save IAM configuration: %v", err)
+ }
+
+ // Return updated user
+ user := &ObjectStoreUser{
+ Username: username,
+ Email: req.Email,
+ Permissions: updatedIdentity.Actions,
+ }
+
+ // Get first access key for display
+ if len(updatedIdentity.Credentials) > 0 {
+ user.AccessKey = updatedIdentity.Credentials[0].AccessKey
+ user.SecretKey = updatedIdentity.Credentials[0].SecretKey
+ }
+
+ return user, nil
+}
+
+// DeleteObjectStoreUser deletes a user from identity.json
+func (s *AdminServer) DeleteObjectStoreUser(username string) error {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find and remove user
+ found := false
+ for i, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ s3cfg.Identities = append(s3cfg.Identities[:i], s3cfg.Identities[i+1:]...)
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ return fmt.Errorf("user %s not found", username)
+ }
+
+ // Save configuration
+ return s.saveS3Configuration(s3cfg)
+}
+
+// GetObjectStoreUserDetails returns detailed information about a user
+func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails, error) {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find user
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ details := &UserDetails{
+ Username: username,
+ Actions: identity.Actions,
+ }
+
+ // Set email from account if available
+ if identity.Account != nil {
+ details.Email = identity.Account.EmailAddress
+ }
+
+ // Convert credentials to access key info
+ for _, cred := range identity.Credentials {
+ details.AccessKeys = append(details.AccessKeys, AccessKeyInfo{
+ AccessKey: cred.AccessKey,
+ SecretKey: cred.SecretKey,
+ CreatedAt: time.Now().AddDate(0, -1, 0), // Mock creation date
+ })
+ }
+
+ return details, nil
+ }
+ }
+
+ return nil, fmt.Errorf("user %s not found", username)
+}
+
+// CreateAccessKey creates a new access key for a user
+func (s *AdminServer) CreateAccessKey(username string) (*AccessKeyInfo, error) {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find user
+ var targetIdentity *iam_pb.Identity
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ targetIdentity = identity
+ break
+ }
+ }
+
+ if targetIdentity == nil {
+ return nil, fmt.Errorf("user %s not found", username)
+ }
+
+ // Generate new access key
+ accessKey := generateAccessKey()
+ secretKey := generateSecretKey()
+
+ newCredential := &iam_pb.Credential{
+ AccessKey: accessKey,
+ SecretKey: secretKey,
+ }
+
+ // Add to user's credentials
+ targetIdentity.Credentials = append(targetIdentity.Credentials, newCredential)
+
+ // Save configuration
+ err = s.saveS3Configuration(s3cfg)
+ if err != nil {
+ return nil, fmt.Errorf("failed to save IAM configuration: %v", err)
+ }
+
+ return &AccessKeyInfo{
+ AccessKey: accessKey,
+ SecretKey: secretKey,
+ CreatedAt: time.Now(),
+ }, nil
+}
+
+// DeleteAccessKey deletes an access key for a user
+func (s *AdminServer) DeleteAccessKey(username, accessKeyId string) error {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find user and remove access key
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ for i, cred := range identity.Credentials {
+ if cred.AccessKey == accessKeyId {
+ identity.Credentials = append(identity.Credentials[:i], identity.Credentials[i+1:]...)
+ return s.saveS3Configuration(s3cfg)
+ }
+ }
+ return fmt.Errorf("access key %s not found for user %s", accessKeyId, username)
+ }
+ }
+
+ return fmt.Errorf("user %s not found", username)
+}
+
+// GetUserPolicies returns the policies for a user (actions)
+func (s *AdminServer) GetUserPolicies(username string) ([]string, error) {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return nil, fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find user and return policies
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ return identity.Actions, nil
+ }
+ }
+
+ return nil, fmt.Errorf("user %s not found", username)
+}
+
+// UpdateUserPolicies updates the policies (actions) for a user
+func (s *AdminServer) UpdateUserPolicies(username string, actions []string) error {
+ s3cfg := &iam_pb.S3ApiConfiguration{}
+
+ // Load existing configuration
+ err := s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ReadEntry(nil, client, filer.IamConfigDirectory, filer.IamIdentityFile, &buf); err != nil {
+ return err
+ }
+ if buf.Len() > 0 {
+ return filer.ParseS3ConfigurationFromBytes(buf.Bytes(), s3cfg)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return fmt.Errorf("failed to load IAM configuration: %v", err)
+ }
+
+ // Find user and update policies
+ for _, identity := range s3cfg.Identities {
+ if identity.Name == username {
+ identity.Actions = actions
+ return s.saveS3Configuration(s3cfg)
+ }
+ }
+
+ return fmt.Errorf("user %s not found", username)
+}
+
+// saveS3Configuration saves the S3 configuration to identity.json
+func (s *AdminServer) saveS3Configuration(s3cfg *iam_pb.S3ApiConfiguration) error {
+ return s.WithFilerClient(func(client filer_pb.SeaweedFilerClient) error {
+ var buf bytes.Buffer
+ if err := filer.ProtoToText(&buf, s3cfg); err != nil {
+ return fmt.Errorf("failed to marshal configuration: %v", err)
+ }
+
+ return filer.SaveInsideFiler(client, filer.IamConfigDirectory, filer.IamIdentityFile, buf.Bytes())
+ })
+}
+
+// Helper functions for generating keys and IDs
+func generateAccessKey() string {
+ // Generate 20-character access key (AWS standard)
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+ b := make([]byte, 20)
+ for i := range b {
+ b[i] = charset[randomInt(len(charset))]
+ }
+ return string(b)
+}
+
+func generateSecretKey() string {
+ // Generate 40-character secret key (AWS standard)
+ b := make([]byte, 30) // 30 bytes = 40 characters in base64
+ rand.Read(b)
+ return base64.StdEncoding.EncodeToString(b)
+}
+
+func generateAccountId() string {
+ // Generate 12-digit account ID
+ b := make([]byte, 8)
+ rand.Read(b)
+ return fmt.Sprintf("%012d", b[0]<<24|b[1]<<16|b[2]<<8|b[3])
+}
+
+func randomInt(max int) int {
+ b := make([]byte, 1)
+ rand.Read(b)
+ return int(b[0]) % max
+}