From 542906be282ee627aaa46dd429e030a82e46cc70 Mon Sep 17 00:00:00 2001 From: Rakshith R Date: Thu, 23 Feb 2023 16:14:14 +0530 Subject: [PATCH] osd: add rotate-key functionality to rook's key-management cmd This commit adds functionality to be able to rotate key encryption key of encrypted PVC backed OSDs. Necessary changes such as adding update functionality to kms and rbac changes are made as well. Signed-off-by: Rakshith R (cherry picked from commit 71e011f731a6127dda4cefbe9be3b979f5e67f93) --- cmd/rook/secret.go | 52 ++++++++-- .../library/templates/_cluster-role.tpl | 4 +- deploy/examples/common-second-cluster.yaml | 5 + deploy/examples/common.yaml | 4 +- pkg/daemon/ceph/osd/key_rotation.go | 97 +++++++++++++++++++ pkg/operator/ceph/cluster/osd/config.go | 2 +- pkg/operator/ceph/cluster/osd/create.go | 2 +- 7 files changed, 152 insertions(+), 14 deletions(-) create mode 100644 pkg/daemon/ceph/osd/key_rotation.go diff --git a/cmd/rook/secret.go b/cmd/rook/secret.go index 84068b209..e3eaf6b46 100644 --- a/cmd/rook/secret.go +++ b/cmd/rook/secret.go @@ -23,7 +23,9 @@ import ( "github.com/pkg/errors" "github.com/rook/rook/cmd/rook/rook" + "github.com/rook/rook/pkg/clusterd" "github.com/rook/rook/pkg/daemon/ceph/client" + "github.com/rook/rook/pkg/daemon/ceph/osd" "github.com/rook/rook/pkg/daemon/ceph/osd/kms" operator "github.com/rook/rook/pkg/operator/ceph" "github.com/rook/rook/pkg/operator/k8sutil" @@ -37,16 +39,18 @@ var KeyManagementCmd = &cobra.Command{ Use: "key-management", Short: "key-management interacts with a given Key Management System and perform actions.", Long: `The secret sub-command helps interacting with Key Management System. - It can perform various actions such as retrieving the content of a Key Encryption Key.`, + It can perform various actions such as retrieving the content of a Key Encryption Key and + rotating Key Encryption Key.`, } func init() { KeyManagementCmd.AddCommand( cliGetSecret(), + cliRotateSecret(), ) } -func startSecret() *kms.Config { +func startSecret() (*kms.Config, *clusterd.Context) { // Initialize the context ctx, cancel := signal.NotifyContext(context.Background(), operator.ShutdownSignals...) defer cancel() @@ -71,13 +75,15 @@ func startSecret() *kms.Config { rook.TerminateFatal(errors.Wrapf(err, "failed to get ceph cluster in namespace %q", namespace)) } - // Validate connection details - err = kms.ValidateConnectionDetails(ctx, context, &cephCluster.Spec.Security.KeyManagementService, namespace) - if err != nil { - rook.TerminateFatal(errors.Wrap(err, "failed to validate kms connection details")) + if cephCluster.Spec.Security.KeyManagementService.IsEnabled() { + // Validate connection details + err = kms.ValidateConnectionDetails(ctx, context, &cephCluster.Spec.Security.KeyManagementService, namespace) + if err != nil { + rook.TerminateFatal(errors.Wrap(err, "failed to validate kms connection details")) + } } - return kms.NewConfig(context, &cephCluster.Spec, clusterInfo) + return kms.NewConfig(context, &cephCluster.Spec, clusterInfo), context } // cliGetSecret is the Cobra CLI call @@ -98,7 +104,7 @@ func getSecret(cmd *cobra.Command, args []string) { secretName := args[0] secretPath := args[1] - keyManagementService := startSecret() + keyManagementService, _ := startSecret() keyManagementService.ClusterInfo.Context = ctx // Fetch the secret @@ -113,3 +119,33 @@ func getSecret(cmd *cobra.Command, args []string) { rook.TerminateFatal(errors.Wrapf(err, "failed to write secret %q file to %q", secretName, secretPath)) } } + +// cliRotateSecret is the Cobra CLI call +func cliRotateSecret() *cobra.Command { + cmd := &cobra.Command{ + Use: "rotate-key kms-secret-key data-device [metadata-device] [wal-device]", + Short: "Rotate key", + Args: cobra.RangeArgs(2, 4), + Run: rotateSecret, + } + return cmd +} + +// rotateSecret rotates the Key Encryption Key of a given OSD. +// It accepts the name of the secret to rotate, and the path to the +// encrypted devices. +func rotateSecret(cmd *cobra.Command, args []string) { + rook.SetLogLevel() + // Initialize the context + ctx, cancel := signal.NotifyContext(context.Background(), operator.ShutdownSignals...) + defer cancel() + secretName := args[0] + devicePaths := args[1:] + keyManagementService, context := startSecret() + keyManagementService.ClusterInfo.Context = ctx + + err := osd.RotateKeyEncryptionKey(context, keyManagementService, secretName, devicePaths) + if err != nil { + rook.TerminateFatal(errors.Wrapf(err, "failed to rotate secret %q", secretName)) + } +} diff --git a/deploy/charts/library/templates/_cluster-role.tpl b/deploy/charts/library/templates/_cluster-role.tpl index 61b10509c..13de8de58 100644 --- a/deploy/charts/library/templates/_cluster-role.tpl +++ b/deploy/charts/library/templates/_cluster-role.tpl @@ -9,10 +9,10 @@ metadata: namespace: {{ .Release.Namespace }} # namespace:cluster rules: # this is needed for rook's "key-management" CLI to fetch the vault token from the secret when - # validating the connection details + # validating the connection details and for key rotation operations. - apiGroups: [""] resources: ["secrets"] - verbs: ["get"] + verbs: ["get","update"] - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list", "watch", "create", "update", "delete"] diff --git a/deploy/examples/common-second-cluster.yaml b/deploy/examples/common-second-cluster.yaml index f84633397..684868e6c 100644 --- a/deploy/examples/common-second-cluster.yaml +++ b/deploy/examples/common-second-cluster.yaml @@ -166,6 +166,11 @@ metadata: name: rook-ceph-osd namespace: $NAMESPACE rules: + # this is needed for rook's "key-management" CLI to fetch the vault token from the secret when + # validating the connection details and for key rotation operations. + - apiGroups: [""] + resources: ["secrets"] + verbs: ["get", "update"] - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list", "watch", "create", "update", "delete"] diff --git a/deploy/examples/common.yaml b/deploy/examples/common.yaml index 7247ec65e..c27c858e3 100644 --- a/deploy/examples/common.yaml +++ b/deploy/examples/common.yaml @@ -801,10 +801,10 @@ metadata: namespace: rook-ceph # namespace:cluster rules: # this is needed for rook's "key-management" CLI to fetch the vault token from the secret when - # validating the connection details + # validating the connection details and for key rotation operations. - apiGroups: [""] resources: ["secrets"] - verbs: ["get"] + verbs: ["get", "update"] - apiGroups: [""] resources: ["configmaps"] verbs: ["get", "list", "watch", "create", "update", "delete"] diff --git a/pkg/daemon/ceph/osd/key_rotation.go b/pkg/daemon/ceph/osd/key_rotation.go new file mode 100644 index 000000000..0ba6dd638 --- /dev/null +++ b/pkg/daemon/ceph/osd/key_rotation.go @@ -0,0 +1,97 @@ +/* +Copyright 2023 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 osd + +import ( + "github.com/pkg/errors" + "github.com/rook/rook/pkg/clusterd" + "github.com/rook/rook/pkg/daemon/ceph/osd/kms" + oposd "github.com/rook/rook/pkg/operator/ceph/cluster/osd" +) + +const ( + slotZero string = "0" + slotOne string = "1" +) + +func RotateKeyEncryptionKey(context *clusterd.Context, kms *kms.Config, secretName string, devicePaths []string) error { + logger.Info("fetching the current key") + // Fetch the currentKey. + currentKey, err := kms.GetSecret(secretName) + if err != nil { + return errors.Wrapf(err, "failed to get secret %q", secretName) + } + + // Ensure currentKey is in slot 1. + for _, devicePath := range devicePaths { + logger.Infof("adding the current key to slot %q of the device %q", slotOne, devicePath) + err = addEncryptionKey(context, devicePath, currentKey, currentKey, slotOne) + if err != nil { + return errors.Wrapf(err, "failed to add the current key to slot %q of the device", slotOne) + } + } + + logger.Info("generating new key") + // Generate new key. + newKey, err := oposd.GenerateDmCryptKey() + if err != nil { + return errors.Wrapf(err, "failed to generate new key") + } + + // Add newKey to slot 0. + for _, devicePath := range devicePaths { + logger.Infof("removing key slot %q, if found, of the device %q", slotZero, devicePath) + err = removeEncryptionKeySlot(context, devicePath, currentKey, slotZero) + if err != nil { + return errors.Wrapf(err, "failed to remove key slot %q of the device %q", slotZero, devicePath) + } + logger.Infof("adding new key to slot %q of the device %q", slotZero, devicePath) + err = addEncryptionKey(context, devicePath, currentKey, newKey, slotZero) + if err != nil { + return errors.Wrapf(err, "failed to add new key to slot %q of the device %q", slotZero, devicePath) + } + } + + logger.Info("updating the new key in the KMS") + // Update new key. + err = kms.UpdateSecret(secretName, newKey) + if err != nil { + return errors.Wrapf(err, "failed to update secret %q with new key", secretName) + } + + logger.Info("fetching the key from the KMS to verify it.") + // Fetch key to verify its the new key. + keyInKMS, err := kms.GetSecret(secretName) + if err != nil { + return errors.Wrapf(err, "failed to get secret %q", secretName) + } + if keyInKMS != newKey { + return errors.New("failed to verify the new key in the KMS, the fetched key is not the same as the new key") + } + + // Remove old key from slot 1. + for _, devicePath := range devicePaths { + logger.Infof("removing the old key from the slot %q of the device %q", slotOne, devicePath) + err = removeEncryptionKeySlot(context, devicePath, newKey, slotOne) + if err != nil { + return errors.Wrapf(err, "failed to remove the old key from the slot %q of the device %q", slotOne, devicePath) + } + } + logger.Infof("Successfully rotated the key") + + return nil +} diff --git a/pkg/operator/ceph/cluster/osd/config.go b/pkg/operator/ceph/cluster/osd/config.go index ef57ee279..7f97ab501 100644 --- a/pkg/operator/ceph/cluster/osd/config.go +++ b/pkg/operator/ceph/cluster/osd/config.go @@ -58,7 +58,7 @@ func encryptionBlockDestinationCopy(mountPath, blockType string) string { return path.Join(mountPath, blockType) + "-tmp" } -func generateDmCryptKey() (string, error) { +func GenerateDmCryptKey() (string, error) { key, err := mgr.GenerateRandomBytes(dmCryptKeySize) if err != nil { return "", errors.Wrap(err, "failed to generate random bytes") diff --git a/pkg/operator/ceph/cluster/osd/create.go b/pkg/operator/ceph/cluster/osd/create.go index bf38a71ae..9d5d97d3e 100644 --- a/pkg/operator/ceph/cluster/osd/create.go +++ b/pkg/operator/ceph/cluster/osd/create.go @@ -187,7 +187,7 @@ func (c *Cluster) startProvisioningOverPVCs(config *provisionConfig, errs *provi if osdProps.encrypted { // create encryption Kubernetes Secret if the PVC is encrypted - key, err := generateDmCryptKey() + key, err := GenerateDmCryptKey() if err != nil { errMsg := fmt.Sprintf("failed to generate dmcrypt key for osd claim %q. %v", osdProps.pvc.ClaimName, err) errs.addError(errMsg)