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 }