mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 02:11:09 +00:00
kmsv2: add LocalKEKService
Signed-off-by: Krzysztof Ostrowski <kostrows@redhat.com>
This commit is contained in:
parent
d2f40481d1
commit
711a985217
68
staging/src/k8s.io/kms/encryption/interface.go
Normal file
68
staging/src/k8s.io/kms/encryption/interface.go
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package encryption
|
||||
|
||||
import "context"
|
||||
|
||||
// Store is a simple interface to store and retrieve Transformer. It is expected
|
||||
// to be thread-safe.
|
||||
type Store interface {
|
||||
// Add adds a transformer to the store with its encrypted key as key.
|
||||
Add([]byte, Transformer)
|
||||
// Get returns a transformer from the store by its encrypted key as key.
|
||||
Get([]byte) (Transformer, bool)
|
||||
}
|
||||
|
||||
// CreateTransformer enables the creation of a Transformer based on a key.
|
||||
type CreateTransformer interface {
|
||||
// Transformer creates a transformer with a given key.
|
||||
Transformer(context.Context, []byte) (Transformer, error)
|
||||
// Key creates a key that should match the expectations of Transformer().
|
||||
Key() ([]byte, error)
|
||||
}
|
||||
|
||||
/*
|
||||
Copied from:
|
||||
- "k8s.io/apiserver/pkg/storage/value"
|
||||
- "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
|
||||
*/
|
||||
|
||||
// Transformer allows a value to be transformed before being read from or written to the underlying store. The methods
|
||||
// must be able to undo the transformation caused by the other.
|
||||
type Transformer interface {
|
||||
// TransformFromStorage may transform the provided data from its underlying storage representation or return an error.
|
||||
// Stale is true if the object on disk is stale and a write to etcd should be issued, even if the contents of the object
|
||||
// have not changed.
|
||||
TransformFromStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, stale bool, err error)
|
||||
// TransformToStorage may transform the provided data into the appropriate form in storage or return an error.
|
||||
TransformToStorage(ctx context.Context, data []byte, dataCtx Context) (out []byte, err error)
|
||||
}
|
||||
|
||||
// Context is additional information that a storage transformation may need to verify the data at rest.
|
||||
type Context interface {
|
||||
// AuthenticatedData should return an array of bytes that describes the current value. If the value changes,
|
||||
// the transformer may report the value as unreadable or tampered. This may be nil if no such description exists
|
||||
// or is needed. For additional verification, set this to data that strongly identifies the value, such as
|
||||
// the key and creation version of the stored data.
|
||||
AuthenticatedData() []byte
|
||||
}
|
||||
|
||||
// DefaultContext is a simple implementation of Context for a slice of bytes.
|
||||
type DefaultContext []byte
|
||||
|
||||
// AuthenticatedData returns itself.
|
||||
func (c DefaultContext) AuthenticatedData() []byte { return c }
|
197
staging/src/k8s.io/kms/encryption/service.go
Normal file
197
staging/src/k8s.io/kms/encryption/service.go
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kms/service"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoCipher means that there is no remote kms given and therefore the keys in use can't be protected.
|
||||
ErrNoCipher = errors.New("no remote encryption service was specified")
|
||||
// EmptyContext is an empty slice of bytes.
|
||||
EmptyContext = DefaultContext([]byte{})
|
||||
// LocalKEKID is the key used to store the localKEK in the annotations.
|
||||
LocalKEKID = "kmsv2:local-kek"
|
||||
)
|
||||
|
||||
// LocalKEKService adds an additional KEK layer to reduce calls to the remote
|
||||
// KMS.
|
||||
// The KEKs are stored as transformers in the local store. The encrypted
|
||||
// form of the KEK is used to pick a transformer from the store. The KEKs should
|
||||
// be encrypted by the remote KMS.
|
||||
// There is a distinguished KEK (localKEK), that is generated and used by the
|
||||
// LocalKEKService to encrypt.
|
||||
type LocalKEKService struct {
|
||||
// remoteKMS is the remote kms that is used to encrypt the local KEKs.
|
||||
remoteKMS service.Service
|
||||
// remoteKMSID is the ID that helps remoteKMS to decrypt localKEKID.
|
||||
remoteKMSID string
|
||||
// localKEKID is the localKEK in encrypted form.
|
||||
localKEKID []byte
|
||||
|
||||
// transformers is a store that holds all known transformers.
|
||||
transformers Store
|
||||
// createTransformer creates a new transformer and appropriate keys.
|
||||
createTransformer CreateTransformer
|
||||
// createUID creates a new uid.
|
||||
createUID func() (string, error)
|
||||
}
|
||||
|
||||
// NewLocalKEKService is being initialized with a key that is encrypted by the
|
||||
// remoteService. In the current implementation, the localKEK Service needs to be
|
||||
// restarted by the caller after security thresholds are met.
|
||||
func NewLocalKEKService(
|
||||
ctx context.Context,
|
||||
remoteService service.Service,
|
||||
store Store,
|
||||
createTransformer CreateTransformer,
|
||||
createUID func() (string, error), // TODO add sensible defaults, use functional options
|
||||
) (*LocalKEKService, error) {
|
||||
if remoteService == nil {
|
||||
klog.V(2).InfoS("can't create LocalKEKService without remoteService")
|
||||
return nil, ErrNoCipher
|
||||
}
|
||||
|
||||
key, err := createTransformer.Key()
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("create key", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transformer, err := createTransformer.Transformer(ctx, key)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("create new cipher", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid, err := createUID()
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("create new uid", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
encRes, err := remoteService.Encrypt(ctx, uid, key)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("encrypt with remote", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store.Add(encRes.Ciphertext, transformer)
|
||||
|
||||
return &LocalKEKService{
|
||||
remoteKMSID: encRes.KeyID,
|
||||
remoteKMS: remoteService,
|
||||
localKEKID: encRes.Ciphertext,
|
||||
|
||||
transformers: store,
|
||||
createTransformer: createTransformer,
|
||||
createUID: createUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getTransformer returns the transformer for the given keyID. If the keyID is
|
||||
// not known, the key gets decrypted by the remoteKMS.
|
||||
func (m *LocalKEKService) getTransformer(ctx context.Context, encKey []byte, uid, keyID string) (Transformer, error) {
|
||||
transformer, ok := m.transformers.Get(encKey)
|
||||
if ok {
|
||||
return transformer, nil
|
||||
}
|
||||
|
||||
// Decrypt the unknown key with remote KMS. Plainkey must be treated with secrecy.
|
||||
plainKey, err := m.remoteKMS.Decrypt(ctx, uid, &service.DecryptRequest{
|
||||
Ciphertext: encKey,
|
||||
KeyID: keyID,
|
||||
})
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("decrypt key with remote key", "id", uid, "err", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := m.createTransformer.Transformer(ctx, plainKey)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("create transformer", "id", uid, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Overwrite the plain key with 0s.
|
||||
copy(plainKey, make([]byte, len(plainKey)))
|
||||
|
||||
m.transformers.Add(encKey, t)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts the plaintext with the localKEK.
|
||||
func (m *LocalKEKService) Encrypt(ctx context.Context, uid string, pt []byte) (*service.EncryptResponse, error) {
|
||||
// It could happen that the localKEK is not available, if the store is an expiring cache.
|
||||
transformer, err := m.getTransformer(ctx, m.localKEKID, uid, m.remoteKMSID)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("encrypt plaintext", "id", uid, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ct, err := transformer.TransformToStorage(ctx, pt, EmptyContext)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("encrypt plaintext", "id", uid, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &service.EncryptResponse{
|
||||
Ciphertext: ct,
|
||||
KeyID: m.remoteKMSID,
|
||||
Annotations: map[string][]byte{
|
||||
LocalKEKID: m.localKEKID,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Decrypt attempts to decrypt the ciphertext with the localKEK, a KEK from the
|
||||
// store, or the remote KMS.
|
||||
func (m *LocalKEKService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
encKEK, ok := req.Annotations[LocalKEKID]
|
||||
if !ok {
|
||||
// If there is no local KEK ID in the annotations, we must delegate to remote KMS.
|
||||
pt, err := m.remoteKMS.Decrypt(ctx, uid, req)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("decrypt key with remote key", "id", uid, "err", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
transformer, err := m.getTransformer(ctx, encKEK, uid, req.KeyID)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("decrypt ciphertext", "id", uid, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pt, _, err := transformer.TransformFromStorage(ctx, req.Ciphertext, EmptyContext)
|
||||
if err != nil {
|
||||
klog.V(2).InfoS("decrypt ciphertext with pulled key", "id", uid, "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pt, nil
|
||||
}
|
687
staging/src/k8s.io/kms/encryption/service_test.go
Normal file
687
staging/src/k8s.io/kms/encryption/service_test.go
Normal file
@ -0,0 +1,687 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kms/service"
|
||||
)
|
||||
|
||||
var (
|
||||
errCreateKey = errors.New("can't create key")
|
||||
errCreateTransformer = errors.New("can't create transformer")
|
||||
errCreateUID = errors.New("can't create uid")
|
||||
errRemoteEncrypt = errors.New("can't encrypt with remote kms")
|
||||
errRemoteDecrypt = errors.New("can't decrypt with remote kms")
|
||||
errLocallyEncrypt = errors.New("can't encrypt with local kms")
|
||||
errLocallyDecrypt = errors.New("can't decrypt with local kms")
|
||||
errStoreDecrypt = errors.New("can't decrypt with local store")
|
||||
errWrongValue = errors.New("wrong value")
|
||||
errNotBeCalled = errors.New("should not be called")
|
||||
|
||||
keyID = "id:123"
|
||||
genKey = []byte("genkey:123")
|
||||
storeKey = []byte("storekey:123")
|
||||
anonKey = []byte("anonkey:123")
|
||||
plainKey = []byte("plainkey:123")
|
||||
encryptKey = []byte("enckey:123")
|
||||
decryptKey = []byte("deckey:123")
|
||||
encryptMessage = []byte("encrypt_to_storage")
|
||||
decryptMessage = []byte("decrypt_from_storage")
|
||||
)
|
||||
|
||||
func TestService(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
initTestCases := []struct {
|
||||
name string
|
||||
localKEKService func() (*LocalKEKService, error)
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "should fail if no remote service is given",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
nil, nil, nil, nil,
|
||||
)
|
||||
},
|
||||
err: ErrNoCipher,
|
||||
},
|
||||
{
|
||||
name: "should fail if you can't create a key",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{},
|
||||
nil,
|
||||
&createTransformer{
|
||||
key: func() ([]byte, error) {
|
||||
return nil, errCreateKey
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
},
|
||||
err: errCreateKey,
|
||||
},
|
||||
{
|
||||
name: "should fail if you can't create a transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{},
|
||||
nil,
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(ctx context.Context, key []byte) (Transformer, error) {
|
||||
if !bytes.Equal(key, genKey) {
|
||||
return nil, errWrongValue
|
||||
}
|
||||
|
||||
return nil, errCreateTransformer
|
||||
},
|
||||
},
|
||||
nil,
|
||||
)
|
||||
},
|
||||
err: errCreateTransformer,
|
||||
},
|
||||
{
|
||||
name: "should fail if you can't create a uid",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{},
|
||||
nil,
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
func() (string, error) {
|
||||
return "", errCreateUID
|
||||
},
|
||||
)
|
||||
},
|
||||
err: errCreateUID,
|
||||
},
|
||||
{
|
||||
name: "should fail if you can't encrypt remotely",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: func(ctx context.Context, uid string, key []byte) (*service.EncryptResponse, error) {
|
||||
return nil, errRemoteEncrypt
|
||||
},
|
||||
},
|
||||
nil,
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
err: errRemoteEncrypt,
|
||||
},
|
||||
{
|
||||
name: "should succeed initializing",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{encrypt: remoteEncryptDefault},
|
||||
makeEmptyStore(),
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range initTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
localKEKService, err := tc.localKEKService()
|
||||
if err == tc.err {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if localKEKService == nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
encryptTestCases := []struct {
|
||||
name string
|
||||
localKEKService func() (*LocalKEKService, error)
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "should fail if local kek is not in cache and remote decrypt fails",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, decReq *service.DecryptRequest) ([]byte, error) {
|
||||
return nil, errRemoteDecrypt
|
||||
},
|
||||
},
|
||||
makeEmptyStore(),
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{
|
||||
transformToStorage: func(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
|
||||
return encryptMessage, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
err: errRemoteDecrypt,
|
||||
},
|
||||
{
|
||||
name: "should fail if you can't encrypt locally",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{encrypt: remoteEncryptDefault, decrypt: remoteDecryptShouldNotBeCalled},
|
||||
&testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(encKey []byte) (Transformer, bool) {
|
||||
return &testTransformer{
|
||||
transformToStorage: func(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
|
||||
return nil, errLocallyEncrypt
|
||||
},
|
||||
}, true
|
||||
},
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
err: errLocallyEncrypt,
|
||||
},
|
||||
{
|
||||
name: "should succeed encrypting with stored transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{encrypt: remoteEncryptDefault, decrypt: remoteDecryptShouldNotBeCalled},
|
||||
&testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(encKey []byte) (Transformer, bool) {
|
||||
return &testTransformer{
|
||||
transformToStorage: func(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
|
||||
return encryptMessage, nil
|
||||
},
|
||||
}, true
|
||||
},
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should succeed encrypting with stored transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{encrypt: remoteEncryptDefault, decrypt: remoteDecryptShouldNotBeCalled},
|
||||
&testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(encKey []byte) (Transformer, bool) {
|
||||
return &testTransformer{
|
||||
transformToStorage: func(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
|
||||
return encryptMessage, nil
|
||||
},
|
||||
}, true
|
||||
},
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should succeed encrypting with created transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, decReq *service.DecryptRequest) ([]byte, error) {
|
||||
return encryptKey, nil
|
||||
},
|
||||
},
|
||||
&testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(encKey []byte) (Transformer, bool) {
|
||||
return nil, false
|
||||
},
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{
|
||||
transformToStorage: func(ctx context.Context, data []byte, dataCtx Context) ([]byte, error) {
|
||||
return encryptMessage, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range encryptTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
localKEKService, err := tc.localKEKService()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
encRes, err := localKEKService.Encrypt(context.Background(), "id:999", []byte("message"))
|
||||
if err == tc.err {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(encryptMessage, encRes.Ciphertext) {
|
||||
t.Fatalf("unexpected ciphertext - want: %s, have: %s", encryptMessage, encRes.Ciphertext)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
noAnnotationsDecReq := &service.DecryptRequest{
|
||||
Ciphertext: encryptMessage,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{},
|
||||
}
|
||||
|
||||
anonDecReq := &service.DecryptRequest{
|
||||
Ciphertext: encryptMessage,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{
|
||||
LocalKEKID: anonKey,
|
||||
},
|
||||
}
|
||||
|
||||
decryptTestCases := []struct {
|
||||
name string
|
||||
localKEKService func() (*LocalKEKService, error)
|
||||
decReq *service.DecryptRequest
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "should fail decrypting without annotations, if remote decryption doesn't work",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return nil, errRemoteDecrypt
|
||||
},
|
||||
},
|
||||
makeEmptyStore(),
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: noAnnotationsDecReq,
|
||||
err: errRemoteDecrypt,
|
||||
},
|
||||
{
|
||||
name: "should succeed decrypting without annotations, if remote decryption works",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return decryptMessage, nil
|
||||
},
|
||||
},
|
||||
makeEmptyStore(),
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: noAnnotationsDecReq,
|
||||
},
|
||||
{
|
||||
name: "should fail decrypting, if decrypting with remote KMS doesn't work for unknown key",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return nil, errRemoteDecrypt
|
||||
},
|
||||
},
|
||||
&testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(encKey []byte) (Transformer, bool) { return nil, false },
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{
|
||||
transformFromStorage: func(ctx context.Context, ct []byte, defaultCtx Context) ([]byte, bool, error) {
|
||||
return nil, false, errNotBeCalled
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: anonDecReq,
|
||||
err: errRemoteDecrypt,
|
||||
},
|
||||
{
|
||||
name: "should fail decrypting, if creating a transformer fails with unknown key",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return plainKey, nil
|
||||
},
|
||||
},
|
||||
&testStore{
|
||||
add: func(key []byte, transformer Transformer) {},
|
||||
get: func(key []byte) (Transformer, bool) {
|
||||
return nil, false
|
||||
},
|
||||
},
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(ctx context.Context, key []byte) (Transformer, error) {
|
||||
if bytes.Equal(key, plainKey) {
|
||||
return nil, errCreateTransformer
|
||||
}
|
||||
|
||||
return &testTransformer{}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: &service.DecryptRequest{
|
||||
Ciphertext: encryptMessage,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{
|
||||
LocalKEKID: anonKey,
|
||||
},
|
||||
},
|
||||
err: errCreateTransformer,
|
||||
},
|
||||
{
|
||||
name: "should fail decrypting, if decryption fails with stored transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: remoteDecryptShouldNotBeCalled,
|
||||
},
|
||||
&testStore{
|
||||
add: func(key []byte, transformer Transformer) {},
|
||||
get: func(key []byte) (Transformer, bool) {
|
||||
if !bytes.Equal(key, storeKey) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &testTransformer{
|
||||
transformFromStorage: func(ctx context.Context, ct []byte, defaultCtx Context) ([]byte, bool, error) {
|
||||
return nil, false, errLocallyDecrypt
|
||||
},
|
||||
}, true
|
||||
},
|
||||
},
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: &service.DecryptRequest{
|
||||
Ciphertext: encryptMessage,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{
|
||||
LocalKEKID: storeKey,
|
||||
},
|
||||
},
|
||||
err: errLocallyDecrypt,
|
||||
},
|
||||
{
|
||||
name: "should succeed decrypting, if decryption works with stored transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: remoteDecryptShouldNotBeCalled,
|
||||
},
|
||||
&testStore{
|
||||
add: func(key []byte, transformer Transformer) {},
|
||||
get: func(key []byte) (Transformer, bool) {
|
||||
if !bytes.Equal(key, storeKey) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &testTransformer{
|
||||
transformFromStorage: func(ctx context.Context, ct []byte, defaultCtx Context) ([]byte, bool, error) {
|
||||
return decryptMessage, false, nil
|
||||
},
|
||||
}, true
|
||||
},
|
||||
},
|
||||
&createTransformer{key: genLocalKey, transformer: makeEmptyTransformer},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: &service.DecryptRequest{
|
||||
Ciphertext: encryptMessage,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{
|
||||
LocalKEKID: storeKey,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "should succeed decrypting, with newly created transformer",
|
||||
localKEKService: func() (*LocalKEKService, error) {
|
||||
return NewLocalKEKService(
|
||||
context.Background(),
|
||||
&testService{
|
||||
encrypt: remoteEncryptDefault,
|
||||
decrypt: func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return plainKey, nil
|
||||
},
|
||||
},
|
||||
makeEmptyStore(),
|
||||
&createTransformer{
|
||||
key: genLocalKey,
|
||||
transformer: func(ctx context.Context, key []byte) (Transformer, error) {
|
||||
return &testTransformer{
|
||||
transformFromStorage: func(ctx context.Context, ct []byte, defaultCtx Context) ([]byte, bool, error) {
|
||||
return decryptMessage, false, nil
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
genUID,
|
||||
)
|
||||
},
|
||||
decReq: anonDecReq,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range decryptTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
localKEKService, err := tc.localKEKService()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
pt, err := localKEKService.Decrypt(context.Background(), tc.name, tc.decReq)
|
||||
if err == tc.err {
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !bytes.Equal(pt, decryptMessage) {
|
||||
t.Fatalf("unexpected plaintext - want: %s, have: %s", decryptMessage, pt)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testStore struct {
|
||||
add func([]byte, Transformer)
|
||||
get func([]byte) (Transformer, bool)
|
||||
}
|
||||
|
||||
var _ Store = (*testStore)(nil)
|
||||
|
||||
func (t *testStore) Add(key []byte, transformer Transformer) {
|
||||
t.add(key, transformer)
|
||||
}
|
||||
|
||||
func (t *testStore) Get(key []byte) (Transformer, bool) {
|
||||
return t.get(key)
|
||||
}
|
||||
|
||||
type testTransformer struct {
|
||||
transformFromStorage func(context.Context, []byte, Context) ([]byte, bool, error)
|
||||
transformToStorage func(context.Context, []byte, Context) ([]byte, error)
|
||||
}
|
||||
|
||||
var _ Transformer = (*testTransformer)(nil)
|
||||
|
||||
func (t *testTransformer) TransformFromStorage(ctx context.Context, data []byte, context Context) ([]byte, bool, error) {
|
||||
return t.transformFromStorage(ctx, data, context)
|
||||
}
|
||||
|
||||
func (t *testTransformer) TransformToStorage(ctx context.Context, data []byte, context Context) ([]byte, error) {
|
||||
return t.transformToStorage(ctx, data, context)
|
||||
}
|
||||
|
||||
type createTransformer struct {
|
||||
transformer func(context.Context, []byte) (Transformer, error)
|
||||
key func() ([]byte, error)
|
||||
}
|
||||
|
||||
var _ CreateTransformer = (*createTransformer)(nil)
|
||||
|
||||
func (c *createTransformer) Transformer(ctx context.Context, key []byte) (Transformer, error) {
|
||||
return c.transformer(ctx, key)
|
||||
}
|
||||
|
||||
func (c *createTransformer) Key() ([]byte, error) {
|
||||
return c.key()
|
||||
}
|
||||
|
||||
type testService struct {
|
||||
decrypt func(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error)
|
||||
encrypt func(ctx context.Context, uid string, data []byte) (*service.EncryptResponse, error)
|
||||
status func(ctx context.Context) (*service.StatusResponse, error)
|
||||
}
|
||||
|
||||
var _ service.Service = (*testService)(nil)
|
||||
|
||||
func (s *testService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return s.decrypt(ctx, uid, req)
|
||||
}
|
||||
|
||||
func (s *testService) Encrypt(ctx context.Context, uid string, data []byte) (*service.EncryptResponse, error) {
|
||||
return s.encrypt(ctx, uid, data)
|
||||
}
|
||||
|
||||
func (s *testService) Status(ctx context.Context) (*service.StatusResponse, error) {
|
||||
return s.status(ctx)
|
||||
}
|
||||
|
||||
func genUID() (string, error) {
|
||||
return "id:001", nil
|
||||
}
|
||||
|
||||
func genLocalKey() ([]byte, error) {
|
||||
return genKey, nil
|
||||
}
|
||||
|
||||
func makeEmptyTransformer(context.Context, []byte) (Transformer, error) {
|
||||
return &testTransformer{}, nil
|
||||
}
|
||||
|
||||
func makeEmptyStore() *testStore {
|
||||
return &testStore{
|
||||
add: func([]byte, Transformer) {},
|
||||
get: func(key []byte) (Transformer, bool) {
|
||||
return nil, false
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func remoteEncryptDefault(ctx context.Context, uid string, key []byte) (*service.EncryptResponse, error) {
|
||||
return &service.EncryptResponse{
|
||||
Ciphertext: encryptKey,
|
||||
KeyID: keyID,
|
||||
Annotations: map[string][]byte{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func remoteDecryptShouldNotBeCalled(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
|
||||
return nil, errNotBeCalled
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user