diff --git a/pkg/operator/ceph/object/realm/controller.go b/pkg/operator/ceph/object/realm/controller.go index 0fef509a5..df66796e7 100644 --- a/pkg/operator/ceph/object/realm/controller.go +++ b/pkg/operator/ceph/object/realm/controller.go @@ -291,18 +291,22 @@ func (r *ReconcileObjectRealm) createRealmKeys(realm *cephv1.CephObjectRealm) (r return reconcile.Result{}, nil } - // the realm's secret key and access key are randomly generated and then encoded to base64 + // The realm's secret key and access key are randomly generated and then encoded to base64. + // The encoded values are used verbatim as the S3 keys of the realm's system user, so the + // encoding alphabet must be URL-safe: an access key containing '/' breaks AWS SigV4 + // credential scope parsing ("///...") and makes + // "radosgw-admin realm pull" against this realm fail with "(22) Invalid argument". accessKey, err := mgr.GeneratePassword(accessKeyLength, mgr.AccessKey) if err != nil { return reconcile.Result{}, errors.Wrapf(err, "access key failed to generate") } - accessKey = base64.StdEncoding.EncodeToString([]byte(accessKey)) + accessKey = base64.RawURLEncoding.EncodeToString([]byte(accessKey)) secretKey, err := mgr.GeneratePassword(secretKeyLength, mgr.DefaultKey) if err != nil { return reconcile.Result{}, errors.Wrapf(err, "failed to generate secret key") } - secretKey = base64.StdEncoding.EncodeToString([]byte(secretKey)) + secretKey = base64.RawURLEncoding.EncodeToString([]byte(secretKey)) log.NamedDebug(nsName, logger, "creating secrets for new realm %q", realm.Name) diff --git a/pkg/operator/ceph/object/realm/controller_test.go b/pkg/operator/ceph/object/realm/controller_test.go index ea6e23071..7c60498b2 100644 --- a/pkg/operator/ceph/object/realm/controller_test.go +++ b/pkg/operator/ceph/object/realm/controller_test.go @@ -19,6 +19,7 @@ package realm import ( "context" + "regexp" "testing" "time" @@ -30,6 +31,7 @@ import ( "github.com/rook/rook/pkg/operator/test" exectest "github.com/rook/rook/pkg/util/exec/test" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -304,6 +306,30 @@ func TestReconcileObjectRealm_createRealmKeys(t *testing.T) { } }) + t.Run("generated keys are URL-safe", func(t *testing.T) { + // the access key is embedded in the '/'-delimited SigV4 credential scope, so a '/' + // anywhere in it makes "radosgw-admin realm pull" fail with "(22) Invalid argument" + urlSafe := regexp.MustCompile(`^[A-Za-z0-9_-]+$`) + for i := 0; i < 100; i++ { + r := ReconcileObjectRealm{ + context: &clusterd.Context{ + Clientset: k8sfake.NewClientset(), + }, + scheme: scheme, + recorder: events.NewFakeRecorder(50), + } + + res, err := r.createRealmKeys(realm) + require.NoError(t, err) + assert.True(t, res.IsZero()) + + secret, err := r.context.Clientset.CoreV1().Secrets(ns).Get(ctx, realmName+"-keys", metav1.GetOptions{}) + require.NoError(t, err) + assert.Regexp(t, urlSafe, string(secret.Data["access-key"])) + assert.Regexp(t, urlSafe, string(secret.Data["secret-key"])) + } + }) + t.Run("should fail if the secret doesn't have the necessary keys", func(t *testing.T) { secret := &v1.Secret{ TypeMeta: metav1.TypeMeta{