knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/webhook_test.go (about)

     1  /*
     2  Copyright 2019 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  	"crypto/tls"
    22  	"net"
    23  	"testing"
    24  	"time"
    25  
    26  	"golang.org/x/sync/errgroup"
    27  
    28  	// Make system.Namespace() work in tests.
    29  	_ "knative.dev/pkg/system/testing"
    30  
    31  	. "knative.dev/pkg/reconciler/testing"
    32  )
    33  
    34  func newDefaultOptions() Options {
    35  	return Options{
    36  		ServiceName: "webhook",
    37  		Port:        8443,
    38  		SecretName:  "webhook-certs",
    39  	}
    40  }
    41  
    42  const (
    43  	testResourceName = "test-resource"
    44  	user1            = "brutto@knative.dev"
    45  )
    46  
    47  func newNonRunningTestWebhook(t *testing.T, options Options, acs ...interface{}) (
    48  	ctx context.Context, ac *Webhook, cancel context.CancelFunc,
    49  ) {
    50  	t.Helper()
    51  
    52  	// override the grace period so it drains quickly
    53  	options.GracePeriod = 100 * time.Millisecond
    54  
    55  	// Create fake clients
    56  	ctx, ctxCancel, informers := SetupFakeContextWithCancel(t)
    57  	ctx = WithOptions(ctx, options)
    58  
    59  	stopCb, err := RunAndSyncInformers(ctx, informers...)
    60  	if err != nil {
    61  		t.Fatal("StartInformers() =", err)
    62  	}
    63  	cancel = func() {
    64  		ctxCancel()
    65  		stopCb()
    66  	}
    67  
    68  	ac, err = New(ctx, acs)
    69  	if err != nil {
    70  		t.Fatal("Failed to create new admission controller:", err)
    71  	}
    72  	return ctx, ac, cancel
    73  }
    74  
    75  func TestRegistrationStopChanFire(t *testing.T) {
    76  	test := testSetup(t, withNoTLS())
    77  	defer test.cancel()
    78  
    79  	stopCh := make(chan struct{})
    80  
    81  	var g errgroup.Group
    82  	g.Go(func() error {
    83  		return test.webhook.Run(stopCh)
    84  	})
    85  	close(stopCh)
    86  
    87  	if err := g.Wait(); err != nil {
    88  		t.Fatal("Error during run: ", err)
    89  	}
    90  	conn, err := net.Dial("tcp", test.addr)
    91  	if err == nil {
    92  		conn.Close()
    93  		t.Error("Unexpected success to dial to ", test.addr)
    94  	}
    95  }
    96  
    97  func newAdmissionControllerWebhook(t *testing.T, options Options, acs ...interface{}) (*Webhook, error) {
    98  	ctx, cancel, _ := SetupFakeContextWithCancel(t)
    99  	defer cancel()
   100  	ctx = WithOptions(ctx, options)
   101  	return New(ctx, acs)
   102  }
   103  
   104  func TestTLSMinVersionWebhookOption(t *testing.T) {
   105  	opts := newDefaultOptions()
   106  	t.Run("when TLSMinVersion is not configured, default is TLS 1.3", func(t *testing.T) {
   107  		wh, err := newAdmissionControllerWebhook(t, opts)
   108  		if err != nil {
   109  			t.Fatal("Unexpected error", err)
   110  		}
   111  		if wh.tlsConfig == nil {
   112  			t.Fatal("Expected tlsConfig to be set")
   113  		}
   114  		if wh.tlsConfig.MinVersion != tls.VersionTLS13 {
   115  			t.Errorf("Expected default MinVersion to be TLS 1.3 (%#x), got %#x", tls.VersionTLS13, wh.tlsConfig.MinVersion)
   116  		}
   117  	})
   118  	t.Run("when the TLS minimum version configured is supported", func(t *testing.T) {
   119  		opts.TLSMinVersion = tls.VersionTLS12
   120  		_, err := newAdmissionControllerWebhook(t, opts)
   121  		if err != nil {
   122  			t.Fatal("Unexpected error", err)
   123  		}
   124  	})
   125  	t.Run("when the TLS minimum version configured is not supported", func(t *testing.T) {
   126  		opts.TLSMinVersion = tls.VersionTLS11
   127  		_, err := newAdmissionControllerWebhook(t, opts)
   128  		if err == nil {
   129  			t.Fatal("Admission Controller Webhook creation expected to fail due to unsupported TLS version")
   130  		}
   131  	})
   132  }
   133  
   134  func TestTLSMaxVersionWebhookOption(t *testing.T) {
   135  	opts := newDefaultOptions()
   136  	t.Run("when TLSMaxVersion is not configured, default is used", func(t *testing.T) {
   137  		wh, err := newAdmissionControllerWebhook(t, opts)
   138  		if err != nil {
   139  			t.Fatal("Unexpected error", err)
   140  		}
   141  
   142  		if wh.tlsConfig != nil && wh.tlsConfig.MaxVersion != 0 {
   143  			t.Errorf("Expected MaxVersion to be 0 (default), got %d", wh.tlsConfig.MaxVersion)
   144  		}
   145  	})
   146  	t.Run("when TLSMaxVersion is configured to TLS 1.3", func(t *testing.T) {
   147  		opts.TLSMaxVersion = tls.VersionTLS13
   148  		wh, err := newAdmissionControllerWebhook(t, opts)
   149  		if err != nil {
   150  			t.Fatal("Unexpected error", err)
   151  		}
   152  		if wh.tlsConfig == nil {
   153  			t.Fatal("Expected tlsConfig to be set")
   154  		}
   155  		if wh.tlsConfig.MaxVersion != tls.VersionTLS13 {
   156  			t.Errorf("Expected MaxVersion to be TLS 1.3, got %d", wh.tlsConfig.MaxVersion)
   157  		}
   158  	})
   159  	t.Run("when both TLSMinVersion and TLSMaxVersion are TLS 1.3 (Modern profile)", func(t *testing.T) {
   160  		opts.TLSMinVersion = tls.VersionTLS13
   161  		opts.TLSMaxVersion = tls.VersionTLS13
   162  		wh, err := newAdmissionControllerWebhook(t, opts)
   163  		if err != nil {
   164  			t.Fatal("Unexpected error", err)
   165  		}
   166  		if wh.tlsConfig == nil {
   167  			t.Fatal("Expected tlsConfig to be set")
   168  		}
   169  		if wh.tlsConfig.MinVersion != tls.VersionTLS13 {
   170  			t.Errorf("Expected MinVersion to be TLS 1.3, got %d", wh.tlsConfig.MinVersion)
   171  		}
   172  		if wh.tlsConfig.MaxVersion != tls.VersionTLS13 {
   173  			t.Errorf("Expected MaxVersion to be TLS 1.3, got %d", wh.tlsConfig.MaxVersion)
   174  		}
   175  	})
   176  }
   177  
   178  func TestTLSMinMaxVersionValidation(t *testing.T) {
   179  	t.Run("max version less than min version returns error", func(t *testing.T) {
   180  		opts := newDefaultOptions()
   181  		opts.TLSMinVersion = tls.VersionTLS13
   182  		opts.TLSMaxVersion = tls.VersionTLS12
   183  		_, err := newAdmissionControllerWebhook(t, opts)
   184  		if err == nil {
   185  			t.Fatal("Expected error when TLS max version is less than min version")
   186  		}
   187  	})
   188  	t.Run("max version equal to min version is ok", func(t *testing.T) {
   189  		opts := newDefaultOptions()
   190  		opts.TLSMinVersion = tls.VersionTLS13
   191  		opts.TLSMaxVersion = tls.VersionTLS13
   192  		_, err := newAdmissionControllerWebhook(t, opts)
   193  		if err != nil {
   194  			t.Fatal("Unexpected error:", err)
   195  		}
   196  	})
   197  	t.Run("max version zero skips validation", func(t *testing.T) {
   198  		opts := newDefaultOptions()
   199  		opts.TLSMinVersion = tls.VersionTLS13
   200  		opts.TLSMaxVersion = 0
   201  		_, err := newAdmissionControllerWebhook(t, opts)
   202  		if err != nil {
   203  			t.Fatal("Unexpected error:", err)
   204  		}
   205  	})
   206  }
   207  
   208  func TestTLSCipherSuitesWebhookOption(t *testing.T) {
   209  	opts := newDefaultOptions()
   210  	t.Run("when TLSCipherSuites is not configured", func(t *testing.T) {
   211  		wh, err := newAdmissionControllerWebhook(t, opts)
   212  		if err != nil {
   213  			t.Fatal("Unexpected error", err)
   214  		}
   215  
   216  		if wh.tlsConfig != nil && wh.tlsConfig.CipherSuites != nil {
   217  			t.Errorf("Expected CipherSuites to be nil (default), got %v", wh.tlsConfig.CipherSuites)
   218  		}
   219  	})
   220  	t.Run("when TLSCipherSuites is configured with specific ciphers", func(t *testing.T) {
   221  		expectedCiphers := []uint16{
   222  			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   223  			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   224  		}
   225  		opts.TLSCipherSuites = expectedCiphers
   226  		wh, err := newAdmissionControllerWebhook(t, opts)
   227  		if err != nil {
   228  			t.Fatal("Unexpected error", err)
   229  		}
   230  		if wh.tlsConfig == nil {
   231  			t.Fatal("Expected tlsConfig to be set")
   232  		}
   233  		if len(wh.tlsConfig.CipherSuites) != len(expectedCiphers) {
   234  			t.Errorf("Expected %d cipher suites, got %d", len(expectedCiphers), len(wh.tlsConfig.CipherSuites))
   235  		}
   236  		for i, cipher := range expectedCiphers {
   237  			if wh.tlsConfig.CipherSuites[i] != cipher {
   238  				t.Errorf("Expected cipher suite at index %d to be %d, got %d", i, cipher, wh.tlsConfig.CipherSuites[i])
   239  			}
   240  		}
   241  	})
   242  }
   243  
   244  func TestTLSCurvePreferencesWebhookOption(t *testing.T) {
   245  	opts := newDefaultOptions()
   246  	t.Run("when TLSCurvePreferences is not configured", func(t *testing.T) {
   247  		wh, err := newAdmissionControllerWebhook(t, opts)
   248  		if err != nil {
   249  			t.Fatal("Unexpected error", err)
   250  		}
   251  
   252  		if wh.tlsConfig != nil && wh.tlsConfig.CurvePreferences != nil {
   253  			t.Errorf("Expected CurvePreferences to be nil (default), got %v", wh.tlsConfig.CurvePreferences)
   254  		}
   255  	})
   256  	t.Run("when TLSCurvePreferences is configured with specific curves", func(t *testing.T) {
   257  		expectedCurves := []tls.CurveID{
   258  			tls.CurveP256,
   259  			tls.CurveP384,
   260  			tls.X25519,
   261  		}
   262  		opts.TLSCurvePreferences = expectedCurves
   263  		wh, err := newAdmissionControllerWebhook(t, opts)
   264  		if err != nil {
   265  			t.Fatal("Unexpected error", err)
   266  		}
   267  		if wh.tlsConfig == nil {
   268  			t.Fatal("Expected tlsConfig to be set")
   269  		}
   270  		if len(wh.tlsConfig.CurvePreferences) != len(expectedCurves) {
   271  			t.Errorf("Expected %d curve preferences, got %d", len(expectedCurves), len(wh.tlsConfig.CurvePreferences))
   272  		}
   273  		for i, curve := range expectedCurves {
   274  			if wh.tlsConfig.CurvePreferences[i] != curve {
   275  				t.Errorf("Expected curve at index %d to be %d, got %d", i, curve, wh.tlsConfig.CurvePreferences[i])
   276  			}
   277  		}
   278  	})
   279  }
   280  
   281  func TestTLSConfigFromEnvironment(t *testing.T) {
   282  	t.Run("env min version used when opts min version is zero", func(t *testing.T) {
   283  		t.Setenv("WEBHOOK_TLS_MIN_VERSION", "1.2")
   284  		opts := newDefaultOptions()
   285  		wh, err := newAdmissionControllerWebhook(t, opts)
   286  		if err != nil {
   287  			t.Fatal("Unexpected error", err)
   288  		}
   289  		if wh.tlsConfig == nil {
   290  			t.Fatal("Expected tlsConfig to be set")
   291  		}
   292  		if wh.tlsConfig.MinVersion != tls.VersionTLS12 {
   293  			t.Errorf("Expected MinVersion from env to be TLS 1.2, got %d", wh.tlsConfig.MinVersion)
   294  		}
   295  	})
   296  
   297  	t.Run("opts min version takes precedence over env", func(t *testing.T) {
   298  		t.Setenv("WEBHOOK_TLS_MIN_VERSION", "1.2")
   299  		opts := newDefaultOptions()
   300  		opts.TLSMinVersion = tls.VersionTLS13
   301  		wh, err := newAdmissionControllerWebhook(t, opts)
   302  		if err != nil {
   303  			t.Fatal("Unexpected error", err)
   304  		}
   305  		if wh.tlsConfig == nil {
   306  			t.Fatal("Expected tlsConfig to be set")
   307  		}
   308  		if wh.tlsConfig.MinVersion != tls.VersionTLS13 {
   309  			t.Errorf("Expected MinVersion from opts (TLS 1.3), got %d", wh.tlsConfig.MinVersion)
   310  		}
   311  	})
   312  
   313  	t.Run("env max version used when opts max version is zero", func(t *testing.T) {
   314  		t.Setenv("WEBHOOK_TLS_MAX_VERSION", "1.3")
   315  		opts := newDefaultOptions()
   316  		wh, err := newAdmissionControllerWebhook(t, opts)
   317  		if err != nil {
   318  			t.Fatal("Unexpected error", err)
   319  		}
   320  		if wh.tlsConfig == nil {
   321  			t.Fatal("Expected tlsConfig to be set")
   322  		}
   323  		if wh.tlsConfig.MaxVersion != tls.VersionTLS13 {
   324  			t.Errorf("Expected MaxVersion from env to be TLS 1.3, got %d", wh.tlsConfig.MaxVersion)
   325  		}
   326  	})
   327  
   328  	t.Run("opts max version takes precedence over env", func(t *testing.T) {
   329  		t.Setenv("WEBHOOK_TLS_MAX_VERSION", "1.2")
   330  		opts := newDefaultOptions()
   331  		opts.TLSMaxVersion = tls.VersionTLS13
   332  		wh, err := newAdmissionControllerWebhook(t, opts)
   333  		if err != nil {
   334  			t.Fatal("Unexpected error", err)
   335  		}
   336  		if wh.tlsConfig == nil {
   337  			t.Fatal("Expected tlsConfig to be set")
   338  		}
   339  		if wh.tlsConfig.MaxVersion != tls.VersionTLS13 {
   340  			t.Errorf("Expected MaxVersion from opts (TLS 1.3), got %d", wh.tlsConfig.MaxVersion)
   341  		}
   342  	})
   343  
   344  	t.Run("env cipher suites used when opts cipher suites is nil", func(t *testing.T) {
   345  		t.Setenv("WEBHOOK_TLS_CIPHER_SUITES", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
   346  		opts := newDefaultOptions()
   347  		wh, err := newAdmissionControllerWebhook(t, opts)
   348  		if err != nil {
   349  			t.Fatal("Unexpected error", err)
   350  		}
   351  		if wh.tlsConfig == nil {
   352  			t.Fatal("Expected tlsConfig to be set")
   353  		}
   354  		if len(wh.tlsConfig.CipherSuites) != 1 {
   355  			t.Fatalf("Expected 1 cipher suite from env, got %d", len(wh.tlsConfig.CipherSuites))
   356  		}
   357  		if wh.tlsConfig.CipherSuites[0] != tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 {
   358  			t.Errorf("Expected CipherSuites from env, got %v", wh.tlsConfig.CipherSuites)
   359  		}
   360  	})
   361  
   362  	t.Run("opts cipher suites take precedence over env", func(t *testing.T) {
   363  		t.Setenv("WEBHOOK_TLS_CIPHER_SUITES", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
   364  		opts := newDefaultOptions()
   365  		opts.TLSCipherSuites = []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}
   366  		wh, err := newAdmissionControllerWebhook(t, opts)
   367  		if err != nil {
   368  			t.Fatal("Unexpected error", err)
   369  		}
   370  		if wh.tlsConfig == nil {
   371  			t.Fatal("Expected tlsConfig to be set")
   372  		}
   373  		if len(wh.tlsConfig.CipherSuites) != 1 {
   374  			t.Fatalf("Expected 1 cipher suite from opts, got %d", len(wh.tlsConfig.CipherSuites))
   375  		}
   376  		if wh.tlsConfig.CipherSuites[0] != tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 {
   377  			t.Errorf("Expected CipherSuites from opts, got %v", wh.tlsConfig.CipherSuites)
   378  		}
   379  	})
   380  
   381  	t.Run("env curve preferences used when opts curve preferences is nil", func(t *testing.T) {
   382  		t.Setenv("WEBHOOK_TLS_CURVE_PREFERENCES", "X25519,CurveP256")
   383  		opts := newDefaultOptions()
   384  		wh, err := newAdmissionControllerWebhook(t, opts)
   385  		if err != nil {
   386  			t.Fatal("Unexpected error", err)
   387  		}
   388  		if wh.tlsConfig == nil {
   389  			t.Fatal("Expected tlsConfig to be set")
   390  		}
   391  		if len(wh.tlsConfig.CurvePreferences) != 2 {
   392  			t.Fatalf("Expected 2 curve preferences from env, got %d", len(wh.tlsConfig.CurvePreferences))
   393  		}
   394  		if wh.tlsConfig.CurvePreferences[0] != tls.X25519 {
   395  			t.Errorf("Expected CurvePreferences[0] = X25519, got %d", wh.tlsConfig.CurvePreferences[0])
   396  		}
   397  		if wh.tlsConfig.CurvePreferences[1] != tls.CurveP256 {
   398  			t.Errorf("Expected CurvePreferences[1] = CurveP256, got %d", wh.tlsConfig.CurvePreferences[1])
   399  		}
   400  	})
   401  
   402  	t.Run("opts curve preferences take precedence over env", func(t *testing.T) {
   403  		t.Setenv("WEBHOOK_TLS_CURVE_PREFERENCES", "X25519")
   404  		opts := newDefaultOptions()
   405  		opts.TLSCurvePreferences = []tls.CurveID{tls.CurveP384}
   406  		wh, err := newAdmissionControllerWebhook(t, opts)
   407  		if err != nil {
   408  			t.Fatal("Unexpected error", err)
   409  		}
   410  		if wh.tlsConfig == nil {
   411  			t.Fatal("Expected tlsConfig to be set")
   412  		}
   413  		if len(wh.tlsConfig.CurvePreferences) != 1 {
   414  			t.Fatalf("Expected 1 curve preference from opts, got %d", len(wh.tlsConfig.CurvePreferences))
   415  		}
   416  		if wh.tlsConfig.CurvePreferences[0] != tls.CurveP384 {
   417  			t.Errorf("Expected CurvePreferences from opts, got %v", wh.tlsConfig.CurvePreferences)
   418  		}
   419  	})
   420  
   421  	t.Run("invalid env TLS config returns error", func(t *testing.T) {
   422  		t.Setenv("WEBHOOK_TLS_MIN_VERSION", "bad")
   423  		opts := newDefaultOptions()
   424  		_, err := newAdmissionControllerWebhook(t, opts)
   425  		if err == nil {
   426  			t.Fatal("Expected error for invalid env TLS min version")
   427  		}
   428  	})
   429  }
   430  
   431  func TestTLSConfigCombinedOptions(t *testing.T) {
   432  	opts := newDefaultOptions()
   433  	t.Run("when all TLS options are configured together", func(t *testing.T) {
   434  		opts.TLSMinVersion = tls.VersionTLS12
   435  		opts.TLSMaxVersion = tls.VersionTLS13
   436  		opts.TLSCipherSuites = []uint16{
   437  			tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
   438  			tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
   439  		}
   440  		opts.TLSCurvePreferences = []tls.CurveID{
   441  			tls.CurveP256,
   442  			tls.X25519,
   443  		}
   444  
   445  		wh, err := newAdmissionControllerWebhook(t, opts)
   446  		if err != nil {
   447  			t.Fatal("Unexpected error", err)
   448  		}
   449  
   450  		if wh.tlsConfig == nil {
   451  			t.Fatal("Expected tlsConfig to be set")
   452  		}
   453  
   454  		if wh.tlsConfig.MinVersion != tls.VersionTLS12 {
   455  			t.Errorf("Expected MinVersion to be TLS 1.2, got %d", wh.tlsConfig.MinVersion)
   456  		}
   457  		if wh.tlsConfig.MaxVersion != tls.VersionTLS13 {
   458  			t.Errorf("Expected MaxVersion to be TLS 1.3, got %d", wh.tlsConfig.MaxVersion)
   459  		}
   460  		if len(wh.tlsConfig.CipherSuites) != 2 {
   461  			t.Errorf("Expected 2 cipher suites, got %d", len(wh.tlsConfig.CipherSuites))
   462  		}
   463  		if len(wh.tlsConfig.CurvePreferences) != 2 {
   464  			t.Errorf("Expected 2 curve preferences, got %d", len(wh.tlsConfig.CurvePreferences))
   465  		}
   466  	})
   467  }