В 22:00 МСК будет объявлен перерыв - 10 минут. Вы отдыхаете - мы обновляем!

kubelatte-ce

Форк от sbertech/kubelatte-ce
Форк
2
/
webhook_test.go 
466 строк · 16.8 Кб
1
/*
2
 * Copyright (c) 2020, salesforce.com, inc.
3
 * All rights reserved.
4
 * SPDX-License-Identifier: BSD-3-Clause
5
 * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
 */
7

8
package webhook
9

10
import (
11
	"bytes"
12
	"context"
13
	"crypto/rand"
14
	"crypto/rsa"
15
	"crypto/x509"
16
	"crypto/x509/pkix"
17
	"encoding/json"
18
	"encoding/pem"
19
	"fmt"
20
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
21
	v1alpha12 "gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
22
	"gitverse.ru/synapse/kubelatte/pkg/modules"
23
	"gitverse.ru/synapse/kubelatte/pkg/observability/logger/lib"
24
	"gitverse.ru/synapse/kubelatte/pkg/operator/utils"
25
	"gitverse.ru/synapse/kubelatte/pkg/sideeffect/grpc"
26
	"gitverse.ru/synapse/kubelatte/pkg/storage"
27
	"gitverse.ru/synapse/kubelatte/pkg/util"
28
	"gitverse.ru/synapse/kubelatte/pkg/util/env"
29
	"go.uber.org/zap"
30
	"io"
31
	v1 "k8s.io/api/authentication/v1"
32
	"k8s.io/apimachinery/pkg/runtime"
33
	"math/big"
34
	"net"
35
	"net/http"
36
	"net/http/httptest"
37
	"os"
38
	"path/filepath"
39
	"strings"
40
	"sync"
41
	"testing"
42
	"time"
43

44
	"github.com/stretchr/testify/assert"
45
	"gitverse.ru/synapse/kubelatte/pkg/webhook/config"
46
	"k8s.io/api/admission/v1beta1"
47
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48
)
49

50
func TestMain(m *testing.M) {
51
	lib.ZapLogger = zap.NewNop()
52
	os.Exit(m.Run())
53
}
54

55
func TestImmutable(t *testing.T) {
56
	lib.ZapLogger = zap.NewNop()
57
	os.Setenv("KBLT_ENABLED_TRIGGER_PREFIX", "false")
58
	os.Setenv("KBLT_VALIDATION", "false")
59
	env.InitEnvWebhookServer()
60
	whsvr := &Server{
61
		config: &config.WebhookConfig{},
62
	}
63

64
	ar := v1beta1.AdmissionReview{Request: &v1beta1.AdmissionRequest{UID: "jingle bells"}}
65
	body, err := json.Marshal(ar)
66
	if err != nil {
67
		t.Fatalf("Marshalling Admmission Request error: %v", err)
68
	}
69

70
	req, err := http.NewRequest(http.MethodGet, "", bytes.NewBuffer(body))
71
	if err != nil {
72
		t.Fatalf("Creatimg request error: %v", err)
73
	}
74

75
	whsvr.ImmutableHandler(httptest.NewRecorder(), req)
76
}
77

78
// TempCerts for temporary certificates
79
type TempCerts struct {
80
	certsDirectory string
81
	certFileName   string
82
	keyFileName    string
83
}
84

85
// GenerateTestCertificates generates test certificates
86
func GenerateTestCertificates() (*TempCerts, error) {
87
	// Source - https://golang.org/src/crypto/tls/generate_cert.go
88

89
	var err error
90
	tempDir := os.TempDir()
91
	if err != nil {
92
		return nil, fmt.Errorf("failed to create a temp directory: %s", err)
93
	}
94

95
	certFileName := filepath.Join(tempDir, "cert.pem")
96
	keyFileName := filepath.Join(tempDir, "key.pem")
97

98
	var privateKey *rsa.PrivateKey
99
	privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
100

101
	if err != nil {
102
		return nil, fmt.Errorf("failed to generate private key: %s", err)
103
	}
104

105
	notBefore := time.Now()
106
	notAfter := notBefore.Add(365 * 24 * time.Hour)
107

108
	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
109
	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
110

111
	if err != nil {
112
		return nil, fmt.Errorf("failed to generate serial number: %s", err)
113
	}
114

115
	tmpl := x509.Certificate{
116
		SerialNumber: serialNumber,
117
		Subject: pkix.Name{
118
			Organization: []string{"SBT"},
119
		},
120
		NotBefore: notBefore,
121
		NotAfter:  notAfter,
122

123
		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
124
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
125
		BasicConstraintsValid: true,
126
		IPAddresses:           []net.IP{net.IPv4(127, 0, 0, 1)},
127
		DNSNames:              []string{"localhost"},
128
	}
129

130
	publicKey := &privateKey.PublicKey
131
	derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, publicKey, privateKey)
