mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-05 12:26:34 +02:00
agent: add caching encryption package (#10986)
* agent: add caching encryption package * Fix documentation * Add GetKey, GetPersistentKey * Remove chan from interface * Add error to interface * Fix tests
This commit is contained in:
parent
40ecb9b304
commit
e84e7b2d76
18
command/agent/cache/crypto/crypto.go
vendored
Normal file
18
command/agent/cache/crypto/crypto.go
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyID = "root"
|
||||
)
|
||||
|
||||
type KeyManager interface {
|
||||
GetKey() []byte
|
||||
GetPersistentKey() ([]byte, error)
|
||||
Renewable() bool
|
||||
Renewer(context.Context) error
|
||||
Encrypt(context.Context, []byte, []byte) ([]byte, error)
|
||||
Decrypt(context.Context, []byte, []byte) ([]byte, error)
|
||||
}
|
||||
97
command/agent/cache/crypto/k8s.go
vendored
Normal file
97
command/agent/cache/crypto/k8s.go
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
|
||||
wrapping "github.com/hashicorp/go-kms-wrapping"
|
||||
"github.com/hashicorp/go-kms-wrapping/wrappers/aead"
|
||||
)
|
||||
|
||||
var _ KeyManager = (*KubeEncryptionKey)(nil)
|
||||
|
||||
type KubeEncryptionKey struct {
|
||||
renewable bool
|
||||
wrapper *aead.Wrapper
|
||||
}
|
||||
|
||||
// NewK8s returns a new instance of the Kube encryption key. Kubernetes
|
||||
// encryption keys aren't renewable.
|
||||
func NewK8s(existingKey []byte) (*KubeEncryptionKey, error) {
|
||||
k := &KubeEncryptionKey{
|
||||
renewable: false,
|
||||
wrapper: aead.NewWrapper(nil),
|
||||
}
|
||||
|
||||
k.wrapper.SetConfig(map[string]string{"key_id": KeyID})
|
||||
|
||||
var rootKey []byte = nil
|
||||
if len(existingKey) != 0 {
|
||||
if len(existingKey) != 32 {
|
||||
return k, fmt.Errorf("invalid key size, should be 32, got %d", len(existingKey))
|
||||
}
|
||||
rootKey = existingKey
|
||||
}
|
||||
|
||||
if rootKey == nil {
|
||||
newKey := make([]byte, 32)
|
||||
_, err := rand.Read(newKey)
|
||||
if err != nil {
|
||||
return k, err
|
||||
}
|
||||
rootKey = newKey
|
||||
}
|
||||
|
||||
if err := k.wrapper.SetAESGCMKeyBytes(rootKey); err != nil {
|
||||
return k, err
|
||||
}
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// GetKey returns the encryption key in a format optimized for storage.
|
||||
// In k8s we store the key as is, so just return the key stored.
|
||||
func (k *KubeEncryptionKey) GetKey() []byte {
|
||||
return k.wrapper.GetKeyBytes()
|
||||
}
|
||||
|
||||
// GetPersistentKey returns the key which should be stored in the persisent
|
||||
// cache. In k8s we store the key as is, so just return the key stored.
|
||||
func (k *KubeEncryptionKey) GetPersistentKey() ([]byte, error) {
|
||||
return k.wrapper.GetKeyBytes(), nil
|
||||
}
|
||||
|
||||
// Renewable lets the caller know if this encryption key type is
|
||||
// renewable. In Kubernetes the key isn't renewable.
|
||||
func (k *KubeEncryptionKey) Renewable() bool {
|
||||
return k.renewable
|
||||
}
|
||||
|
||||
// Renewer is used when the encryption key type is renewable. Since Kubernetes
|
||||
// keys aren't renewable, returning nothing.
|
||||
func (k *KubeEncryptionKey) Renewer(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Encrypt takes plaintext values and encrypts them using the store key and additional
|
||||
// data. For Kubernetes the AAD should be the service account JWT.
|
||||
func (k *KubeEncryptionKey) Encrypt(ctx context.Context, plaintext, aad []byte) ([]byte, error) {
|
||||
blob, err := k.wrapper.Encrypt(ctx, plaintext, aad)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return blob.Ciphertext, nil
|
||||
}
|
||||
|
||||
// Decrypt takes ciphertext and AAD values and returns the decrypted value. For Kubernetes the AAD
|
||||
// should be the service account JWT.
|
||||
func (k *KubeEncryptionKey) Decrypt(ctx context.Context, ciphertext, aad []byte) ([]byte, error) {
|
||||
blob := &wrapping.EncryptedBlobInfo{
|
||||
Ciphertext: ciphertext,
|
||||
KeyInfo: &wrapping.KeyInfo{
|
||||
KeyID: KeyID,
|
||||
},
|
||||
}
|
||||
return k.wrapper.Decrypt(ctx, blob, aad)
|
||||
}
|
||||
155
command/agent/cache/crypto/k8s_test.go
vendored
Normal file
155
command/agent/cache/crypto/k8s_test.go
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCrypto_KubernetesNewKey(t *testing.T) {
|
||||
k8sKey, err := NewK8s([]byte{})
|
||||
if err != nil {
|
||||
t.Fatalf(fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
key := k8sKey.GetKey()
|
||||
if key == nil {
|
||||
t.Fatalf(fmt.Sprintf("key is nil, it shouldn't be: %s", key))
|
||||
}
|
||||
|
||||
persistentKey, _ := k8sKey.GetPersistentKey()
|
||||
if persistentKey == nil {
|
||||
t.Fatalf(fmt.Sprintf("key is nil, it shouldn't be: %s", persistentKey))
|
||||
}
|
||||
|
||||
if string(key) != string(persistentKey) {
|
||||
t.Fatalf("keys don't match, they should: key: %s, persistentKey: %s", key, persistentKey)
|
||||
}
|
||||
|
||||
plaintextInput := []byte("test")
|
||||
aad := []byte("kubernetes")
|
||||
|
||||
ciphertext, err := k8sKey.Encrypt(nil, plaintextInput, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if ciphertext == nil {
|
||||
t.Fatalf("ciphertext nil, it shouldn't be")
|
||||
}
|
||||
|
||||
plaintext, err := k8sKey.Decrypt(nil, ciphertext, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if string(plaintext) != string(plaintextInput) {
|
||||
t.Fatalf("expected %s, got %s", plaintextInput, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrypto_KubernetesExistingKey(t *testing.T) {
|
||||
rootKey := make([]byte, 32)
|
||||
n, err := rand.Read(rootKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 32 {
|
||||
t.Fatal(n)
|
||||
}
|
||||
|
||||
k8sKey, err := NewK8s(rootKey)
|
||||
if err != nil {
|
||||
t.Fatalf(fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
key := k8sKey.GetKey()
|
||||
if key == nil {
|
||||
t.Fatalf(fmt.Sprintf("key is nil, it shouldn't be: %s", key))
|
||||
}
|
||||
|
||||
if string(key) != string(rootKey) {
|
||||
t.Fatalf(fmt.Sprintf("expected keys to be the same, they weren't: expected: %s, got: %s", rootKey, key))
|
||||
}
|
||||
|
||||
persistentKey, _ := k8sKey.GetPersistentKey()
|
||||
if persistentKey == nil {
|
||||
t.Fatalf("key is nil, it shouldn't be")
|
||||
}
|
||||
|
||||
if string(persistentKey) != string(rootKey) {
|
||||
t.Fatalf(fmt.Sprintf("expected keys to be the same, they weren't: expected: %s, got: %s", rootKey, persistentKey))
|
||||
}
|
||||
|
||||
if string(key) != string(persistentKey) {
|
||||
t.Fatalf(fmt.Sprintf("expected keys to be the same, they weren't: %s %s", rootKey, persistentKey))
|
||||
}
|
||||
|
||||
plaintextInput := []byte("test")
|
||||
aad := []byte("kubernetes")
|
||||
|
||||
ciphertext, err := k8sKey.Encrypt(nil, plaintextInput, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if ciphertext == nil {
|
||||
t.Fatalf("ciphertext nil, it shouldn't be")
|
||||
}
|
||||
|
||||
plaintext, err := k8sKey.Decrypt(nil, ciphertext, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if string(plaintext) != string(plaintextInput) {
|
||||
t.Fatalf("expected %s, got %s", plaintextInput, plaintext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrypto_KubernetesPassGeneratedKey(t *testing.T) {
|
||||
k8sFirstKey, err := NewK8s([]byte{})
|
||||
if err != nil {
|
||||
t.Fatalf(fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
firstPersistentKey := k8sFirstKey.GetKey()
|
||||
if firstPersistentKey == nil {
|
||||
t.Fatalf(fmt.Sprintf("key is nil, it shouldn't be: %s", firstPersistentKey))
|
||||
}
|
||||
|
||||
plaintextInput := []byte("test")
|
||||
aad := []byte("kubernetes")
|
||||
|
||||
ciphertext, err := k8sFirstKey.Encrypt(nil, plaintextInput, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if ciphertext == nil {
|
||||
t.Fatalf("ciphertext nil, it shouldn't be")
|
||||
}
|
||||
|
||||
k8sLoadedKey, err := NewK8s(firstPersistentKey)
|
||||
if err != nil {
|
||||
t.Fatalf(fmt.Sprintf("unexpected error: %s", err))
|
||||
}
|
||||
|
||||
loadedKey, _ := k8sLoadedKey.GetPersistentKey()
|
||||
if loadedKey == nil {
|
||||
t.Fatalf(fmt.Sprintf("key is nil, it shouldn't be: %s", loadedKey))
|
||||
}
|
||||
|
||||
if string(loadedKey) != string(firstPersistentKey) {
|
||||
t.Fatalf(fmt.Sprintf("keys do not match"))
|
||||
}
|
||||
|
||||
plaintext, err := k8sLoadedKey.Decrypt(nil, ciphertext, aad)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
||||
if string(plaintext) != string(plaintextInput) {
|
||||
t.Fatalf("expected %s, got %s", plaintextInput, plaintext)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user