diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD index cd50db24e6e..55e6204a6f1 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/BUILD @@ -5,6 +5,7 @@ licenses(["notice"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -17,3 +18,11 @@ go_library( "//vendor/k8s.io/apiserver/pkg/storage/value/encrypt/aes:go_default_library", ], ) + +go_test( + name = "go_default_test", + srcs = ["envelope_test.go"], + library = ":go_default_library", + tags = ["automanaged"], + deps = ["//vendor/k8s.io/apiserver/pkg/storage/value:go_default_library"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go index aa5eae120b6..3ae4ec2b7a0 100644 --- a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope.go @@ -71,10 +71,12 @@ func NewEnvelopeTransformer(envelopeService Service, cacheSize int) (value.Trans // TransformFromStorage decrypts data encrypted by this transformer using envelope encryption. func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Context) ([]byte, bool, error) { - // Read the 16 bit length-of-DEK encoded at the start of the encrypted DEK. + // Read the 16 bit length-of-DEK encoded at the start of the encrypted DEK. 16 bits can + // represent a maximum key length of 65536 bytes. We are using a 256 bit key, whose + // length cannot fit in 8 bits (1 byte). Thus, we use 16 bits (2 bytes) to store the length. keyLen := int(binary.BigEndian.Uint16(data[:2])) if keyLen+2 > len(data) { - return []byte{}, false, fmt.Errorf("invalid data encountered by genvelope transformer, length longer than available bytes: %q", data) + return nil, false, fmt.Errorf("invalid data encountered by genvelope transformer, length longer than available bytes: %q", data) } encKey := string(data[2 : keyLen+2]) encData := data[2+keyLen:] @@ -87,11 +89,11 @@ func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Co } else { key, err := t.envelopeService.Decrypt(encKey) if err != nil { - return []byte{}, false, fmt.Errorf("error while decrypting key: %q", err) + return nil, false, fmt.Errorf("error while decrypting key: %q", err) } transformer, err = t.addTransformer(encKey, key) if err != nil { - return []byte{}, false, err + return nil, false, err } } return transformer.TransformFromStorage(encData, context) @@ -101,17 +103,17 @@ func (t *envelopeTransformer) TransformFromStorage(data []byte, context value.Co func (t *envelopeTransformer) TransformToStorage(data []byte, context value.Context) ([]byte, error) { newKey, err := generateKey(32) if err != nil { - return []byte{}, err + return nil, err } encKey, err := t.envelopeService.Encrypt(newKey) if err != nil { - return []byte{}, err + return nil, err } transformer, err := t.addTransformer(encKey, newKey) if err != nil { - return []byte{}, err + return nil, err } // Append the length of the encrypted DEK as the first 2 bytes. @@ -139,7 +141,6 @@ func (t *envelopeTransformer) addTransformer(encKey string, key []byte) (value.T if err != nil { return nil, err } - transformer := aestransformer.NewCBCTransformer(block) t.transformers.Add(encKey, transformer) return transformer, nil @@ -150,7 +151,7 @@ func generateKey(length int) ([]byte, error) { key := make([]byte, length) _, err := rand.Read(key) if err != nil { - return []byte{}, err + return nil, err } return key, nil diff --git a/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go new file mode 100644 index 00000000000..2832dd5dc78 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/storage/value/encrypt/envelope/envelope_test.go @@ -0,0 +1,143 @@ +/* +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 envelope + +import ( + "bytes" + "encoding/base64" + "fmt" + "strconv" + "strings" + "testing" + + "k8s.io/apiserver/pkg/storage/value" +) + +const ( + testText = "abcdefghijklmnopqrstuvwxyz" + testContextText = "0123456789" + testEnvelopeCacheSize = 10 +) + +// testEnvelopeService is a mock Envelope service which can be used to simulate remote Envelope services +// for testing of Envelope based encryption providers. +type testEnvelopeService struct { + disabled bool + keyVersion string +} + +func (t *testEnvelopeService) Decrypt(data string) ([]byte, error) { + if t.disabled { + return nil, fmt.Errorf("Envelope service was disabled") + } + dataChunks := strings.SplitN(data, ":", 2) + if len(dataChunks) != 2 { + return nil, fmt.Errorf("invalid data encountered for decryption: %s. Missing key version", data) + } + return base64.StdEncoding.DecodeString(dataChunks[1]) +} + +func (t *testEnvelopeService) Encrypt(data []byte) (string, error) { + if t.disabled { + return "", fmt.Errorf("Envelope service was disabled") + } + return t.keyVersion + ":" + base64.StdEncoding.EncodeToString(data), nil +} + +func (t *testEnvelopeService) SetDisabledStatus(status bool) { + t.disabled = status +} + +func (t *testEnvelopeService) Rotate() { + i, _ := strconv.Atoi(t.keyVersion) + t.keyVersion = strconv.FormatInt(int64(i+1), 10) +} + +func newTestEnvelopeService() *testEnvelopeService { + return &testEnvelopeService{ + keyVersion: "1", + } +} + +// Throw error if Envelope transformer tries to contact Envelope without hitting cache. +func TestEnvelopeCaching(t *testing.T) { + envelopeService := newTestEnvelopeService() + envelopeTransformer, err := NewEnvelopeTransformer(envelopeService, testEnvelopeCacheSize) + if err != nil { + t.Fatalf("failed to initialize envelope transformer: %v", err) + } + context := value.DefaultContext([]byte(testContextText)) + originalText := []byte(testText) + + transformedData, err := envelopeTransformer.TransformToStorage(originalText, context) + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data to storage: %s", err) + } + untransformedData, _, err := envelopeTransformer.TransformFromStorage(transformedData, context) + if err != nil { + t.Fatalf("could not decrypt Envelope transformer's encrypted data even once: %v", err) + } + if bytes.Compare(untransformedData, originalText) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly. Expected: %v, got %v", originalText, untransformedData) + } + + envelopeService.SetDisabledStatus(true) + // Subsequent read for the same data should work fine due to caching. + untransformedData, _, err = envelopeTransformer.TransformFromStorage(transformedData, context) + if err != nil { + t.Fatalf("could not decrypt Envelope transformer's encrypted data using just cache: %v", err) + } + if bytes.Compare(untransformedData, originalText) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", originalText, untransformedData) + } +} + +// Makes Envelope transformer hit cache limit, throws error if it misbehaves. +func TestEnvelopeCacheLimit(t *testing.T) { + envelopeTransformer, err := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize) + if err != nil { + t.Fatalf("failed to initialize envelope transformer: %v", err) + } + context := value.DefaultContext([]byte(testContextText)) + + transformedOutputs := map[int][]byte{} + + // Overwrite lots of entries in the map + for i := 0; i < 2*testEnvelopeCacheSize; i++ { + numberText := []byte(strconv.Itoa(i)) + + res, err := envelopeTransformer.TransformToStorage(numberText, context) + transformedOutputs[i] = res + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data (%v) to storage: %s", numberText, err) + } + } + + // Try reading all the data now, ensuring cache misses don't cause a concern. + for i := 0; i < 2*testEnvelopeCacheSize; i++ { + numberText := []byte(strconv.Itoa(i)) + + output, _, err := envelopeTransformer.TransformFromStorage(transformedOutputs[i], context) + if err != nil { + t.Fatalf("envelopeTransformer: error while transforming data (%v) from storage: %s", transformedOutputs[i], err) + } + + if bytes.Compare(numberText, output) != 0 { + t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", numberText, output) + } + } +}