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

     1  //go:build !windows
     2  // +build !windows
     3  
     4  /*
     5  Copyright 2017 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  // Package envelope transforms values for storage at rest using a Envelope provider
    21  package envelope
    22  
    23  import (
    24  	"context"
    25  	"fmt"
    26  	"reflect"
    27  	"sync"
    28  	"testing"
    29  	"time"
    30  
    31  	mock "k8s.io/apiserver/pkg/storage/value/encrypt/envelope/testing/v1beta1"
    32  
    33  	"k8s.io/apimachinery/pkg/util/uuid"
    34  )
    35  
    36  type testSocket struct {
    37  	path     string
    38  	endpoint string
    39  }
    40  
    41  // newEndpoint constructs a unique name for a Linux Abstract Socket to be used in a test.
    42  // This package uses Linux Domain Sockets to remove the need for clean-up of socket files.
    43  func newEndpoint() *testSocket {
    44  	p := fmt.Sprintf("@%s.sock", uuid.NewUUID())
    45  
    46  	return &testSocket{
    47  		path:     p,
    48  		endpoint: fmt.Sprintf("unix:///%s", p),
    49  	}
    50  }
    51  
    52  // TestKMSPluginLateStart tests the scenario where kms-plugin pod/container starts after kube-apiserver pod/container.
    53  // Since the Dial to kms-plugin is non-blocking we expect the construction of gRPC service to succeed even when
    54  // kms-plugin is not yet up - dialing happens in the background.
    55  func TestKMSPluginLateStart(t *testing.T) {
    56  	t.Parallel()
    57  	callTimeout := 3 * time.Second
    58  	s := newEndpoint()
    59  
    60  	ctx := testContext(t)
    61  
    62  	service, err := NewGRPCService(ctx, s.endpoint, callTimeout)
    63  	if err != nil {
    64  		t.Fatalf("failed to create envelope service, error: %v", err)
    65  	}
    66  	defer destroyService(service)
    67  
    68  	time.Sleep(callTimeout / 2)
    69  	_ = mock.NewBase64Plugin(t, s.path)
    70  
    71  	data := []byte("test data")
    72  	_, err = service.Encrypt(data)
    73  	if err != nil {
    74  		t.Fatalf("failed when execute encrypt, error: %v", err)
    75  	}
    76  }
    77  
    78  // TestTimeout tests behaviour of the kube-apiserver based on the supplied timeout and delayed start of kms-plugin.
    79  func TestTimeouts(t *testing.T) {
    80  	t.Parallel()
    81  	var testCases = []struct {
    82  		desc               string
    83  		callTimeout        time.Duration
    84  		pluginDelay        time.Duration
    85  		kubeAPIServerDelay time.Duration
    86  		wantErr            string
    87  	}{
    88  		{
    89  			desc:        "timeout zero - expect failure when call from kube-apiserver arrives before plugin starts",
    90  			callTimeout: 0 * time.Second,
    91  			pluginDelay: 3 * time.Second,
    92  			wantErr:     "rpc error: code = DeadlineExceeded desc = context deadline exceeded",
    93  		},
    94  		{
    95  			desc:               "timeout zero but kms-plugin already up - still failure - zero timeout is an invalid value",
    96  			callTimeout:        0 * time.Second,
    97  			pluginDelay:        0 * time.Second,
    98  			kubeAPIServerDelay: 2 * time.Second,
    99  			wantErr:            "rpc error: code = DeadlineExceeded desc = context deadline exceeded",
   100  		},
   101  		{
   102  			desc:        "timeout greater than kms-plugin delay - expect success",
   103  			callTimeout: 6 * time.Second,
   104  			pluginDelay: 3 * time.Second,
   105  		},
   106  		{
   107  			desc:        "timeout less than kms-plugin delay - expect failure",
   108  			callTimeout: 3 * time.Second,
   109  			pluginDelay: 6 * time.Second,
   110  			wantErr:     "rpc error: code = DeadlineExceeded desc = context deadline exceeded",
   111  		},
   112  	}
   113  
   114  	for _, tt := range testCases {
   115  		tt := tt
   116  		t.Run(tt.desc, func(t *testing.T) {
   117  			t.Parallel()
   118  			var (
   119  				service         Service
   120  				err             error
   121  				data            = []byte("test data")
   122  				kubeAPIServerWG sync.WaitGroup
   123  				kmsPluginWG     sync.WaitGroup
   124  				testCompletedWG sync.WaitGroup
   125  				socketName      = newEndpoint()
   126  			)
   127  
   128  			testCompletedWG.Add(1)
   129  			defer testCompletedWG.Done()
   130  
   131  			ctx := testContext(t)
   132  
   133  			kubeAPIServerWG.Add(1)
   134  			go func() {
   135  				// Simulating late start of kube-apiserver - plugin is up before kube-apiserver, if requested by the testcase.
   136  				time.Sleep(tt.kubeAPIServerDelay)
   137  
   138  				service, err = NewGRPCService(ctx, socketName.endpoint, tt.callTimeout)
   139  				if err != nil {
   140  					t.Errorf("failed to create envelope service, error: %v", err)
   141  					return
   142  				}
   143  				defer destroyService(service)
   144  				kubeAPIServerWG.Done()
   145  				// Keeping kube-apiserver up to process requests.
   146  				testCompletedWG.Wait()
   147  			}()
   148  
   149  			kmsPluginWG.Add(1)
   150  			go func() {
   151  				// Simulating delayed start of kms-plugin, kube-apiserver is up before the plugin, if requested by the testcase.
   152  				time.Sleep(tt.pluginDelay)
   153  
   154  				_ = mock.NewBase64Plugin(t, socketName.path)
   155  
   156  				kmsPluginWG.Done()
   157  				// Keeping plugin up to process requests.
   158  				testCompletedWG.Wait()
   159  			}()
   160  
   161  			kubeAPIServerWG.Wait()
   162  			if t.Failed() {
   163  				return
   164  			}
   165  			_, err = service.Encrypt(data)
   166  
   167  			if err == nil && tt.wantErr != "" {
   168  				t.Fatalf("got nil, want %s", tt.wantErr)
   169  			}
   170  
   171  			if err != nil && tt.wantErr == "" {
   172  				t.Fatalf("got %q, want nil", err.Error())
   173  			}
   174  
   175  			// Collecting kms-plugin - allowing plugin to clean-up.
   176  			kmsPluginWG.Wait()
   177  		})
   178  	}
   179  }
   180  
   181  // TestIntermittentConnectionLoss tests the scenario where the connection with kms-plugin is intermittently lost.
   182  func TestIntermittentConnectionLoss(t *testing.T) {
   183  	t.Parallel()
   184  	var (
   185  		wg1        sync.WaitGroup
   186  		wg2        sync.WaitGroup
   187  		timeout    = 30 * time.Second
   188  		blackOut   = 1 * time.Second
   189  		data       = []byte("test data")
   190  		endpoint   = newEndpoint()
   191  		encryptErr error
   192  	)
   193  	// Start KMS Plugin
   194  	f := mock.NewBase64Plugin(t, endpoint.path)
   195  
   196  	ctx := testContext(t)
   197  
   198  	//  connect to kms plugin
   199  	service, err := NewGRPCService(ctx, endpoint.endpoint, timeout)
   200  	if err != nil {
   201  		t.Fatalf("failed to create envelope service, error: %v", err)
   202  	}
   203  	defer destroyService(service)
   204  
   205  	_, err = service.Encrypt(data)
   206  	if err != nil {
   207  		t.Fatalf("failed when execute encrypt, error: %v", err)
   208  	}
   209  	t.Log("Connected to KMSPlugin")
   210  	f.CleanUp()
   211  
   212  	// Stop KMS Plugin - simulating connection loss
   213  	t.Log("KMS Plugin is stopping")
   214  	time.Sleep(2 * time.Second)
   215  
   216  	wg1.Add(1)
   217  	wg2.Add(1)
   218  	go func() {
   219  		defer wg2.Done()
   220  		// Call service to encrypt data.
   221  		t.Log("Sending encrypt request")
   222  		wg1.Done()
   223  		_, err := service.Encrypt(data)
   224  		if err != nil {
   225  			encryptErr = fmt.Errorf("failed when executing encrypt, error: %v", err)
   226  		}
   227  	}()
   228  
   229  	wg1.Wait()
   230  	time.Sleep(blackOut)
   231  	// Start KMS Plugin
   232  	_ = mock.NewBase64Plugin(t, endpoint.path)
   233  	t.Log("Restarted KMS Plugin")
   234  
   235  	wg2.Wait()
   236  
   237  	if encryptErr != nil {
   238  		t.Error(encryptErr)
   239  	}
   240  }
   241  
   242  func TestUnsupportedVersion(t *testing.T) {
   243  	t.Parallel()
   244  	ver := "invalid"
   245  	data := []byte("test data")
   246  	wantErr := fmt.Errorf(versionErrorf, ver, kmsapiVersion)
   247  	endpoint := newEndpoint()
   248  
   249  	f := mock.NewBase64Plugin(t, endpoint.path)
   250  	f.SetVersion(ver)
   251  
   252  	ctx := testContext(t)
   253  
   254  	s, err := NewGRPCService(ctx, endpoint.endpoint, 1*time.Second)
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  	defer destroyService(s)
   259  
   260  	// Encrypt
   261  	_, err = s.Encrypt(data)
   262  	if err == nil || err.Error() != wantErr.Error() {
   263  		t.Errorf("got err: %ver, want: %ver", err, wantErr)
   264  	}
   265  
   266  	destroyService(s)
   267  
   268  	s, err = NewGRPCService(ctx, endpoint.endpoint, 1*time.Second)
   269  	if err != nil {
   270  		t.Fatal(err)
   271  	}
   272  	defer destroyService(s)
   273  
   274  	// Decrypt
   275  	_, err = s.Decrypt(data)
   276  	if err == nil || err.Error() != wantErr.Error() {
   277  		t.Errorf("got err: %ver, want: %ver", err, wantErr)
   278  	}
   279  }
   280  
   281  // Normal encryption and decryption operation.
   282  func TestGRPCService(t *testing.T) {
   283  	t.Parallel()
   284  	// Start a test gRPC server.
   285  	endpoint := newEndpoint()
   286  	_ = mock.NewBase64Plugin(t, endpoint.path)
   287  
   288  	ctx := testContext(t)
   289  
   290  	// Create the gRPC client service.
   291  	service, err := NewGRPCService(ctx, endpoint.endpoint, 1*time.Second)
   292  	if err != nil {
   293  		t.Fatalf("failed to create envelope service, error: %v", err)
   294  	}
   295  	defer destroyService(service)
   296  
   297  	// Call service to encrypt data.
   298  	data := []byte("test data")
   299  	cipher, err := service.Encrypt(data)
   300  	if err != nil {
   301  		t.Fatalf("failed when execute encrypt, error: %v", err)
   302  	}
   303  
   304  	// Call service to decrypt data.
   305  	result, err := service.Decrypt(cipher)
   306  	if err != nil {
   307  		t.Fatalf("failed when execute decrypt, error: %v", err)
   308  	}
   309  
   310  	if !reflect.DeepEqual(data, result) {
   311  		t.Errorf("expect: %v, but: %v", data, result)
   312  	}
   313  }
   314  
   315  // Normal encryption and decryption operation by multiple go-routines.
   316  func TestGRPCServiceConcurrentAccess(t *testing.T) {
   317  	t.Parallel()
   318  	// Start a test gRPC server.
   319  	endpoint := newEndpoint()
   320  	_ = mock.NewBase64Plugin(t, endpoint.path)
   321  
   322  	ctx := testContext(t)
   323  
   324  	// Create the gRPC client service.
   325  	service, err := NewGRPCService(ctx, endpoint.endpoint, 15*time.Second)
   326  	if err != nil {
   327  		t.Fatalf("failed to create envelope service, error: %v", err)
   328  	}
   329  	defer destroyService(service)
   330  
   331  	var wg sync.WaitGroup
   332  	n := 100
   333  	wg.Add(n)
   334  	for i := 0; i < n; i++ {
   335  		go func() {
   336  			defer wg.Done()
   337  			// Call service to encrypt data.
   338  			data := []byte("test data")
   339  			cipher, err := service.Encrypt(data)
   340  			if err != nil {
   341  				t.Errorf("failed when execute encrypt, error: %v", err)
   342  			}
   343  
   344  			// Call service to decrypt data.
   345  			result, err := service.Decrypt(cipher)
   346  			if err != nil {
   347  				t.Errorf("failed when execute decrypt, error: %v", err)
   348  			}
   349  
   350  			if !reflect.DeepEqual(data, result) {
   351  				t.Errorf("expect: %v, but: %v", data, result)
   352  			}
   353  		}()
   354  	}
   355  
   356  	wg.Wait()
   357  }
   358  
   359  func destroyService(service Service) {
   360  	if service != nil {
   361  		s := service.(*gRPCService)
   362  		s.connection.Close()
   363  	}
   364  }
   365  
   366  // Test all those invalid configuration for KMS provider.
   367  func TestInvalidConfiguration(t *testing.T) {
   368  	t.Parallel()
   369  	// Start a test gRPC server.
   370  	_ = mock.NewBase64Plugin(t, newEndpoint().path)
   371  
   372  	ctx := testContext(t)
   373  
   374  	invalidConfigs := []struct {
   375  		name     string
   376  		endpoint string
   377  	}{
   378  		{"emptyConfiguration", ""},
   379  		{"invalidScheme", "tcp://localhost:6060"},
   380  	}
   381  
   382  	for _, testCase := range invalidConfigs {
   383  		t.Run(testCase.name, func(t *testing.T) {
   384  			_, err := NewGRPCService(ctx, testCase.endpoint, 1*time.Second)
   385  			if err == nil {
   386  				t.Fatalf("should fail to create envelope service for %s.", testCase.name)
   387  			}
   388  		})
   389  	}
   390  }
   391  
   392  func testContext(t *testing.T) context.Context {
   393  	ctx, cancel := context.WithCancel(context.Background())
   394  	t.Cleanup(cancel)
   395  	return ctx
   396  }