From 4e7496d2469da56195ef30e6521dbc21569df20f Mon Sep 17 00:00:00 2001 From: James Rasell Date: Tue, 12 Nov 2024 14:26:35 +0000 Subject: [PATCH] keyring: Fix a panic when decrypting aead with empty RSA block. (#24442) Clusters that have gone through several upgrades have be found to include keyring material which has an empty RSA block. In more recent versions of Nomad, an empty RSA block is omitted from being written to disk. This results in the panic not being present. Older versions, however, did not have this struct tag meaning we wrote an empty JSON block which is not accounted for in the current version. --- .changelog/24442.txt | 3 +++ nomad/encrypter.go | 2 +- nomad/encrypter_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 .changelog/24442.txt diff --git a/.changelog/24442.txt b/.changelog/24442.txt new file mode 100644 index 000000000..324bab1a4 --- /dev/null +++ b/.changelog/24442.txt @@ -0,0 +1,3 @@ +```release-note:bug +keyring: Fixed a bug when decrypting aead with an empty RSA block on state upserts +``` diff --git a/nomad/encrypter.go b/nomad/encrypter.go index 64bd4c047..0409f5c30 100644 --- a/nomad/encrypter.go +++ b/nomad/encrypter.go @@ -477,7 +477,7 @@ func (e *Encrypter) decryptWrappedKeyTask(ctx context.Context, cancel context.Ca // Decrypt RSAKey for Workload Identity JWT signing if one exists. Prior to // 1.7 an ed25519 key derived from the root key was used instead of an RSA // key. - if wrappedKey.WrappedRSAKey != nil { + if wrappedKey.WrappedRSAKey != nil && len(wrappedKey.WrappedRSAKey.Ciphertext) > 0 { rsaKey, err = wrapper.Decrypt(e.srv.shutdownCtx, wrappedKey.WrappedRSAKey) if err != nil { err := fmt.Errorf("%w (rsa key): %w", ErrDecryptFailed, err) diff --git a/nomad/encrypter_test.go b/nomad/encrypter_test.go index e539ac1a5..6d866e842 100644 --- a/nomad/encrypter_test.go +++ b/nomad/encrypter_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/go-jose/go-jose/v3/jwt" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc/v2" "github.com/hashicorp/nomad/ci" "github.com/hashicorp/nomad/helper/pointer" @@ -834,3 +835,42 @@ func TestEncrypter_TransitConfigFallback(t *testing.T) { fallbackVaultConfig(providers[2], &config.VaultConfig{}) must.Eq(t, expect, providers[2].Config, must.Sprint("expected fallback to env")) } + +func TestEncrypter_decryptWrappedKeyTask(t *testing.T) { + ci.Parallel(t) + + srv := &Server{ + logger: testlog.HCLogger(t), + config: &Config{}, + } + + tmpDir := t.TempDir() + + key, err := structs.NewUnwrappedRootKey(structs.EncryptionAlgorithmAES256GCM) + must.NoError(t, err) + + encrypter, err := NewEncrypter(srv, tmpDir) + must.NoError(t, err) + + wrappedKey, err := encrypter.encryptDEK(key, &structs.KEKProviderConfig{}) + must.NotNil(t, wrappedKey) + must.NoError(t, err) + + // Purposely empty the RSA key, but do not nil it, so we can test for a + // panic where the key doesn't contain the ciphertext. + wrappedKey.WrappedRSAKey = &wrapping.BlobInfo{} + + provider, ok := encrypter.providerConfigs[string(structs.KEKProviderAEAD)] + must.True(t, ok) + must.NotNil(t, provider) + + KMSWrapper, err := encrypter.newKMSWrapper(provider, key.Meta.KeyID, wrappedKey.KeyEncryptionKey) + must.NoError(t, err) + must.NotNil(t, KMSWrapper) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + err = encrypter.decryptWrappedKeyTask(ctx, cancel, KMSWrapper, provider, key.Meta, wrappedKey) + must.NoError(t, err) +}