Files
my-rook-config/pkg/operator/ceph/object/user.go
T
Sébastien Han 8786b40d64 rgw: do not create the rgw ops user on the secondary cluster
If the cluster where the rgw is started is secondary and not primary,
trying to create the admin ops user will fail with:

```
Please run the command on master zone.
Performing this operation on non-master zone
leads to inconsistent metadata between zones
```

So we need to force the creation regardless, it is fine the creation will
return UserAlreadyExist and then we just read the current user.

Closes: https://github.com/rook/rook/issues/8671
Signed-off-by: Sébastien Han <seb@redhat.com>
2021-09-21 16:58:03 +02:00

200 lines
6.1 KiB
Go

/*
Copyright 2016 The Rook Authors. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package object
import (
"encoding/json"
"strings"
"syscall"
"github.com/ceph/go-ceph/rgw/admin"
"github.com/pkg/errors"
"github.com/rook/rook/pkg/util/exec"
)
const (
RGWErrorNone = iota
RGWErrorUnknown
RGWErrorNotFound
RGWErrorBadData
RGWErrorParse
ErrorCodeFileExists = 17
)
// An ObjectUser defines the details of an object store user.
type ObjectUser struct {
UserID string `json:"userId"`
DisplayName *string `json:"displayName"`
Email *string `json:"email"`
AccessKey *string `json:"accessKey"`
SecretKey *string `json:"secretKey"`
SystemUser bool `json:"systemuser"`
AdminOpsUser bool `json:"adminopsuser"`
MaxBuckets int `json:"max_buckets"`
UserQuota admin.QuotaSpec `json:"user_quota"`
Caps []admin.UserCapSpec `json:"caps"`
}
// func decodeUser(data string) (*ObjectUser, int, error) {
func decodeUser(data string) (*ObjectUser, int, error) {
var user admin.User
err := json.Unmarshal([]byte(data), &user)
if err != nil {
return nil, RGWErrorParse, errors.Wrapf(err, "failed to unmarshal json. %s", data)
}
rookUser := ObjectUser{UserID: user.ID, DisplayName: &user.DisplayName, Email: &user.Email}
if len(user.Caps) > 0 {
rookUser.Caps = user.Caps
}
if user.MaxBuckets != nil {
rookUser.MaxBuckets = *user.MaxBuckets
}
if user.UserQuota.Enabled != nil {
rookUser.UserQuota = user.UserQuota
}
if len(user.Keys) > 0 {
rookUser.AccessKey = &user.Keys[0].AccessKey
rookUser.SecretKey = &user.Keys[0].SecretKey
} else {
return nil, RGWErrorBadData, errors.New("AccessKey and SecretKey are missing")
}
return &rookUser, RGWErrorNone, nil
}
// GetUser returns the user with the given ID.
// The function is used **ONCE** only to provision so the RGW Admin Ops User
// Subsequent interaction with the API will be done with the created user
func GetUser(c *Context, id string) (*ObjectUser, int, error) {
logger.Debugf("getting s3 user %q", id)
// note: err is set for non-existent user but result output is also empty
result, err := runAdminCommand(c, false, "user", "info", "--uid", id)
if strings.Contains(result, "no user info saved") {
return nil, RGWErrorNotFound, errors.New("warn: s3 user not found")
}
if err != nil {
return nil, RGWErrorUnknown, errors.Wrapf(err, "radosgw-admin command err. %s", result)
}
match, err := extractJSON(result)
if err != nil {
return nil, RGWErrorParse, errors.Wrap(err, "failed to get json")
}
return decodeUser(match)
}
// CreateUser creates a new user with the information given.
// The function is used **ONCE** only to provision so the RGW Admin Ops User
// Subsequent interaction with the API will be done with the created user
func CreateUser(c *Context, user ObjectUser, force bool) (*ObjectUser, int, error) {
logger.Debugf("creating s3 user %q", user.UserID)
if strings.TrimSpace(user.UserID) == "" {
return nil, RGWErrorBadData, errors.New("userId cannot be empty")
}
if user.DisplayName == nil {
return nil, RGWErrorBadData, errors.New("displayName is required")
}
args := []string{
"user",
"create",
"--uid", user.UserID,
"--display-name", *user.DisplayName,
}
if user.Email != nil {
args = append(args, "--email", *user.Email)
}
if user.SystemUser {
args = append(args, "--system")
}
if user.AdminOpsUser {
args = append(args, "--caps", rgwAdminOpsUserCaps)
}
if force {
args = append(args, "--yes-i-really-mean-it")
}
result, err := runAdminCommand(c, true, args...)
if err != nil {
if code, err := exec.ExtractExitCode(err); err == nil && code == int(syscall.EEXIST) {
return nil, ErrorCodeFileExists, errors.New("s3 user already exists")
}
if strings.Contains(result, "could not create user: unable to create user, user: ") {
return nil, ErrorCodeFileExists, errors.New("s3 user already exists")
}
if strings.Contains(result, "could not create user: unable to create user, email: ") && strings.Contains(result, " is the email address an existing user") {
return nil, RGWErrorBadData, errors.New("email already in use")
}
if strings.Contains(result, "global_init: unable to open config file from search list") {
return nil, RGWErrorUnknown, errors.New("skipping reconcile since operator is still initializing")
}
// We don't know what happened
return nil, RGWErrorUnknown, errors.Wrapf(err, "failed to create s3 user. %s", result)
}
return decodeUser(result)
}
func ListUserBuckets(c *Context, id string, opts ...string) (string, error) {
args := []string{"bucket", "list", "--uid", id}
if opts != nil {
args = append(args, opts...)
}
result, err := runAdminCommand(c, false, args...)
return result, errors.Wrapf(err, "failed to list buckets for user uid=%q", id)
}
// DeleteUser deletes the user with the given ID.
// Even though we should be using the Admin Ops API, we keep this on purpose until the entire migration is completed
// Used for the dashboard user
func DeleteUser(c *Context, id string, opts ...string) (string, error) {
args := []string{"user", "rm", "--uid", id}
if opts != nil {
args = append(args, opts...)
}
result, err := runAdminCommand(c, false, args...)
if err != nil {
// If User does not exist return success
if code, ok := exec.ExitStatus(err); ok && code == int(syscall.ENOENT) {
return result, nil
}
res, innerErr := ListUserBuckets(c, id)
if innerErr == nil && res != "" && res != "[]" {
return result, errors.Wrapf(err, "s3 user uid=%q have following buckets %q", id, res)
}
}
return result, errors.Wrapf(err, "failed to delete s3 user uid=%q", id)
}