keyring: Fix a panic when decrypting aead with empty RSA block. (#24383)

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.
This commit is contained in:
James Rasell
2024-11-07 16:08:18 +00:00
committed by GitHub
parent 498b29b3cf
commit 316430b094
3 changed files with 65 additions and 1 deletions

3
.changelog/24383.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:bug
keyring: Fixed a panic on server startup when decrypting AEAD key data with empty RSA block
```

View File

@@ -805,7 +805,7 @@ func (e *Encrypter) loadKeyFromStore(path string) (*structs.UnwrappedRootKey, er
// 1.7 an ed25519 key derived from the root key was used instead of an RSA
// key.
var rsaKey []byte
if kekWrapper.WrappedRSAKey != nil {
if kekWrapper.WrappedRSAKey != nil && len(kekWrapper.WrappedRSAKey.Ciphertext) > 0 {
rsaKey, err = wrapper.Decrypt(e.srv.shutdownCtx, kekWrapper.WrappedRSAKey)
if err != nil {
return nil, fmt.Errorf("%w (rsa key): %w", ErrDecryptFailed, err)

View File

@@ -120,6 +120,67 @@ func TestEncrypter_LoadSave(t *testing.T) {
}
// TestEncrypter_loadKeyFromStore_emptyRSA tests a panic seen by some
// operators where the aead key disk file content had an empty RSA block.
func TestEncrypter_loadKeyFromStore_emptyRSA(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)
// Use an artisanally crafted key file.
kek, err := json.Marshal(wrappedKey.KeyEncryptionKey)
must.NoError(t, err)
wrappedDEKCipher, err := json.Marshal(wrappedKey.WrappedDataEncryptionKey.Ciphertext)
must.NoError(t, err)
testData := fmt.Sprintf(`
{
"Meta": {
"KeyID": %q,
"Algorithm": "aes256-gcm",
"CreateTime": 1730000000000000000,
"CreateIndex": 1555555,
"ModifyIndex": 1555555,
"State": "active",
"PublishTime": 0
},
"ProviderID": "aead",
"WrappedDEK": {
"ciphertext": %s,
"key_info": {
"key_id": %q
}
},
"WrappedRSAKey": {},
"KEK": %s
}
`, key.Meta.KeyID, wrappedDEKCipher, key.Meta.KeyID, kek)
path := filepath.Join(tmpDir, key.Meta.KeyID+".nks.json")
err = os.WriteFile(path, []byte(testData), 0o600)
must.NoError(t, err)
unwrappedKey, err := encrypter.loadKeyFromStore(path)
must.NoError(t, err)
must.NotNil(t, unwrappedKey)
}
// TestEncrypter_Restore exercises the entire reload of a keystore,
// including pairing metadata with key material
func TestEncrypter_Restore(t *testing.T) {