diff --git a/weed/admin/dash/admin_data.go b/weed/admin/dash/admin_data.go index 46a7ddb14..3cf6abf16 100644 --- a/weed/admin/dash/admin_data.go +++ b/weed/admin/dash/admin_data.go @@ -80,6 +80,11 @@ type AccessKeyInfo struct { CreatedAt time.Time `json:"created_at"` } +type CreateAccessKeyRequest struct { + AccessKey string `json:"access_key"` + SecretKey string `json:"secret_key"` +} + type UpdateAccessKeyStatusRequest struct { Status string `json:"status" binding:"required"` } diff --git a/weed/admin/dash/user_management.go b/weed/admin/dash/user_management.go index ecae2169b..7832e501f 100644 --- a/weed/admin/dash/user_management.go +++ b/weed/admin/dash/user_management.go @@ -4,13 +4,21 @@ import ( "context" "crypto/rand" "encoding/base64" + "errors" "fmt" + "strings" "time" "github.com/seaweedfs/seaweedfs/weed/credential" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" ) +var ( + ErrAccessKeyInUse = errors.New("access key already in use") + ErrUserNotFound = errors.New("user not found") + ErrInvalidInput = errors.New("invalid input") +) + // CreateObjectStoreUser creates a new user using the credential manager func (s *AdminServer) CreateObjectStoreUser(req CreateUserRequest) (*ObjectStoreUser, error) { if s.credentialManager == nil { @@ -219,7 +227,7 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails, } // CreateAccessKey creates a new access key for a user -func (s *AdminServer) CreateAccessKey(username string) (*AccessKeyInfo, error) { +func (s *AdminServer) CreateAccessKey(username string, req *CreateAccessKeyRequest) (*AccessKeyInfo, error) { if s.credentialManager == nil { return nil, fmt.Errorf("credential manager not available") } @@ -230,14 +238,41 @@ func (s *AdminServer) CreateAccessKey(username string) (*AccessKeyInfo, error) { _, err := s.credentialManager.GetUser(ctx, username) if err != nil { if err == credential.ErrUserNotFound { - return nil, fmt.Errorf("user %s not found", username) + return nil, fmt.Errorf("user %s: %w", username, ErrUserNotFound) } return nil, fmt.Errorf("failed to get user: %w", err) } - // Generate new access key - accessKey := generateAccessKey() - secretKey := generateSecretKey() + if req == nil { + req = &CreateAccessKeyRequest{} + } + + // Validate provided keys + if req.AccessKey != "" && (len(req.AccessKey) < 4 || len(req.AccessKey) > 128) { + return nil, fmt.Errorf("access key must be between 4 and 128 characters: %w", ErrInvalidInput) + } + if req.SecretKey != "" && (len(req.SecretKey) < 8 || len(req.SecretKey) > 128) { + return nil, fmt.Errorf("secret key must be between 8 and 128 characters: %w", ErrInvalidInput) + } + + // Use provided keys or generate new ones + accessKey := req.AccessKey + if accessKey == "" { + accessKey = generateAccessKey() + } + secretKey := req.SecretKey + if secretKey == "" { + secretKey = generateSecretKey() + } + + // Verify access key is globally unique + existingUser, err := s.credentialManager.GetUserByAccessKey(ctx, accessKey) + if existingUser != nil { + return nil, ErrAccessKeyInUse + } + if err != nil && !errors.Is(err, credential.ErrAccessKeyNotFound) && !isNotFoundError(err) { + return nil, fmt.Errorf("failed to check access key uniqueness: %w", err) + } credential := &iam_pb.Credential{ AccessKey: accessKey, @@ -382,6 +417,12 @@ func (s *AdminServer) UpdateUserPolicies(username string, actions []string) erro return nil } +// isNotFoundError checks for "not found" in the error message as a fallback +// for stores (e.g. gRPC) that don't return the credential.ErrAccessKeyNotFound sentinel. +func isNotFoundError(err error) bool { + return err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") +} + // Helper functions for generating keys and IDs func generateAccessKey() string { // Generate 20-character access key (AWS standard) diff --git a/weed/admin/handlers/user_handlers.go b/weed/admin/handlers/user_handlers.go index fa08a71fc..5b8b0443d 100644 --- a/weed/admin/handlers/user_handlers.go +++ b/weed/admin/handlers/user_handlers.go @@ -1,7 +1,9 @@ package handlers import ( + "errors" "fmt" + "io" "net/http" "time" @@ -155,10 +157,30 @@ func (h *UserHandlers) CreateAccessKey(w http.ResponseWriter, r *http.Request) { return } - accessKey, err := h.adminServer.CreateAccessKey(username) + var req *dash.CreateAccessKeyRequest + var body dash.CreateAccessKeyRequest + if err := decodeJSONBody(newJSONMaxReader(w, r), &body); err != nil { + if !errors.Is(err, io.EOF) { + writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error()) + return + } + // Empty body: auto-generate both keys + } else { + req = &body + } + + accessKey, err := h.adminServer.CreateAccessKey(username, req) if err != nil { glog.Errorf("Failed to create access key for user %s: %v", username, err) - writeJSONError(w, http.StatusInternalServerError, "Failed to create access key: "+err.Error()) + if errors.Is(err, dash.ErrAccessKeyInUse) { + writeJSONError(w, http.StatusConflict, err.Error()) + } else if errors.Is(err, dash.ErrUserNotFound) { + writeJSONError(w, http.StatusNotFound, err.Error()) + } else if errors.Is(err, dash.ErrInvalidInput) { + writeJSONError(w, http.StatusBadRequest, err.Error()) + } else { + writeJSONError(w, http.StatusInternalServerError, "Failed to create access key: "+err.Error()) + } return } diff --git a/weed/admin/static/js/admin.js b/weed/admin/static/js/admin.js index 316f9d2a0..ec809f9d5 100644 --- a/weed/admin/static/js/admin.js +++ b/weed/admin/static/js/admin.js @@ -2196,10 +2196,6 @@ function showNewAccessKeyModal(accessKeyData) { Success! Your new access key has been created. -
- - Important: This is the only time the secret key will be displayed. Please save it securely. -
diff --git a/weed/admin/view/app/object_store_users.templ b/weed/admin/view/app/object_store_users.templ index 86715dc54..3773c8797 100644 --- a/weed/admin/view/app/object_store_users.templ +++ b/weed/admin/view/app/object_store_users.templ @@ -442,10 +442,32 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
Create New User
Hold Ctrl/Cmd to select multiple permissions
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
Hold Ctrl/Cmd to select multiple policies
Edit User
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
User Details
Manage Access Keys
Access Keys for
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
Create New User
Hold Ctrl/Cmd to select multiple permissions
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
Hold Ctrl/Cmd to select multiple policies
Edit User
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
User Details
Manage Access Keys
Access Keys for

Leave blank to auto-generate.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }