Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rbd: fix encrypted PVC with metadata KMS cannot be deleted #5149

Merged
merged 1 commit into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PendingReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
value [PR](https://github.com/ceph/ceph-csi/pull/4887)
- cephfs: support omap data store in radosnamespace [PR](https://github.com/ceph/ceph-csi/pull/4661)
- helm: Support setting nodepluigin and provisioner annotations
- rbd: fix encrypted PVC with metadata KMS cannot be deleted

## NOTE
7 changes: 5 additions & 2 deletions internal/kms/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,11 @@ func newDefaultTestDummy() EncryptionKMS {

func newSecretsMetadataTestDummy() EncryptionKMS {
smKMS := secretsMetadataKMS{}
smKMS.secretsKMS = secretsKMS{passphrase: base64.URLEncoding.EncodeToString(
[]byte("test dummy passphrase"))}
smKMS.Secrets = map[string]string{
encryptionPassphraseKey: "test dummy passphrase",
}
smKMS.Tenant = "tenant"
smKMS.Config = nil

return smKMS
}
Expand Down
71 changes: 45 additions & 26 deletions internal/kms/secretskms.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (kms secretsKMS) RemoveDEK(ctx context.Context, key string) error {
// secretsMetadataKMS is a KMS based on the secretKMS, but stores the
// Data-Encryption-Key (DEK) in the metadata of the volume.
type secretsMetadataKMS struct {
secretsKMS
ProviderInitArgs
}

var _ = RegisterProvider(Provider{
Expand All @@ -109,28 +109,9 @@ var _ = RegisterProvider(Provider{
// so that the passphrase from the user provided or StorageClass secrets can be used
// for encrypting/decrypting DEKs that are stored in a detached DEKStore.
func initSecretsMetadataKMS(args ProviderInitArgs) (EncryptionKMS, error) {
var (
smKMS secretsMetadataKMS
encryptionPassphrase string
ok bool
err error
)
var smKMS secretsMetadataKMS

encryptionPassphrase, err = smKMS.fetchEncryptionPassphrase(
args.Config, args.Tenant)
if err != nil {
if !errors.Is(err, errConfigOptionMissing) {
return nil, err
}
// if 'userSecret' option is not specified, fetch encryptionPassphrase
// from storageclass secrets.
encryptionPassphrase, ok = args.Secrets[encryptionPassphraseKey]
if !ok {
return nil, fmt.Errorf(
"missing %q in storageclass secret", encryptionPassphraseKey)
}
}
smKMS.secretsKMS = secretsKMS{passphrase: encryptionPassphrase}
smKMS.ProviderInitArgs = args

return smKMS, nil
}
Expand Down Expand Up @@ -184,7 +165,45 @@ func (kms secretsMetadataKMS) fetchEncryptionPassphrase(

// Destroy frees all used resources.
func (kms secretsMetadataKMS) Destroy() {
kms.secretsKMS.Destroy()
// nothing to do
}

// FetchDEK returns passphrase from Kubernetes secrets.
func (kms secretsMetadataKMS) FetchDEK(ctx context.Context, key string) (string, error) {
var (
encryptionPassphrase string
ok bool
err error
)

encryptionPassphrase, err = kms.fetchEncryptionPassphrase(
kms.Config, kms.Tenant)
if err != nil {
if !errors.Is(err, errConfigOptionMissing) {
return "", err
}
// if 'userSecret' option is not specified, fetch encryptionPassphrase
// from storageclass secrets.
encryptionPassphrase, ok = kms.Secrets[encryptionPassphraseKey]
if !ok {
return "", fmt.Errorf(
"missing %q in storageclass secret", encryptionPassphraseKey)
}
}

return encryptionPassphrase, nil
}

// StoreDEK does nothing, as there is no passphrase per key (volume), so
// no need to store is anywhere.
func (kms secretsMetadataKMS) StoreDEK(ctx context.Context, key, value string) error {
return nil
}

// RemoveDEK is doing nothing as no new passphrases are saved with
// secretsKMS.
func (kms secretsMetadataKMS) RemoveDEK(ctx context.Context, key string) error {
return nil
}

func (kms secretsMetadataKMS) RequiresDEKStore() DEKStoreType {
Expand All @@ -208,7 +227,7 @@ type encryptedMetedataDEK struct {
// nonce that was used for encrypting.
func (kms secretsMetadataKMS) EncryptDEK(ctx context.Context, volumeID, plainDEK string) (string, error) {
// use the passphrase from the secretKMS
passphrase, err := kms.secretsKMS.FetchDEK(ctx, volumeID)
passphrase, err := kms.FetchDEK(ctx, volumeID)
if err != nil {
return "", fmt.Errorf("failed to get passphrase: %w", err)
}
Expand Down Expand Up @@ -238,7 +257,7 @@ func (kms secretsMetadataKMS) EncryptDEK(ctx context.Context, volumeID, plainDEK
// fetches secretKMS passphrase to decrypt the DEK.
func (kms secretsMetadataKMS) DecryptDEK(ctx context.Context, volumeID, encryptedDEK string) (string, error) {
// use the passphrase from the secretKMS
passphrase, err := kms.secretsKMS.FetchDEK(ctx, volumeID)
passphrase, err := kms.FetchDEK(ctx, volumeID)
if err != nil {
return "", fmt.Errorf("failed to get passphrase: %w", err)
}
Expand All @@ -265,7 +284,7 @@ func (kms secretsMetadataKMS) DecryptDEK(ctx context.Context, volumeID, encrypte

func (kms secretsMetadataKMS) GetSecret(ctx context.Context, volumeID string) (string, error) {
// use the passphrase from the secretKMS
return kms.secretsKMS.FetchDEK(ctx, volumeID)
return kms.FetchDEK(ctx, volumeID)
}

// generateCipher returns a AEAD cipher based on a passphrase and salt
Expand Down
31 changes: 19 additions & 12 deletions internal/kms/secretskms_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,41 +70,48 @@ func TestInitSecretsMetadataKMS(t *testing.T) {
Secrets: map[string]string{},
}

// passphrase it not set, init should fail
kms, err := initSecretsMetadataKMS(args)
require.Error(t, err)
require.Nil(t, kms)

// set a passphrase to get a working KMS
args.Secrets[encryptionPassphraseKey] = "my-passphrase-from-kubernetes"

kms, err = initSecretsMetadataKMS(args)
require.NoError(t, err)
require.NotNil(t, kms)
require.Equal(t, DEKStoreMetadata, kms.RequiresDEKStore())
}

func TestWorkflowSecretsMetadataKMS(t *testing.T) {
t.Parallel()
secrets := map[string]string{
encryptionPassphraseKey: "my-passphrase-from-kubernetes",
}
args := ProviderInitArgs{
Tenant: "tenant",
Config: nil,
Secrets: secrets,
Secrets: map[string]string{},
}
volumeID := "csi-vol-1b00f5f8-b1c1-11e9-8421-9243c1f659f0"

kms, err := initSecretsMetadataKMS(args)
require.NoError(t, err)
require.NotNil(t, kms)
require.Equal(t, DEKStoreMetadata, kms.RequiresDEKStore())

// plainDEK is the (LUKS) passphrase for the volume
plainDEK := "usually created with generateNewEncryptionPassphrase()"

ctx := context.TODO()

// with missing encryptionPassphraseKey, encrypting should fail
_, err = kms.EncryptDEK(ctx, volumeID, plainDEK)
require.Error(t, err)

secrets := map[string]string{
encryptionPassphraseKey: "my-passphrase-from-kubernetes",
}
args = ProviderInitArgs{
Tenant: "tenant",
Config: nil,
Secrets: secrets,
}

kms, err = initSecretsMetadataKMS(args)
require.NoError(t, err)
require.NotNil(t, kms)

encryptedDEK, err := kms.EncryptDEK(ctx, volumeID, plainDEK)
require.NoError(t, err)
require.NotEqual(t, "", encryptedDEK)
Expand Down