Merge pull request #115677 from aramase/kmsv2-middleware

[KMSv2] implement local KEK service
This commit is contained in:
Kubernetes Prow Robot 2023-02-10 16:21:29 -08:00 committed by GitHub
commit d0db9a959b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 791 additions and 1 deletions

View File

@ -0,0 +1,262 @@
/*
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"
"crypto/aes"
"crypto/rand"
"encoding/base64"
"fmt"
"strings"
"sync"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2"
aestransformer "k8s.io/kms/pkg/encrypt/aes"
"k8s.io/kms/pkg/value"
"k8s.io/kms/service"
"k8s.io/utils/lru"
)
var (
// emptyContext is an empty slice of bytes. This is passed as value.Context to the
// GCM transformer. The grpc interface does not provide any additional authenticated data
// to use with AEAD.
emptyContext = value.DefaultContext([]byte{})
// errInvalidKMSAnnotationKeySuffix is returned when the annotation key suffix is not allowed.
errInvalidKMSAnnotationKeySuffix = fmt.Errorf("annotation keys are not allowed to use %s", referenceSuffix)
// these are var instead of const so that we can set them during tests
localKEKGenerationPollInterval = 1 * time.Second
localKEKGenerationPollTimeout = 5 * time.Minute
)
const (
referenceSuffix = ".reference.encryption.k8s.io"
// referenceKEKAnnotationKey is the key used to store the localKEK in the annotations.
referenceKEKAnnotationKey = "encrypted-kek" + referenceSuffix
numAnnotations = 1
cacheSize = 1_000
// keyLength is the length of the local KEK in bytes.
// This is the same length used for the DEKs generated in kube-apiserver.
keyLength = 32
)
var _ service.Service = &LocalKEKService{}
// LocalKEKService adds an additional KEK layer to reduce calls to the remote
// KMS.
// The local KEK is generated once and stored in the LocalKEKService. This KEK
// is used for all encryption operations. For the decrypt operation, if the encrypted
// local KEK is not found in the cache, the remote KMS is used to decrypt the local KEK.
type LocalKEKService struct {
// remoteKMS is the remote kms that is used to encrypt and decrypt the local KEKs.
remoteKMS service.Service
remoteOnce sync.Once
// transformers is a thread-safe LRU cache which caches decrypted DEKs indexed by their encrypted form.
transformers *lru.Cache
remoteKMSResponse *service.EncryptResponse
localTransformer value.Transformer
localTransformerErr error
}
// NewLocalKEKService is being initialized with a remote KMS service.
// In the current implementation, the localKEK Service needs to be
// restarted by the caller after security thresholds are met.
// TODO(aramase): handle rotation of local KEKs
// - when the keyID in Status() no longer matches the keyID used during encryption
// - when the local KEK has been used for a certain number of times
func NewLocalKEKService(remoteService service.Service) *LocalKEKService {
return &LocalKEKService{
remoteKMS: remoteService,
transformers: lru.New(cacheSize),
}
}
func (m *LocalKEKService) getTransformerForEncryption(uid string) (value.Transformer, *service.EncryptResponse, error) {
// Check if we have a local KEK
// - If exists, use the local KEK for encryption and return
// - Not exists, generate local KEK, encrypt with remote KEK,
// store it in cache encrypt the data and return. This can be
// expensive but only 1 in N calls will incur this additional latency,
// N being number of times local KEK is reused)
m.remoteOnce.Do(func() {
m.localTransformerErr = wait.PollImmediateWithContext(context.Background(), localKEKGenerationPollInterval, localKEKGenerationPollTimeout,
func(ctx context.Context) (done bool, err error) {
key, err := generateKey(keyLength)
if err != nil {
return false, fmt.Errorf("failed to generate local KEK: %w", err)
}
block, err := aes.NewCipher(key)
if err != nil {
return false, fmt.Errorf("failed to create cipher block: %w", err)
}
transformer := aestransformer.NewGCMTransformer(block)
resp, err := m.remoteKMS.Encrypt(ctx, uid, key)
if err != nil {
klog.ErrorS(err, "failed to encrypt local KEK with remote KMS", "uid", uid)
return false, nil
}
if err = validateRemoteKMSResponse(resp); err != nil {
return false, fmt.Errorf("response annotations failed validation: %w", err)
}
m.remoteKMSResponse = copyResponseAndAddLocalKEKAnnotation(resp)
m.localTransformer = transformer
m.transformers.Add(base64.StdEncoding.EncodeToString(resp.Ciphertext), transformer)
return true, nil
})
})
return m.localTransformer, m.remoteKMSResponse, m.localTransformerErr
}
func copyResponseAndAddLocalKEKAnnotation(resp *service.EncryptResponse) *service.EncryptResponse {
annotations := make(map[string][]byte, len(resp.Annotations)+numAnnotations)
for s, bytes := range resp.Annotations {
s := s
bytes := bytes
annotations[s] = bytes
}
annotations[referenceKEKAnnotationKey] = resp.Ciphertext
return &service.EncryptResponse{
// Ciphertext is not set on purpose - it is different per Encrypt call
KeyID: resp.KeyID,
Annotations: annotations,
}
}
// Encrypt encrypts the plaintext with the localKEK.
func (m *LocalKEKService) Encrypt(ctx context.Context, uid string, pt []byte) (*service.EncryptResponse, error) {
transformer, resp, err := m.getTransformerForEncryption(uid)
if err != nil {
klog.V(2).InfoS("encrypt plaintext", "uid", uid, "err", err)
return nil, err
}
ct, err := transformer.TransformToStorage(ctx, pt, emptyContext)
if err != nil {
klog.V(2).InfoS("encrypt plaintext", "uid", uid, "err", err)
return nil, err
}
return &service.EncryptResponse{
Ciphertext: ct,
KeyID: resp.KeyID, // TODO what about rotation ??
Annotations: resp.Annotations,
}, nil
}
func (m *LocalKEKService) getTransformerForDecryption(ctx context.Context, uid string, req *service.DecryptRequest) (value.Transformer, error) {
encKEK := req.Annotations[referenceKEKAnnotationKey]
if _transformer, found := m.transformers.Get(base64.StdEncoding.EncodeToString(encKEK)); found {
return _transformer.(value.Transformer), nil
}
key, err := m.remoteKMS.Decrypt(ctx, uid, &service.DecryptRequest{
Ciphertext: encKEK,
KeyID: req.KeyID,
Annotations: annotationsWithoutReferenceKeys(req.Annotations),
})
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
transformer := aestransformer.NewGCMTransformer(block)
// Overwrite the plain key with 0s.
copy(key, make([]byte, len(key)))
m.transformers.Add(encKEK, transformer)
return transformer, 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) {
if _, ok := req.Annotations[referenceKEKAnnotationKey]; !ok {
return nil, fmt.Errorf("unable to find local KEK for request with uid %q", uid)
}
transformer, err := m.getTransformerForDecryption(ctx, uid, req)
if err != nil {
klog.V(2).InfoS("decrypt ciphertext", "uid", uid, "err", err)
return nil, fmt.Errorf("failed to get transformer for decryption: %w", err)
}
pt, _, err := transformer.TransformFromStorage(ctx, req.Ciphertext, emptyContext)
if err != nil {
klog.V(2).InfoS("decrypt ciphertext with pulled key", "uid", uid, "err", err)
return nil, err
}
return pt, nil
}
// Status returns the status of the remote KMS.
func (m *LocalKEKService) Status(ctx context.Context) (*service.StatusResponse, error) {
// TODO(aramase): the response from the remote KMS is funneled through without any validation/action.
// This needs to handle the case when remote KEK has changed. The local KEK needs to be rotated and
// re-encrypted with the new remote KEK.
return m.remoteKMS.Status(ctx)
}
func annotationsWithoutReferenceKeys(annotations map[string][]byte) map[string][]byte {
if len(annotations) <= numAnnotations {
return nil
}
m := make(map[string][]byte, len(annotations)-numAnnotations)
for k, v := range annotations {
k, v := k, v
if strings.HasSuffix(k, referenceSuffix) {
continue
}
m[k] = v
}
return m
}
func validateRemoteKMSResponse(resp *service.EncryptResponse) error {
// validate annotations don't contain the reference implementation annotations
for k := range resp.Annotations {
if strings.HasSuffix(k, referenceSuffix) {
return errInvalidKMSAnnotationKeySuffix
}
}
return nil
}
// generateKey generates a random key using system randomness.
func generateKey(length int) (key []byte, err error) {
key = make([]byte, length)
if _, err = rand.Read(key); err != nil {
return nil, err
}
return key, nil
}

View File

@ -0,0 +1,394 @@
/*
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"
"encoding/base64"
"errors"
"reflect"
"sync"
"testing"
"time"
"k8s.io/kms/service"
)
func TestCopyResponseAndAddLocalKEKAnnotation(t *testing.T) {
testCases := []struct {
name string
input *service.EncryptResponse
want *service.EncryptResponse
}{
{
name: "annotations is nil",
input: &service.EncryptResponse{
Ciphertext: []byte("encryptedLocalKEK"),
KeyID: "keyID",
Annotations: nil,
},
want: &service.EncryptResponse{
KeyID: "keyID",
Annotations: map[string][]byte{
referenceKEKAnnotationKey: []byte("encryptedLocalKEK"),
},
},
},
{
name: "remote KMS sent 1 annotation",
input: &service.EncryptResponse{
Ciphertext: []byte("encryptedLocalKEK"),
KeyID: "keyID",
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
},
},
want: &service.EncryptResponse{
KeyID: "keyID",
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
referenceKEKAnnotationKey: []byte("encryptedLocalKEK"),
},
},
},
{
name: "remote KMS sent 2 annotations",
input: &service.EncryptResponse{
Ciphertext: []byte("encryptedLocalKEK"),
KeyID: "keyID",
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
"key-version.encryption.remote.io": []byte("2"),
},
},
want: &service.EncryptResponse{
KeyID: "keyID",
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
"key-version.encryption.remote.io": []byte("2"),
referenceKEKAnnotationKey: []byte("encryptedLocalKEK"),
},
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := copyResponseAndAddLocalKEKAnnotation(tc.input)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("copyResponseAndAddLocalKEKAnnotation(%v) = %v, want %v", tc.input, got, tc.want)
}
})
}
}
func TestAnnotationsWithoutReferenceKeys(t *testing.T) {
testCases := []struct {
name string
input map[string][]byte
want map[string][]byte
}{
{
name: "annotations is nil",
input: nil,
want: nil,
},
{
name: "annotations is empty",
input: map[string][]byte{},
want: nil,
},
{
name: "annotations only contains reference keys",
input: map[string][]byte{
referenceKEKAnnotationKey: []byte("encryptedLocalKEK"),
},
want: nil,
},
{
name: "annotations contains 1 reference key and 1 other key",
input: map[string][]byte{
referenceKEKAnnotationKey: []byte("encryptedLocalKEK"),
"version.encryption.remote.io": []byte("1"),
},
want: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
},
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := annotationsWithoutReferenceKeys(tc.input)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("annotationsWithoutReferenceKeys(%v) = %v, want %v", tc.input, got, tc.want)
}
})
}
}
func TestValidateRemoteKMSResponse(t *testing.T) {
testCases := []struct {
name string
input *service.EncryptResponse
want error
}{
{
name: "annotations is nil",
input: &service.EncryptResponse{},
want: nil,
},
{
name: "annotation key contains reference suffix",
input: &service.EncryptResponse{
Annotations: map[string][]byte{
"version.reference.encryption.k8s.io": []byte("1"),
},
},
want: errInvalidKMSAnnotationKeySuffix,
},
{
name: "no annotation key contains reference suffix",
input: &service.EncryptResponse{
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
"key-version.encryption.remote.io": []byte("2"),
},
},
want: nil,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
got := validateRemoteKMSResponse(tc.input)
if got != tc.want {
t.Errorf("validateRemoteKMSResponse(%v) = %v, want %v", tc.input, got, tc.want)
}
})
}
}
var _ service.Service = &testRemoteService{}
type testRemoteService struct {
mu sync.Mutex
keyID string
disabled bool
}
func (s *testRemoteService) Encrypt(ctx context.Context, uid string, plaintext []byte) (*service.EncryptResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.disabled {
return nil, errors.New("failed to encrypt")
}
return &service.EncryptResponse{
KeyID: s.keyID,
Ciphertext: []byte(base64.StdEncoding.EncodeToString(plaintext)),
Annotations: map[string][]byte{
"version.encryption.remote.io": []byte("1"),
},
}, nil
}
func (s *testRemoteService) Decrypt(ctx context.Context, uid string, req *service.DecryptRequest) ([]byte, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.disabled {
return nil, errors.New("failed to decrypt")
}
if len(req.Annotations) != 1 {
return nil, errors.New("invalid annotations")
}
if v, ok := req.Annotations["version.encryption.remote.io"]; !ok || string(v) != "1" {
return nil, errors.New("invalid version in annotations")
}
return base64.StdEncoding.DecodeString(string(req.Ciphertext))
}
func (s *testRemoteService) Status(ctx context.Context) (*service.StatusResponse, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.disabled {
return nil, errors.New("failed to get status")
}
return &service.StatusResponse{
Version: "v2alpha1",
Healthz: "ok",
KeyID: s.keyID,
}, nil
}
func (s *testRemoteService) SetDisabledStatus(disabled bool) {
s.mu.Lock()
defer s.mu.Unlock()
s.disabled = true
}
func TestEncrypt(t *testing.T) {
remoteKMS := &testRemoteService{keyID: "test-key-id"}
localKEKService := NewLocalKEKService(remoteKMS)
validateResponse := func(got *service.EncryptResponse, t *testing.T) {
if len(got.Annotations) != 2 {
t.Fatalf("Encrypt() annotations = %v, want 2 annotations", got.Annotations)
}
if _, ok := got.Annotations[referenceKEKAnnotationKey]; !ok {
t.Fatalf("Encrypt() annotations = %v, want %v", got.Annotations, referenceKEKAnnotationKey)
}
if got.KeyID != remoteKMS.keyID {
t.Fatalf("Encrypt() keyID = %v, want %v", got.KeyID, remoteKMS.keyID)
}
if localKEKService.localTransformer == nil {
t.Fatalf("Encrypt() localTransformer = %v, want non-nil", localKEKService.localTransformer)
}
}
ctx := testContext(t)
// local KEK is generated and encryption is successful
got, err := localKEKService.Encrypt(ctx, "test-uid", []byte("test-plaintext"))
if err != nil {
t.Fatalf("Encrypt() error = %v", err)
}
validateResponse(got, t)
// local KEK is used for encryption even when remote KMS is failing
remoteKMS.SetDisabledStatus(true)
if got, err = localKEKService.Encrypt(ctx, "test-uid", []byte("test-plaintext")); err != nil {
t.Fatalf("Encrypt() error = %v", err)
}
validateResponse(got, t)
}
func TestEncryptError(t *testing.T) {
remoteKMS := &testRemoteService{keyID: "test-key-id"}
localKEKService := NewLocalKEKService(remoteKMS)
ctx := testContext(t)
localKEKGenerationPollTimeout = 5 * time.Second
// first time local KEK generation fails because of remote KMS
remoteKMS.SetDisabledStatus(true)
_, err := localKEKService.Encrypt(ctx, "test-uid", []byte("test-plaintext"))
if err == nil {
t.Fatalf("Encrypt() error = %v, want non-nil", err)
}
if localKEKService.localTransformer != nil {
t.Fatalf("Encrypt() localTransformer = %v, want nil", localKEKService.localTransformer)
}
remoteKMS.SetDisabledStatus(false)
}
func TestDecrypt(t *testing.T) {
remoteKMS := &testRemoteService{keyID: "test-key-id"}
localKEKService := NewLocalKEKService(remoteKMS)
ctx := testContext(t)
// local KEK is generated and encryption/decryption is successful
got, err := localKEKService.Encrypt(ctx, "test-uid", []byte("test-plaintext"))
if err != nil {
t.Fatalf("Encrypt() error = %v", err)
}
if string(got.Ciphertext) == "test-plaintext" {
t.Fatalf("Encrypt() ciphertext = %v, want it to be encrypted", got.Ciphertext)
}
decryptRequest := &service.DecryptRequest{
Ciphertext: got.Ciphertext,
Annotations: got.Annotations,
KeyID: got.KeyID,
}
plaintext, err := localKEKService.Decrypt(ctx, "test-uid", decryptRequest)
if err != nil {
t.Fatalf("Decrypt() error = %v", err)
}
if string(plaintext) != "test-plaintext" {
t.Fatalf("Decrypt() plaintext = %v, want %v", string(plaintext), "test-plaintext")
}
// local KEK is used for decryption even when remote KMS is failing
remoteKMS.SetDisabledStatus(true)
if _, err = localKEKService.Decrypt(ctx, "test-uid", decryptRequest); err != nil {
t.Fatalf("Decrypt() error = %v", err)
}
}
func TestDecryptError(t *testing.T) {
remoteKMS := &testRemoteService{keyID: "test-key-id"}
localKEKService := NewLocalKEKService(remoteKMS)
ctx := testContext(t)
got, err := localKEKService.Encrypt(ctx, "test-uid", []byte("test-plaintext"))
if err != nil {
t.Fatalf("Encrypt() error = %v", err)
}
decryptRequest := &service.DecryptRequest{
Ciphertext: got.Ciphertext,
Annotations: got.Annotations,
KeyID: got.KeyID,
}
// local KEK for decryption not in cache and remote KMS is failing
remoteKMS.SetDisabledStatus(true)
// clear the cache
localKEKService.transformers.Clear()
if _, err = localKEKService.Decrypt(ctx, "test-uid", decryptRequest); err == nil {
t.Fatalf("Decrypt() error = %v, want non-nil", err)
}
}
func TestStatus(t *testing.T) {
remoteKMS := &testRemoteService{keyID: "test-key-id"}
localKEKService := NewLocalKEKService(remoteKMS)
ctx := testContext(t)
got, err := localKEKService.Status(ctx)
if err != nil {
t.Fatalf("Status() error = %v", err)
}
if got.Version != "v2alpha1" {
t.Fatalf("Status() version = %v, want %v", got.Version, "v2alpha1")
}
if got.Healthz != "ok" {
t.Fatalf("Status() healthz = %v, want %v", got.Healthz, "ok")
}
if got.KeyID != "test-key-id" {
t.Fatalf("Status() keyID = %v, want %v", got.KeyID, "test-key-id")
}
// remote KMS is failing
remoteKMS.SetDisabledStatus(true)
if _, err = localKEKService.Status(ctx); err == nil {
t.Fatalf("Status() error = %v, want non-nil", err)
}
}
func testContext(t *testing.T) context.Context {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
return ctx
}

View File

@ -9,6 +9,7 @@ require (
google.golang.org/grpc v1.51.0
k8s.io/apimachinery v0.0.0
k8s.io/klog/v2 v2.80.1
k8s.io/utils v0.0.0-20230209194617-a36077c30491
)
require (
@ -19,7 +20,6 @@ require (
golang.org/x/text v0.6.0 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/protobuf v1.28.1 // indirect
k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect
)
replace (

View File

@ -0,0 +1,85 @@
/*
Copyright 2017 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.
*/
// Vendored from kubernetes/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
// * commit: 90b42f91fd904b71fd52ca9ae55a5de73e6b779a
// * link: https://github.com/kubernetes/kubernetes/blob/90b42f91fd904b71fd52ca9ae55a5de73e6b779a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/aes/aes.go
// Package aes transforms values for storage at rest using AES-GCM.
package aes
import (
"context"
"crypto/cipher"
"crypto/rand"
"fmt"
"k8s.io/kms/pkg/value"
)
// gcm implements AEAD encryption of the provided values given a cipher.Block algorithm.
// The authenticated data provided as part of the value.Context method must match when the same
// value is set to and loaded from storage. In order to ensure that values cannot be copied by
// an attacker from a location under their control, use characteristics of the storage location
// (such as the etcd key) as part of the authenticated data.
//
// Because this mode requires a generated IV and IV reuse is a known weakness of AES-GCM, keys
// must be rotated before a birthday attack becomes feasible. NIST SP 800-38D
// (http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf) recommends using the same
// key with random 96-bit nonces (the default nonce length) no more than 2^32 times, and
// therefore transformers using this implementation *must* ensure they allow for frequent key
// rotation. Future work should include investigation of AES-GCM-SIV as an alternative to
// random nonces.
type gcm struct {
block cipher.Block
}
// NewGCMTransformer takes the given block cipher and performs encryption and decryption on the given
// data.
func NewGCMTransformer(block cipher.Block) value.Transformer {
return &gcm{block: block}
}
func (t *gcm) TransformFromStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, bool, error) {
aead, err := cipher.NewGCM(t.block)
if err != nil {
return nil, false, err
}
nonceSize := aead.NonceSize()
if len(data) < nonceSize {
return nil, false, fmt.Errorf("the stored data was shorter than the required size")
}
result, err := aead.Open(nil, data[:nonceSize], data[nonceSize:], dataCtx.AuthenticatedData())
return result, false, err
}
func (t *gcm) TransformToStorage(ctx context.Context, data []byte, dataCtx value.Context) ([]byte, error) {
aead, err := cipher.NewGCM(t.block)
if err != nil {
return nil, err
}
nonceSize := aead.NonceSize()
result := make([]byte, nonceSize+aead.Overhead()+len(data))
n, err := rand.Read(result[:nonceSize])
if err != nil {
return nil, err
}
if n != nonceSize {
return nil, fmt.Errorf("unable to read sufficient random bytes")
}
cipherText := aead.Seal(result[nonceSize:nonceSize], result[:nonceSize], data, dataCtx.AuthenticatedData())
return result[:nonceSize+len(cipherText)], nil
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2017 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 value
import "context"
// Vendored from kubernetes/staging/src/k8s.io/apiserver/pkg/storage/value/transformer.go
// * commit: 59e1a32fc8ed35e328a3971d3a1d640ffc28ff55
// * link: https://github.com/kubernetes/kubernetes/blob/59e1a32fc8ed35e328a3971d3a1d640ffc28ff55/staging/src/k8s.io/apiserver/pkg/storage/value/transformer.go
// 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 }