132
	if err != nil {
133
		return nil, fmt.Errorf("failed to create certificate: %s", err)
134
	}
135

136
	err = writePemBlockToFile(certFileName, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
137
	if err != nil {
138
		return nil, err
139
	}
140

141
	err = writePemBlockToFile(keyFileName, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
142
	if err != nil {
143
		return nil, err
144
	}
145

146
	return &TempCerts{
147
		certsDirectory: tempDir,
148
		certFileName:   certFileName,
149
		keyFileName:    keyFileName,
150
	}, nil
151
}
152

153
func writePemBlockToFile(fileName string, pemBlock *pem.Block) error {
154
	certOut, err := os.Create(fileName)
155
	if err != nil {
156
		return fmt.Errorf("failed to create %s: %s", fileName, err)
157
	}
158

159
	if err := pem.Encode(certOut, pemBlock); err != nil {
160
		return fmt.Errorf("failed to write block to file: %s", err)
161
	}
162

163
	if err := certOut.Close(); err != nil {
164
		return fmt.Errorf("unable to close %s: %s", fileName, err)
165
	}
166

167
	return nil
168
}
169

170
func Test_parseRequest(t *testing.T) {
171
	type args struct {
172
		request *v1beta1.AdmissionRequest
173
		body    []byte
174
	}
175
	tests := []struct {
176
		name    string
177
		args    args
178
		want    *common.ARFields
179
		want1   map[string]interface{}
180
		wantErr assert.ErrorAssertionFunc
181
	}{
182
		{
183
			name: "test ok",
184
			args: args{
185
				request: &v1beta1.AdmissionRequest{
186
					Kind: metav1.GroupVersionKind{
187
						Group:   "group",
188
						Version: "version",
189
						Kind:    "kind",
190
					},
191
					Namespace: "some-namespace",
192
					Operation: v1beta1.Update,
193
					UserInfo:  v1.UserInfo{Username: "Katerina"},
194
					OldObject: runtime.RawExtension{Raw: []byte("{\"oldraw\":\"old rrrr\"}")},
195
					Object:    runtime.RawExtension{Raw: []byte("{\"raw\":\"rrrr\"}")},
196
				},
197
				body: []byte("{\"request\":{\"John\": {\"name\": \"Smith\",\"age\": 5}}, \"age\":30, \"car\":null}"),
198
			},
199
			want: &common.ARFields{
200
				Kind: metav1.GroupVersionKind{
201
					Group:   "group",
202
					Version: "version",
203
					Kind:    "kind",
204
				},
205
				Namespace: "some-namespace",
206
				UserInfo:  common.ARUserInfo{Username: "Katerina"},
207
				Operation: v1beta1.Update,
208
				OldObject: map[string]interface{}{"oldraw": "old rrrr"},
209
				Object:    map[string]interface{}{"raw": "rrrr"},
210
			},
211
			want1: map[string]interface{}{"lala": "llad"},
212
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
213
				assert.False(t, err != nil)
214
				return err != nil
215
			},
216
		},
217
		{
218
			name: "unmarshal error 1",
219
			args: args{
220
				request: &v1beta1.AdmissionRequest{
221
					Object: runtime.RawExtension{Raw: []byte("{\"raw\":\"rrrr\"}")},
222
				},
223
				body: []byte("not-json-string"),
224
			},
225
			want:  nil,
226
			want1: nil,
227
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
228
				assert.True(t, err != nil)
229
				return err != nil
230
			},
231
		},
232
		{
233
			name: "unmarshal error 2",
234
			args: args{
235
				request: &v1beta1.AdmissionRequest{
236
					Object: runtime.RawExtension{Raw: []byte("not-json-string")},
237
				},
238
				body: []byte("{\"name\":\"John\", \"age\":30, \"car\":null}"),
239
			},
240
			want:  nil,
241
			want1: nil,
242
			wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
243
				assert.True(t, err != nil)
244
				return err != nil
245
			},
246
		},
