k8s.io/apiserver@v0.31.1/pkg/storage/value/encrypt/envelope/envelope_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package envelope
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"crypto/aes"
    23  	"crypto/cipher"
    24  	"encoding/base64"
    25  	"encoding/binary"
    26  	"fmt"
    27  	"strconv"
    28  	"strings"
    29  	"testing"
    30  
    31  	"k8s.io/apiserver/pkg/storage/value"
    32  	aestransformer "k8s.io/apiserver/pkg/storage/value/encrypt/aes"
    33  )
    34  
    35  const (
    36  	testText              = "abcdefghijklmnopqrstuvwxyz"
    37  	testContextText       = "0123456789"
    38  	testEnvelopeCacheSize = 10
    39  )
    40  
    41  // testEnvelopeService is a mock Envelope service which can be used to simulate remote Envelope services
    42  // for testing of Envelope based encryption providers.
    43  type testEnvelopeService struct {
    44  	disabled   bool
    45  	keyVersion string
    46  }
    47  
    48  func (t *testEnvelopeService) Decrypt(data []byte) ([]byte, error) {
    49  	if t.disabled {
    50  		return nil, fmt.Errorf("Envelope service was disabled")
    51  	}
    52  	dataChunks := strings.SplitN(string(data), ":", 2)
    53  	if len(dataChunks) != 2 {
    54  		return nil, fmt.Errorf("invalid data encountered for decryption: %s. Missing key version", data)
    55  	}
    56  	return base64.StdEncoding.DecodeString(dataChunks[1])
    57  }
    58  
    59  func (t *testEnvelopeService) Encrypt(data []byte) ([]byte, error) {
    60  	if t.disabled {
    61  		return nil, fmt.Errorf("Envelope service was disabled")
    62  	}
    63  	return []byte(t.keyVersion + ":" + base64.StdEncoding.EncodeToString(data)), nil
    64  }
    65  
    66  func (t *testEnvelopeService) SetDisabledStatus(status bool) {
    67  	t.disabled = status
    68  }
    69  
    70  func (t *testEnvelopeService) Rotate() {
    71  	i, _ := strconv.Atoi(t.keyVersion)
    72  	t.keyVersion = strconv.FormatInt(int64(i+1), 10)
    73  }
    74  
    75  func newTestEnvelopeService() *testEnvelopeService {
    76  	return &testEnvelopeService{
    77  		keyVersion: "1",
    78  	}
    79  }
    80  
    81  // Throw error if Envelope transformer tries to contact Envelope without hitting cache.
    82  func TestEnvelopeCaching(t *testing.T) {
    83  	testCases := []struct {
    84  		desc                     string
    85  		cacheSize                int
    86  		simulateKMSPluginFailure bool
    87  		expectedError            string
    88  	}{
    89  		{
    90  			desc:                     "positive cache size should withstand plugin failure",
    91  			cacheSize:                1000,
    92  			simulateKMSPluginFailure: true,
    93  		},
    94  		{
    95  			desc:                     "cache disabled size should not withstand plugin failure",
    96  			cacheSize:                0,
    97  			simulateKMSPluginFailure: true,
    98  			expectedError:            "Envelope service was disabled",
    99  		},
   100  		{
   101  			desc:                     "cache disabled, no plugin failure should succeed",
   102  			cacheSize:                0,
   103  			simulateKMSPluginFailure: false,
   104  		},
   105  	}
   106  
   107  	for _, tt := range testCases {
   108  		t.Run(tt.desc, func(t *testing.T) {
   109  			envelopeService := newTestEnvelopeService()
   110  			cbcTransformer := func(block cipher.Block) (value.Transformer, error) {
   111  				return aestransformer.NewCBCTransformer(block), nil
   112  			}
   113  			envelopeTransformer := NewEnvelopeTransformer(envelopeService, tt.cacheSize, cbcTransformer)
   114  			ctx := context.Background()
   115  			dataCtx := value.DefaultContext(testContextText)
   116  			originalText := []byte(testText)
   117  
   118  			transformedData, err := envelopeTransformer.TransformToStorage(ctx, originalText, dataCtx)
   119  			if err != nil {
   120  				t.Fatalf("envelopeTransformer: error while transforming data to storage: %s", err)
   121  			}
   122  			untransformedData, _, err := envelopeTransformer.TransformFromStorage(ctx, transformedData, dataCtx)
   123  			if err != nil {
   124  				t.Fatalf("could not decrypt Envelope transformer's encrypted data even once: %v", err)
   125  			}
   126  			if !bytes.Equal(untransformedData, originalText) {
   127  				t.Fatalf("envelopeTransformer transformed data incorrectly. Expected: %v, got %v", originalText, untransformedData)
   128  			}
   129  
   130  			envelopeService.SetDisabledStatus(tt.simulateKMSPluginFailure)
   131  			untransformedData, _, err = envelopeTransformer.TransformFromStorage(ctx, transformedData, dataCtx)
   132  			if tt.expectedError != "" {
   133  				if err == nil {
   134  					t.Fatalf("expected error: %v, got nil", tt.expectedError)
   135  				}
   136  				if err.Error() != tt.expectedError {
   137  					t.Fatalf("expected error: %v, got: %v", tt.expectedError, err)
   138  				}
   139  			} else {
   140  				if err != nil {
   141  					t.Fatalf("unexpected error: %v", err)
   142  				}
   143  				if !bytes.Equal(untransformedData, originalText) {
   144  					t.Fatalf("envelopeTransformer transformed data incorrectly. Expected: %v, got %v", originalText, untransformedData)
   145  				}
   146  			}
   147  		})
   148  	}
   149  }
   150  
   151  // Makes Envelope transformer hit cache limit, throws error if it misbehaves.
   152  func TestEnvelopeCacheLimit(t *testing.T) {
   153  	cbcTransformer := func(block cipher.Block) (value.Transformer, error) {
   154  		return aestransformer.NewCBCTransformer(block), nil
   155  	}
   156  	envelopeTransformer := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, cbcTransformer)
   157  	ctx := context.Background()
   158  	dataCtx := value.DefaultContext(testContextText)
   159  
   160  	transformedOutputs := map[int][]byte{}
   161  
   162  	// Overwrite lots of entries in the map
   163  	for i := 0; i < 2*testEnvelopeCacheSize; i++ {
   164  		numberText := []byte(strconv.Itoa(i))
   165  
   166  		res, err := envelopeTransformer.TransformToStorage(ctx, numberText, dataCtx)
   167  		transformedOutputs[i] = res
   168  		if err != nil {
   169  			t.Fatalf("envelopeTransformer: error while transforming data (%v) to storage: %s", numberText, err)
   170  		}
   171  	}
   172  
   173  	// Try reading all the data now, ensuring cache misses don't cause a concern.
   174  	for i := 0; i < 2*testEnvelopeCacheSize; i++ {
   175  		numberText := []byte(strconv.Itoa(i))
   176  
   177  		output, _, err := envelopeTransformer.TransformFromStorage(ctx, transformedOutputs[i], dataCtx)
   178  		if err != nil {
   179  			t.Fatalf("envelopeTransformer: error while transforming data (%v) from storage: %s", transformedOutputs[i], err)
   180  		}
   181  
   182  		if !bytes.Equal(numberText, output) {
   183  			t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", numberText, output)
   184  		}
   185  	}
   186  }
   187  
   188  func BenchmarkEnvelopeCBCRead(b *testing.B) {
   189  	cbcTransformer := func(block cipher.Block) (value.Transformer, error) {
   190  		return aestransformer.NewCBCTransformer(block), nil
   191  	}
   192  	envelopeTransformer := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, cbcTransformer)
   193  	benchmarkRead(b, envelopeTransformer, 1024)
   194  }
   195  
   196  func BenchmarkAESCBCRead(b *testing.B) {
   197  	block, err := aes.NewCipher(bytes.Repeat([]byte("a"), 32))
   198  	if err != nil {
   199  		b.Fatal(err)
   200  	}
   201  
   202  	aesCBCTransformer := aestransformer.NewCBCTransformer(block)
   203  	benchmarkRead(b, aesCBCTransformer, 1024)
   204  }
   205  
   206  func BenchmarkEnvelopeGCMRead(b *testing.B) {
   207  	envelopeTransformer := NewEnvelopeTransformer(newTestEnvelopeService(), testEnvelopeCacheSize, aestransformer.NewGCMTransformer)
   208  	benchmarkRead(b, envelopeTransformer, 1024)
   209  }
   210  
   211  func BenchmarkAESGCMRead(b *testing.B) {
   212  	block, err := aes.NewCipher(bytes.Repeat([]byte("a"), 32))
   213  	if err != nil {
   214  		b.Fatal(err)
   215  	}
   216  
   217  	aesGCMTransformer, err := aestransformer.NewGCMTransformer(block)
   218  	if err != nil {
   219  		b.Fatal(err)
   220  	}
   221  
   222  	benchmarkRead(b, aesGCMTransformer, 1024)
   223  }
   224  
   225  func benchmarkRead(b *testing.B, transformer value.Transformer, valueLength int) {
   226  	ctx := context.Background()
   227  	dataCtx := value.DefaultContext(testContextText)
   228  	v := bytes.Repeat([]byte("0123456789abcdef"), valueLength/16)
   229  
   230  	out, err := transformer.TransformToStorage(ctx, v, dataCtx)
   231  	if err != nil {
   232  		b.Fatal(err)
   233  	}
   234  
   235  	b.ResetTimer()
   236  	for i := 0; i < b.N; i++ {
   237  		from, stale, err := transformer.TransformFromStorage(ctx, out, dataCtx)
   238  		if err != nil {
   239  			b.Fatal(err)
   240  		}
   241  		if stale {
   242  			b.Fatalf("unexpected data: %t %q", stale, from)
   243  		}
   244  	}
   245  	b.StopTimer()
   246  }
   247  
   248  // remove after 1.13
   249  func TestBackwardsCompatibility(t *testing.T) {
   250  	envelopeService := newTestEnvelopeService()
   251  	cbcTransformer := func(block cipher.Block) (value.Transformer, error) {
   252  		return aestransformer.NewCBCTransformer(block), nil
   253  	}
   254  	envelopeTransformerInst := NewEnvelopeTransformer(envelopeService, testEnvelopeCacheSize, cbcTransformer)
   255  	ctx := context.Background()
   256  	dataCtx := value.DefaultContext(testContextText)
   257  	originalText := []byte(testText)
   258  
   259  	transformedData, err := oldTransformToStorage(ctx, envelopeTransformerInst.(*envelopeTransformer), originalText, dataCtx)
   260  	if err != nil {
   261  		t.Fatalf("envelopeTransformer: error while transforming data to storage: %s", err)
   262  	}
   263  	untransformedData, _, err := envelopeTransformerInst.TransformFromStorage(ctx, transformedData, dataCtx)
   264  	if err != nil {
   265  		t.Fatalf("could not decrypt Envelope transformer's encrypted data even once: %v", err)
   266  	}
   267  	if !bytes.Equal(untransformedData, originalText) {
   268  		t.Fatalf("envelopeTransformer transformed data incorrectly. Expected: %v, got %v", originalText, untransformedData)
   269  	}
   270  
   271  	envelopeService.SetDisabledStatus(true)
   272  	// Subsequent read for the same data should work fine due to caching.
   273  	untransformedData, _, err = envelopeTransformerInst.TransformFromStorage(ctx, transformedData, dataCtx)
   274  	if err != nil {
   275  		t.Fatalf("could not decrypt Envelope transformer's encrypted data using just cache: %v", err)
   276  	}
   277  	if !bytes.Equal(untransformedData, originalText) {
   278  		t.Fatalf("envelopeTransformer transformed data incorrectly using cache. Expected: %v, got %v", originalText, untransformedData)
   279  	}
   280  }
   281  
   282  // remove after 1.13
   283  func oldTransformToStorage(ctx context.Context, t *envelopeTransformer, data []byte, dataCtx value.Context) ([]byte, error) {
   284  	newKey, err := generateKey(32)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	encKey, err := t.envelopeService.Encrypt(newKey)
   290  	if err != nil {
   291  		return nil, err
   292  	}
   293  
   294  	transformer, err := t.addTransformer(encKey, newKey)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  
   299  	// Append the length of the encrypted DEK as the first 2 bytes.
   300  	encKeyLen := make([]byte, 2)
   301  	encKeyBytes := []byte(encKey)
   302  	binary.BigEndian.PutUint16(encKeyLen, uint16(len(encKeyBytes)))
   303  
   304  	prefix := append(encKeyLen, encKeyBytes...)
   305  
   306  	prefixedData := make([]byte, len(prefix), len(data)+len(prefix))
   307  	copy(prefixedData, prefix)
   308  	result, err := transformer.TransformToStorage(ctx, data, dataCtx)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	prefixedData = append(prefixedData, result...)
   313  	return prefixedData, nil
   314  }