knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/webhook_integration_test.go (about) 1 /* 2 Copyright 2018 The Knative 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 webhook 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "net" 24 "net/http" 25 "strings" 26 "testing" 27 "time" 28 29 "golang.org/x/sync/errgroup" 30 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 kubeclient "knative.dev/pkg/client/injection/kube/client/fake" 33 34 "go.opentelemetry.io/otel/sdk/metric" 35 36 "knative.dev/pkg/system" 37 pkgtest "knative.dev/pkg/testing" 38 certresources "knative.dev/pkg/webhook/certificates/resources" 39 40 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 41 ) 42 43 // createResource creates a testing.Resource with the given name in the system namespace. 44 func createResource(name string) *pkgtest.Resource { 45 return &pkgtest.Resource{ 46 ObjectMeta: metav1.ObjectMeta{ 47 Namespace: system.Namespace(), 48 Name: name, 49 }, 50 Spec: pkgtest.ResourceSpec{ 51 FieldWithValidation: "magic value", 52 }, 53 } 54 } 55 56 const testTimeout = 10 * time.Second 57 58 func TestMissingContentType(t *testing.T) { 59 test := testSetup(t) 60 61 eg, _ := errgroup.WithContext(test.ctx) 62 eg.Go(func() error { return test.webhook.Run(test.ctx.Done()) }) 63 test.webhook.InformersHaveSynced() 64 defer func() { 65 test.cancel() 66 if err := eg.Wait(); err != nil { 67 t.Error("Unable to run controller:", err) 68 } 69 }() 70 71 if err := waitForServerAvailable(t, test.addr, testTimeout); err != nil { 72 t.Fatal("waitForServerAvailable() =", err) 73 } 74 75 tlsClient, err := createSecureTLSClient(t, kubeclient.Get(test.ctx), &test.webhook.Options) 76 if err != nil { 77 t.Fatal("createSecureTLSClient() =", err) 78 } 79 80 req, err := http.NewRequest(http.MethodGet, "https://"+test.addr, nil) 81 if err != nil { 82 t.Fatal("http.NewRequest() =", err) 83 } 84 85 response, err := tlsClient.Do(req) 86 if err != nil { 87 t.Fatalf("Received %v error from server %s", err, test.addr) 88 } 89 90 if got, want := response.StatusCode, http.StatusUnsupportedMediaType; got != want { 91 t.Errorf("Response status code = %v, wanted %v", got, want) 92 } 93 94 defer response.Body.Close() 95 responseBody, err := io.ReadAll(response.Body) 96 if err != nil { 97 t.Fatal("Failed to read response body", err) 98 } 99 100 if !strings.Contains(string(responseBody), "invalid Content-Type") { 101 t.Errorf("Response body to contain 'invalid Content-Type' , got = '%s'", string(responseBody)) 102 } 103 } 104 105 func TestServerWithCustomSecret(t *testing.T) { 106 test := testSetup(t, withServerCertificateName("tls.crt"), withServerPrivateKeyName("tls.key")) 107 108 eg, _ := errgroup.WithContext(test.ctx) 109 eg.Go(func() error { return test.webhook.Run(test.ctx.Done()) }) 110 test.webhook.InformersHaveSynced() 111 defer func() { 112 test.cancel() 113 if err := eg.Wait(); err != nil { 114 t.Error("Unable to run controller:", err) 115 } 116 }() 117 118 pollErr := waitForServerAvailable(t, test.addr, testTimeout) 119 if pollErr != nil { 120 t.Fatal("waitForServerAvailable() =", pollErr) 121 } 122 } 123 124 func testEmptyRequestBody(t *testing.T, controller any) { 125 test := testSetup(t, withController(controller)) 126 127 eg, _ := errgroup.WithContext(test.ctx) 128 eg.Go(func() error { return test.webhook.Run(test.ctx.Done()) }) 129 test.webhook.InformersHaveSynced() 130 defer func() { 131 test.cancel() 132 if err := eg.Wait(); err != nil { 133 t.Error("Unable to run controller:", err) 134 } 135 }() 136 137 if err := waitForServerAvailable(t, test.addr, testTimeout); err != nil { 138 t.Fatal("waitForServerAvailable() =", err) 139 } 140 141 tlsClient, err := createSecureTLSClient(t, kubeclient.Get(test.ctx), &test.webhook.Options) 142 if err != nil { 143 t.Fatal("createSecureTLSClient() =", err) 144 } 145 146 req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://%s/bazinga", test.addr), nil) 147 if err != nil { 148 t.Fatal("http.NewRequest() =", err) 149 } 150 151 req.Header.Add("Content-Type", "application/json") 152 153 response, err := tlsClient.Do(req) 154 if err != nil { 155 t.Fatal("failed to get resp", err) 156 } 157 158 if got, want := response.StatusCode, http.StatusBadRequest; got != want { 159 t.Errorf("Response status code = %v, wanted %v", got, want) 160 } 161 defer response.Body.Close() 162 163 responseBody, err := io.ReadAll(response.Body) 164 if err != nil { 165 t.Fatal("Failed to read response body", err) 166 } 167 168 if !strings.Contains(string(responseBody), "could not decode body") { 169 t.Errorf("Response body to contain 'decode failure information' , got = %q", string(responseBody)) 170 } 171 } 172 173 func TestSetupWebhookHTTPServerError(t *testing.T) { 174 defaultOpts := newDefaultOptions() 175 defaultOpts.Port = -1 // invalid port 176 ctx, wh, cancel := newNonRunningTestWebhook(t, defaultOpts) 177 defer cancel() 178 kubeClient := kubeclient.Get(ctx) 179 180 nsErr := createNamespace(t, kubeClient, metav1.NamespaceSystem) 181 if nsErr != nil { 182 t.Fatal("createNamespace() =", nsErr) 183 } 184 cMapsErr := createTestConfigMap(t, kubeClient) 185 if cMapsErr != nil { 186 t.Fatal("createTestConfigMap() =", cMapsErr) 187 } 188 189 stopCh := make(chan struct{}) 190 errCh := make(chan error) 191 go func() { 192 if err := wh.Run(stopCh); err != nil { 193 errCh <- err 194 } 195 }() 196 197 select { 198 case <-time.After(6 * time.Second): 199 t.Error("Timeout in testing bootstrap webhook http server failed") 200 case errItem := <-errCh: 201 if !strings.Contains(errItem.Error(), "bootstrap failed") { 202 t.Error("Expected bootstrap webhook http server failed") 203 } 204 } 205 } 206 207 func testSetup(t *testing.T, opts ...func(*testOptions)) testContext { 208 t.Helper() 209 210 // ephemeral port 211 l, err := net.Listen("tcp", ":0") 212 if err != nil { 213 t.Fatal("unable to get ephemeral port: ", err) 214 } 215 216 testOpts := &testOptions{ 217 Options: newDefaultOptions(), 218 } 219 220 reader := metric.NewManualReader() 221 provider := metric.NewMeterProvider(metric.WithReader(reader)) 222 testOpts.Options.MeterProvider = provider 223 224 for _, opt := range opts { 225 opt(testOpts) 226 } 227 228 ctx, wh, cancel := newNonRunningTestWebhook(t, testOpts.Options, testOpts.controllers...) 229 wh.testListener = l 230 231 // Create certificate 232 secret, err := certresources.MakeSecret(ctx, testOpts.SecretName, system.Namespace(), testOpts.ServiceName) 233 if err != nil { 234 t.Fatalf("failed to create certificate") 235 } 236 237 if testOpts.ServerCertificateName != "" { 238 secret.Data[testOpts.ServerCertificateName] = secret.Data[certresources.ServerCert] 239 delete(secret.Data, certresources.ServerCert) 240 } 241 242 if testOpts.ServerPrivateKeyName != "" { 243 secret.Data[testOpts.ServerPrivateKeyName] = secret.Data[certresources.ServerKey] 244 delete(secret.Data, certresources.ServerKey) 245 } 246 247 kubeClient := kubeclient.Get(ctx) 248 249 if _, err := kubeClient.CoreV1().Secrets(secret.Namespace).Create(context.Background(), secret, metav1.CreateOptions{}); err != nil { 250 t.Fatalf("failed to create secret") 251 } 252 253 return testContext{ 254 webhook: wh, 255 addr: l.Addr().String(), 256 ctx: ctx, 257 cancel: cancel, 258 metricReader: reader, 259 } 260 } 261 262 type testContext struct { 263 webhook *Webhook 264 addr string 265 ctx context.Context 266 cancel context.CancelFunc 267 metricReader *metric.ManualReader 268 } 269 270 type testOptions struct { 271 Options 272 controllers []any 273 } 274 275 func withController(controller any) func(o *testOptions) { 276 return func(o *testOptions) { 277 o.controllers = append(o.controllers, controller) 278 } 279 } 280 281 func withServerCertificateName(name string) func(o *testOptions) { 282 return func(o *testOptions) { 283 o.ServerCertificateName = name 284 } 285 } 286 287 func withServerPrivateKeyName(name string) func(o *testOptions) { 288 return func(o *testOptions) { 289 o.ServerPrivateKeyName = name 290 } 291 } 292 293 func withNoTLS() func(o *testOptions) { 294 return func(o *testOptions) { 295 o.SecretName = "" 296 } 297 }