kubelatte-ce
Форк от sbertech/kubelatte-ce
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
8package webhook
9
10import (
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"
21v1alpha12 "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"
31v1 "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"
47metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
48)
49
50func TestMain(m *testing.M) {
51lib.ZapLogger = zap.NewNop()
52os.Exit(m.Run())
53}
54
55func TestImmutable(t *testing.T) {
56lib.ZapLogger = zap.NewNop()
57os.Setenv("KBLT_ENABLED_TRIGGER_PREFIX", "false")
58os.Setenv("KBLT_VALIDATION", "false")
59env.InitEnvWebhookServer()
60whsvr := &Server{
61config: &config.WebhookConfig{},
62}
63
64ar := v1beta1.AdmissionReview{Request: &v1beta1.AdmissionRequest{UID: "jingle bells"}}
65body, err := json.Marshal(ar)
66if err != nil {
67t.Fatalf("Marshalling Admmission Request error: %v", err)
68}
69
70req, err := http.NewRequest(http.MethodGet, "", bytes.NewBuffer(body))
71if err != nil {
72t.Fatalf("Creatimg request error: %v", err)
73}
74
75whsvr.ImmutableHandler(httptest.NewRecorder(), req)
76}
77
78// TempCerts for temporary certificates
79type TempCerts struct {
80certsDirectory string
81certFileName string
82keyFileName string
83}
84
85// GenerateTestCertificates generates test certificates
86func GenerateTestCertificates() (*TempCerts, error) {
87// Source - https://golang.org/src/crypto/tls/generate_cert.go
88
89var err error
90tempDir := os.TempDir()
91if err != nil {
92return nil, fmt.Errorf("failed to create a temp directory: %s", err)
93}
94
95certFileName := filepath.Join(tempDir, "cert.pem")
96keyFileName := filepath.Join(tempDir, "key.pem")
97
98var privateKey *rsa.PrivateKey
99privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
100
101if err != nil {
102return nil, fmt.Errorf("failed to generate private key: %s", err)
103}
104
105notBefore := time.Now()
106notAfter := notBefore.Add(365 * 24 * time.Hour)
107
108serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
109serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
110
111if err != nil {
112return nil, fmt.Errorf("failed to generate serial number: %s", err)
113}
114
115tmpl := x509.Certificate{
116SerialNumber: serialNumber,
117Subject: pkix.Name{
118Organization: []string{"SBT"},
119},
120NotBefore: notBefore,
121NotAfter: notAfter,
122
123KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
124ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
125BasicConstraintsValid: true,
126IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)},
127DNSNames: []string{"localhost"},
128}
129
130publicKey := &privateKey.PublicKey
131derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, publicKey, privateKey)
132if err != nil {
133return nil, fmt.Errorf("failed to create certificate: %s", err)
134}
135
136err = writePemBlockToFile(certFileName, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
137if err != nil {
138return nil, err
139}
140
141err = writePemBlockToFile(keyFileName, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)})
142if err != nil {
143return nil, err
144}
145
146return &TempCerts{
147certsDirectory: tempDir,
148certFileName: certFileName,
149keyFileName: keyFileName,
150}, nil
151}
152
153func writePemBlockToFile(fileName string, pemBlock *pem.Block) error {
154certOut, err := os.Create(fileName)
155if err != nil {
156return fmt.Errorf("failed to create %s: %s", fileName, err)
157}
158
159if err := pem.Encode(certOut, pemBlock); err != nil {
160return fmt.Errorf("failed to write block to file: %s", err)
161}
162
163if err := certOut.Close(); err != nil {
164return fmt.Errorf("unable to close %s: %s", fileName, err)
165}
166
167return nil
168}
169
170func Test_parseRequest(t *testing.T) {
171type args struct {
172request *v1beta1.AdmissionRequest
173body []byte
174}
175tests := []struct {
176name string
177args args
178want *common.ARFields
179want1 map[string]interface{}
180wantErr assert.ErrorAssertionFunc
181}{
182{
183name: "test ok",
184args: args{
185request: &v1beta1.AdmissionRequest{
186Kind: metav1.GroupVersionKind{
187Group: "group",
188Version: "version",
189Kind: "kind",
190},
191Namespace: "some-namespace",
192Operation: v1beta1.Update,
193UserInfo: v1.UserInfo{Username: "Katerina"},
194OldObject: runtime.RawExtension{Raw: []byte("{\"oldraw\":\"old rrrr\"}")},
195Object: runtime.RawExtension{Raw: []byte("{\"raw\":\"rrrr\"}")},
196},
197body: []byte("{\"request\":{\"John\": {\"name\": \"Smith\",\"age\": 5}}, \"age\":30, \"car\":null}"),
198},
199want: &common.ARFields{
200Kind: metav1.GroupVersionKind{
201Group: "group",
202Version: "version",
203Kind: "kind",
204},
205Namespace: "some-namespace",
206UserInfo: common.ARUserInfo{Username: "Katerina"},
207Operation: v1beta1.Update,
208OldObject: map[string]interface{}{"oldraw": "old rrrr"},
209Object: map[string]interface{}{"raw": "rrrr"},
210},
211want1: map[string]interface{}{"lala": "llad"},
212wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
213assert.False(t, err != nil)
214return err != nil
215},
216},
217{
218name: "unmarshal error 1",
219args: args{
220request: &v1beta1.AdmissionRequest{
221Object: runtime.RawExtension{Raw: []byte("{\"raw\":\"rrrr\"}")},
222},
223body: []byte("not-json-string"),
224},
225want: nil,
226want1: nil,
227wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
228assert.True(t, err != nil)
229return err != nil
230},
231},
232{
233name: "unmarshal error 2",
234args: args{
235request: &v1beta1.AdmissionRequest{
236Object: runtime.RawExtension{Raw: []byte("not-json-string")},
237},
238body: []byte("{\"name\":\"John\", \"age\":30, \"car\":null}"),
239},
240want: nil,
241want1: nil,
242wantErr: func(t assert.TestingT, err error, i ...interface{}) bool {
243assert.True(t, err != nil)
244return err != nil
245},
246},
247}
248for _, tt := range tests {
249t.Run(tt.name, func(t *testing.T) {
250os.Setenv("KBLT_ENABLED_TRIGGER_PREFIX", "false")
251os.Setenv("KBLT_VALIDATION", "false")
252got, got1, err := parseRequest(context.Background(), tt.args.request, tt.args.body)
253if !tt.wantErr(t, err, fmt.Sprintf("parseRequest(%v, %v)", tt.args.request, tt.args.body)) {
254fmt.Println()
255return
256}
257assert.Equalf(t, tt.want, got, "parseRequest(%v, %v)", tt.args.request, tt.args.body)
258assert.Equalf(t, tt.want1, got1, "parseRequest(%v, %v)", tt.args.request, tt.args.body)
259fmt.Println(tt.want, got)
260fmt.Println(tt.want1, got1)
261})
262}
263}
264
265func TestWebhookServer_validate(t *testing.T) {
266storageController := &storage.StorageController{Sync: false}
267storage.Storage = storageController
268storageController.Start(false, false)
269utils.StorageUpdateScope <- v1alpha12.Scope{
270TypeMeta: metav1.TypeMeta{
271Kind: "Template",
272APIVersion: "",
273},
274ObjectMeta: metav1.ObjectMeta{
275Name: "test",
276Namespace: "test-kblt",
277},
278}
279
280type fields struct {
281tlsServer *http.Server
282httpServer *http.Server
283config *config.WebhookConfig
284certificateReloader util.CertificateReloader
285state serverState
286mux *sync.Mutex
287grpcCli grpc.ProviderClient
288}
289type args struct {
290w http.ResponseWriter
291r *http.Request
292}
293tests := []struct {
294name string
295fields fields
296args args
297}{
298{
299name: "test 1",
300fields: fields{
301grpcCli: grpc.ProviderClient{},
302},
303args: args{
304w: httptest.NewRecorder(),
305r: httptest.NewRequest("POST", "/ValidateHandler", http.NoBody),
306},
307}, {
308name: "test 2",
309fields: fields{
310state: ServerCertificatesError,
311grpcCli: grpc.ProviderClient{},
312},
313args: args{
314w: httptest.NewRecorder(),
315r: httptest.NewRequest("POST", "/ValidateHandler", http.NoBody),
316},
317},
318{
319name: "test 3",
320fields: fields{
321grpcCli: grpc.ProviderClient{},
322},
323args: args{
324w: httptest.NewRecorder(),
325r: &http.Request{
326Method: "POST",
327Header: http.Header{
328"Content-Type": []string{"application/json"},
329},
330Body: io.NopCloser(bytes.NewBufferString("")),
331},
332},
333},
334{
335name: "test 4",
336fields: fields{
337grpcCli: grpc.ProviderClient{},
338},
339args: args{
340w: httptest.NewRecorder(),
341r: &http.Request{
342Method: "POST",
343Header: http.Header{
344"Content-Type": []string{"application/json"},
345},
346Body: 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{
351name: "test fail",
352fields: fields{
353grpcCli: grpc.ProviderClient{},
354},
355args: args{
356w: httptest.NewRecorder(),
357r: &http.Request{
358Method: "POST",
359Header: http.Header{
360"Content-Type": []string{"application/json"},
361},
362Body: 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}
367for _, tt := range tests {
368t.Run(tt.name, func(t *testing.T) {
369webhookConfig, _ := config.Load()
370whsrv := NewWebhookServer(webhookConfig, &util.CertificatePKIReloader{}, &sync.Mutex{}, modules.GetActors())
371whsrv.ValidateHandler(tt.args.w, tt.args.r)
372})
373}
374}
375
376func TestWebhookServer_mutateHandler(t *testing.T) {
377type fields struct {
378tlsServer *http.Server
379httpServer *http.Server
380config *config.WebhookConfig
381certificateReloader util.CertificateReloader
382state serverState
383mux *sync.Mutex
384grpcCli grpc.ProviderClient
385}
386type args struct {
387w http.ResponseWriter
388r *http.Request
389}
390tests := []struct {
391name string
392fields fields
393args args
394}{
395{
396name: "null",
397fields: fields{
398grpcCli: grpc.ProviderClient{},
399},
400args: args{
401w: httptest.NewRecorder(),
402r: &http.Request{
403Header: http.Header{},
404},
405},
406},
407{
408name: "application/json",
409fields: fields{
410grpcCli: grpc.ProviderClient{},
411},
412args: args{
413w: httptest.NewRecorder(),
414r: &http.Request{
415Header: http.Header{
416"Content-Type": {"application/json"},
417},
418},
419},
420},
421{
422name: "test 1",
423fields: fields{
424grpcCli: grpc.ProviderClient{},
425},
426args: args{
427w: httptest.NewRecorder(),
428r: &http.Request{
429Method: "POST",
430Header: http.Header{
431"Content-Type": []string{"application/json"},
432},
433Body: io.NopCloser(bytes.NewBufferString("Hello")),
434},
435},
436},
437{
438name: "application/json",
439fields: fields{
440grpcCli: grpc.ProviderClient{},
441},
442args: args{
443w: httptest.NewRecorder(),
444r: &http.Request{
445Body: io.NopCloser(strings.NewReader("\"request\":\"val\"")),
446Header: http.Header{
447"Content-Type": {"application/json"},
448},
449},
450},
451},
452}
453for _, tt := range tests {
454t.Run(tt.name, func(t *testing.T) {
455whsvr := &Server{
456tlsServer: tt.fields.tlsServer,
457httpServer: tt.fields.httpServer,
458config: tt.fields.config,
459certificateReloader: tt.fields.certificateReloader,
460state: tt.fields.state,
461mux: tt.fields.mux,
462}
463whsvr.MutateHandler(tt.args.w, tt.args.r)
464})
465}
466}
467