247
	}
248
	for _, tt := range tests {
249
		t.Run(tt.name, func(t *testing.T) {
250
			os.Setenv("KBLT_ENABLED_TRIGGER_PREFIX", "false")
251
			os.Setenv("KBLT_VALIDATION", "false")
252
			got, got1, err := parseRequest(context.Background(), tt.args.request, tt.args.body)
253
			if !tt.wantErr(t, err, fmt.Sprintf("parseRequest(%v, %v)", tt.args.request, tt.args.body)) {
254
				fmt.Println()
255
				return
256
			}
257
			assert.Equalf(t, tt.want, got, "parseRequest(%v, %v)", tt.args.request, tt.args.body)
258
			assert.Equalf(t, tt.want1, got1, "parseRequest(%v, %v)", tt.args.request, tt.args.body)
259
			fmt.Println(tt.want, got)
260
			fmt.Println(tt.want1, got1)
261
		})
262
	}
263
}
264

265
func TestWebhookServer_validate(t *testing.T) {
266
	storageController := &storage.StorageController{Sync: false}
267
	storage.Storage = storageController
268
	storageController.Start(false, false)
269
	utils.StorageUpdateScope <- v1alpha12.Scope{
270
		TypeMeta: metav1.TypeMeta{
271
			Kind:       "Template",
272
			APIVersion: "",
273
		},
274
		ObjectMeta: metav1.ObjectMeta{
275
			Name:      "test",
276
			Namespace: "test-kblt",
277
		},
278
	}
279

280
	type fields struct {
281
		tlsServer           *http.Server
282
		httpServer          *http.Server
283
		config              *config.WebhookConfig
284
		certificateReloader util.CertificateReloader
285
		state               serverState
286
		mux                 *sync.Mutex
287
		grpcCli             grpc.ProviderClient
288
	}
289
	type args struct {
290
		w http.ResponseWriter
291
		r *http.Request
292
	}
293
	tests := []struct {
294
		name   string
295
		fields fields
296
		args   args
297
	}{
298
		{
299
			name: "test 1",
300
			fields: fields{
301
				grpcCli: grpc.ProviderClient{},
302
			},
303
			args: args{
304
				w: httptest.NewRecorder(),
305
				r: httptest.NewRequest("POST", "/ValidateHandler", http.NoBody),
306
			},
307
		}, {
308
			name: "test 2",
309
			fields: fields{
310
				state:   ServerCertificatesError,
311
				grpcCli: grpc.ProviderClient{},
312
			},
313
			args: args{
314
				w: httptest.NewRecorder(),
315
				r: httptest.NewRequest("POST", "/ValidateHandler", http.NoBody),
316
			},
317
		},
318
		{
319
			name: "test 3",
320
			fields: fields{
321
				grpcCli: grpc.ProviderClient{},
322
			},
323
			args: args{
324
				w: httptest.NewRecorder(),
325
				r: &http.Request{
326
					Method: "POST",
327
					Header: http.Header{
328
						"Content-Type": []string{"application/json"},
329
					},
330
					Body: io.NopCloser(bytes.NewBufferString("")),
331
				},
332
			},
333
		},
334
		{
335
			name: "test 4",
336
			fields: fields{
337
				grpcCli: grpc.ProviderClient{},
338
			},
339
			args: args{
340
				w: httptest.NewRecorder(),
341
				r: &http.Request{
342
					Method: "POST",
343
					Header: http.Header{
344
						"Content-Type": []string{"application/json"},
345
					},
346
					Body: io.NopCloser(bytes.NewBufferString("{\n    \"kind\": \"AdmissionReview\",\n    \"apiVersion\": \"admission.k8s.io/v1beta1\",\n    \"request\": {\n        \"kind\": {\n            \"group\": \"apps\",\n            \"version\": \"v1\",\n            \"kind\": \"Pod\"\n        },\n        \"namespace\": \"rod-kblt-dev\",\n        \"operation\": \"CREATE\",\n        \"userInfo\": {\n            \"username\": \"system:serviceaccount:kube-system:replicaset-controller\",\n            \"uid\": \"16dee009-bf23-4b82-ba7a-224bf63831f9\",\n            \"groups\": [\n                \"system:serviceaccounts\",\n                \"system:serviceaccounts:kube-system\",\n                \"system:authenticated\"\n            ]\n        },\n        \"object\": {\n            \"apiVersion\": \"v1\",\n            \"kind\": \"Pod\",\n            \"metadata\": {\n                \"name\": \"http-test-rego-valid\",\n                \"namespace\": \"rod-kblt-dev\",\n                \"labels\": {\n                    \"kblt.dev\": \"test\",\n                    \"kblt.testname\": \"validation-rego-precompile-status\",\n                    \"kubelatte-universal-validator-aft-rego-valid\": \"success-validation\",\n                    \"sidecar.istio.io/inject\": \"false\",\n                    \"creation.resource\": \"true\"\n                }\n            },\n            \"spec\": {\n                \"hostNetwork\": false,\n                \"serviceAccount\": \"default\",\n                \"serviceAccountName\": \"default\",\n                \"restartPolicy\": \"Never\",\n                \"automountServiceAccountToken\": false,\n                \"securityContext\": {\n                    \"seccompProfile\": {\n                        \"type\": \"RuntimeDefault\"\n                    },\n                    \"runAsNonRoot\": true\n                },\n                \"containers\": [\n                    {\n                        \"image\": \"dzo.sw.sbc.space/sbt/ci90000221_fedmesh/federation-demo@sha256:548cc416011c86bb1e2c6d06289aab0771a0f749c044656deaac5fc86d891fea\",\n                        \"name\": \"nginx\",\n                        \"imagePullPolicy\": \"Always\",\n                        \"securityContext\": {\n                            \"allowPrivilegeEscalation\": false,\n                            \"capabilities\": {\n                                \"drop\": [\n                                    \"ALL\"\n                                ]\n                            },\n                            \"runAsNonRoot\": true,\n                            \"privileged\": false\n                        }\n                    }\n                ]\n            }\n        }\n    }\n}")),
347
				},
348
			},
349
		},
350
		{
351
			name: "test fail",
352
			fields: fields{
353
				grpcCli: grpc.ProviderClient{},
354
			},
355
			args: args{
356
				w: httptest.NewRecorder(),
357
				r: &http.Request{
358
					Method: "POST",
359
					Header: http.Header{
360
						"Content-Type": []string{"application/json"},
361
					},
362
					Body: io.NopCloser(bytes.NewBufferString("apiVersion\": \"admission.k8s.io/v1beta1\",\n    \"request\": {\n        \"kind\": {\n            \"group\": \"apps\",\n            \"version\": \"v1\",\n            \"kind\": \"Pod\"\n        },\n        \"namespace\": \"rod-kblt-dev\",\n        \"operation\": \"CREATE\",\n        \"userInfo\": {\n            \"username\": \"system:serviceaccount:kube-system:replicaset-controller\",\n            \"uid\": \"16dee009-bf23-4b82-ba7a-224bf63831f9\",\n            \"groups\": [\n                \"system:serviceaccounts\",\n                \"system:serviceaccounts:kube-system\",\n                \"system:authenticated\"\n            ]\n        },\n        \"object\": {\n            \"apiVersion\": \"v1\",\n            \"kind\": \"Pod\",\n            \"metadata\": {\n                \"name\": \"http-test-rego-valid\",\n                \"namespace\": \"rod-kblt-dev\",\n                \"labels\": {\n                    \"kblt.dev\": \"test\",\n                    \"kblt.testname\": \"validation-rego-precompile-status\",\n                    \"kubelatte-universal-validator-aft-rego-valid\": \"success-validation\",\n                    \"sidecar.istio.io/inject\": \"false\",\n                    \"creation.resource\": \"true\"\n                }\n            },\n            \"spec\": {\n                \"hostNetwork\": false,\n                \"serviceAccount\": \"default\",\n                \"serviceAccountName\": \"default\",\n                \"restartPolicy\": \"Never\",\n                \"automountServiceAccountToken\": false,\n                \"securityContext\": {\n                    \"seccompProfile\": {\n                        \"type\": \"RuntimeDefault\"\n                    },\n                    \"runAsNonRoot\": true\n                },\n                \"containers\": [\n                    {\n                        \"image\": \"dzo.sw.sbc.space/sbt/ci90000221_fedmesh/federation-demo@sha256:548cc416011c86bb1e2c6d06289aab0771a0f749c044656deaac5fc86d891fea\",\n                        \"name\": \"nginx\",\n                        \"imagePullPolicy\": \"Always\",\n                        \"securityContext\": {\n                            \"allowPrivilegeEscalation\": false,\n                            \"capabilities\": {\n                                \"drop\": [\n                                    \"ALL\"\n                                ]\n                            },\n                            \"runAsNonRoot\": true,\n                            \"privileged\": false\n                        }\n                    }\n                ]\n            }\n        }\n    }\n}")),
363
				},
364
			},
365
		},
366
	}
367
	for _, tt := range tests {
368
		t.Run(tt.name, func(t *testing.T) {
369
			webhookConfig, _ := config.Load()
370
			whsrv := NewWebhookServer(webhookConfig, &util.CertificatePKIReloader{}, &sync.Mutex{}, modules.GetActors())
371
			whsrv.ValidateHandler(tt.args.w, tt.args.r)
372
		})
373
	}
374
}
375

