rbd: add user secret based metadata encryption

This commit adds capability to `metadata` encryption
to be able to fetch `encryptionPassphrase` from user
specified secret name and namespace(if not specified,
will default to namespace where PVC was created).

This behavior is followed if `secretName` key is found
in the encryption configuration else defaults to fetching
`encryptionPassphrase` from storageclass secrets.

Closes: 2107

Signed-off-by: Rakshith R <rar@redhat.com>
This commit is contained in:
Rakshith R 2021-06-21 12:31:22 +05:30 committed by mergify[bot]
parent c4060b8aa2
commit 3352d4aabd

View File

@ -17,6 +17,7 @@ limitations under the License.
package util package util
import ( import (
"context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
@ -26,6 +27,7 @@ import (
"io" "io"
"golang.org/x/crypto/scrypt" "golang.org/x/crypto/scrypt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const ( const (
@ -38,6 +40,13 @@ const (
// kmsTypeSecretsMetadata is the SecretsKMS with per-volume encryption, // kmsTypeSecretsMetadata is the SecretsKMS with per-volume encryption,
// where the DEK is stored in the metadata of the volume itself. // where the DEK is stored in the metadata of the volume itself.
kmsTypeSecretsMetadata = "metadata" kmsTypeSecretsMetadata = "metadata"
// metadataSecretNameKey contains the key which corresponds to the
// kubernetes secret name from where encryptionPassphrase is feteched
metadataSecretNameKey = "secretName"
// metadataSecretNamespaceKey contains the key which corresponds to the
// kubernetes secret namespace from where encryptionPassphrase is feteched
metadataSecretNamespaceKey = "secretNamespace"
) )
// SecretsKMS is default KMS implementation that means no KMS is in use. // SecretsKMS is default KMS implementation that means no KMS is in use.
@ -91,26 +100,77 @@ var _ = RegisterKMSProvider(KMSProvider{
Initializer: initSecretsMetadataKMS, Initializer: initSecretsMetadataKMS,
}) })
// initSecretsMetadataKMS initializes a SecretsMetadataKMS that wraps a // initSecretsMetadataKMS initializes a SecretsMetadataKMS that wraps a SecretsKMS,
// SecretsKMS, so that the passphrase from the StorageClass secrets can be used // 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. // for encrypting/decrypting DEKs that are stored in a detached DEKStore.
func initSecretsMetadataKMS(args KMSInitializerArgs) (EncryptionKMS, error) { func initSecretsMetadataKMS(args KMSInitializerArgs) (EncryptionKMS, error) {
eKMS, err := initSecretsKMS(args.Secrets) var (
smKMS SecretsMetadataKMS
encryptionPassphrase string
ok bool
err error
)
encryptionPassphrase, err = smKMS.fetchEncryptionPassphrase(
args.Config, args.Tenant)
if err != nil { if err != nil {
return nil, err 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}
sKMS, ok := eKMS.(SecretsKMS)
if !ok {
return nil, fmt.Errorf("failed to convert %T to SecretsKMS", eKMS)
}
smKMS := SecretsMetadataKMS{}
smKMS.SecretsKMS = sKMS
return smKMS, nil return smKMS, nil
} }
// fetchEncryptionPassphrase fetches encryptionPassphrase from user provided secret.
func (kms SecretsMetadataKMS) fetchEncryptionPassphrase(
config map[string]interface{},
defaultNamespace string) (string, error) {
var (
secretName string
secretNamespace string
)
err := setConfigString(&secretName, config, metadataSecretNameKey)
if err != nil {
return "", err
}
err = setConfigString(&secretNamespace, config, metadataSecretNamespaceKey)
if err != nil {
if !errors.Is(err, errConfigOptionMissing) {
return "", err
}
// if 'secretNamespace' option is not specified, defaults to namespace in
// which PVC was created
secretNamespace = defaultNamespace
}
c := NewK8sClient()
secret, err := c.CoreV1().Secrets(secretNamespace).Get(context.TODO(),
secretName, metav1.GetOptions{})
if err != nil {
return "", fmt.Errorf("failed to get Secret %s/%s: %w",
secretNamespace, secretName, err)
}
passphraseValue, ok := secret.Data[encryptionPassphraseKey]
if !ok {
return "", fmt.Errorf("missing %q in Secret %s/%s",
encryptionPassphraseKey, secretNamespace, secretName)
}
return string(passphraseValue), nil
}
// Destroy frees all used resources. // Destroy frees all used resources.
func (kms SecretsMetadataKMS) Destroy() { func (kms SecretsMetadataKMS) Destroy() {
kms.SecretsKMS.Destroy() kms.SecretsKMS.Destroy()