376
func TestWebhookServer_mutateHandler(t *testing.T) {
377
	type fields struct {
378
		tlsServer           *http.Server
379
		httpServer          *http.Server
380
		config              *config.WebhookConfig
381
		certificateReloader util.CertificateReloader
382
		state               serverState
383
		mux                 *sync.Mutex
384
		grpcCli             grpc.ProviderClient
385
	}
386
	type args struct {
387
		w http.ResponseWriter
388
		r *http.Request
389
	}
390
	tests := []struct {
391
		name   string
392
		fields fields
393
		args   args
394
	}{
395
		{
396
			name: "null",
397
			fields: fields{
398
				grpcCli: grpc.ProviderClient{},
399
			},
400
			args: args{
401
				w: httptest.NewRecorder(),
402
				r: &http.Request{
403
					Header: http.Header{},
404
				},
405
			},
406
		},
407
		{
408
			name: "application/json",
409
			fields: fields{
410
				grpcCli: grpc.ProviderClient{},
411
			},
412
			args: args{
413
				w: httptest.NewRecorder(),
414
				r: &http.Request{
415
					Header: http.Header{
416
						"Content-Type": {"application/json"},
417
					},
418
				},
419
			},
420
		},
421
		{
422
			name: "test 1",
423
			fields: fields{
424
				grpcCli: grpc.ProviderClient{},
425
			},
426
			args: args{
427
				w: httptest.NewRecorder(),
428
				r: &http.Request{
429
					Method: "POST",
430
					Header: http.Header{
431
						"Content-Type": []string{"application/json"},
432
					},
433
					Body: io.NopCloser(bytes.NewBufferString("Hello")),
434
				},
435
			},
436
		},
437
		{
438
			name: "application/json",
439
			fields: fields{
440
				grpcCli: grpc.ProviderClient{},
441
			},
442
			args: args{
443
				w: httptest.NewRecorder(),
444
				r: &http.Request{
445
					Body: io.NopCloser(strings.NewReader("\"request\":\"val\"")),
446
					Header: http.Header{
447
						"Content-Type": {"application/json"},
448
					},
449
				},
450
			},
451
		},
452
	}
453
	for _, tt := range tests {
454
		t.Run(tt.name, func(t *testing.T) {
455
			whsvr := &Server{
456
				tlsServer:           tt.fields.tlsServer,
457
				httpServer:          tt.fields.httpServer,
458
				config:              tt.fields.config,
459
				certificateReloader: tt.fields.certificateReloader,
460
				state:               tt.fields.state,
461
				mux:                 tt.fields.mux,
462
			}
463
			whsvr.MutateHandler(tt.args.w, tt.args.r)
464
		})
465
	}
466
}
467

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.