github.com/projectcontour/contour@v1.28.2/cmd/contour/servecontext_test.go (about)

     1  // Copyright Project Contour Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  //     http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package main
    15  
    16  import (
    17  	"crypto/tls"
    18  	"crypto/x509"
    19  	"net"
    20  	"os"
    21  	"path/filepath"
    22  	"reflect"
    23  	"sort"
    24  	"testing"
    25  	"time"
    26  
    27  	contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1"
    28  	contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1"
    29  	envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3"
    30  	"github.com/projectcontour/contour/internal/fixture"
    31  	"github.com/projectcontour/contour/internal/ref"
    32  	"github.com/projectcontour/contour/pkg/config"
    33  	"github.com/stretchr/testify/assert"
    34  	"github.com/tsaarni/certyaml"
    35  	"google.golang.org/grpc"
    36  )
    37  
    38  func TestServeContextProxyRootNamespaces(t *testing.T) {
    39  	tests := map[string]struct {
    40  		ctx  serveContext
    41  		want []string
    42  	}{
    43  		"empty": {
    44  			ctx: serveContext{
    45  				rootNamespaces: "",
    46  			},
    47  			want: nil,
    48  		},
    49  		"blank-ish": {
    50  			ctx: serveContext{
    51  				rootNamespaces: " \t ",
    52  			},
    53  			want: nil,
    54  		},
    55  		"one value": {
    56  			ctx: serveContext{
    57  				rootNamespaces: "projectcontour",
    58  			},
    59  			want: []string{"projectcontour"},
    60  		},
    61  		"multiple, easy": {
    62  			ctx: serveContext{
    63  				rootNamespaces: "prod1,prod2,prod3",
    64  			},
    65  			want: []string{"prod1", "prod2", "prod3"},
    66  		},
    67  		"multiple, hard": {
    68  			ctx: serveContext{
    69  				rootNamespaces: "prod1, prod2, prod3 ",
    70  			},
    71  			want: []string{"prod1", "prod2", "prod3"},
    72  		},
    73  	}
    74  
    75  	for name, tc := range tests {
    76  		t.Run(name, func(t *testing.T) {
    77  			got := tc.ctx.proxyRootNamespaces()
    78  			if !reflect.DeepEqual(got, tc.want) {
    79  				t.Fatalf("expected: %q, got: %q", tc.want, got)
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  func TestServeContextTLSParams(t *testing.T) {
    86  	tests := map[string]struct {
    87  		tls         *contour_api_v1alpha1.TLS
    88  		expectError bool
    89  	}{
    90  		"tls supplied correctly": {
    91  			tls: &contour_api_v1alpha1.TLS{
    92  				CAFile:   "cacert.pem",
    93  				CertFile: "contourcert.pem",
    94  				KeyFile:  "contourkey.pem",
    95  				Insecure: ref.To(false),
    96  			},
    97  			expectError: false,
    98  		},
    99  		"tls partially supplied": {
   100  			tls: &contour_api_v1alpha1.TLS{
   101  				CertFile: "contourcert.pem",
   102  				KeyFile:  "contourkey.pem",
   103  				Insecure: ref.To(false),
   104  			},
   105  			expectError: true,
   106  		},
   107  		"tls not supplied": {
   108  			tls:         &contour_api_v1alpha1.TLS{},
   109  			expectError: true,
   110  		},
   111  	}
   112  	for name, tc := range tests {
   113  		t.Run(name, func(t *testing.T) {
   114  			err := verifyTLSFlags(tc.tls)
   115  			goterror := err != nil
   116  			if goterror != tc.expectError {
   117  				t.Errorf("TLS Config: %s", err)
   118  			}
   119  		})
   120  	}
   121  }
   122  
   123  func TestServeContextCertificateHandling(t *testing.T) {
   124  	// Create trusted CA, server and client certs.
   125  	trustedCACert := certyaml.Certificate{
   126  		Subject: "cn=trusted-ca",
   127  	}
   128  	contourCertBeforeRotation := certyaml.Certificate{
   129  		Subject:         "cn=contour-before-rotation",
   130  		SubjectAltNames: []string{"DNS:localhost"},
   131  		Issuer:          &trustedCACert,
   132  	}
   133  	contourCertAfterRotation := certyaml.Certificate{
   134  		Subject:         "cn=contour-after-rotation",
   135  		SubjectAltNames: []string{"DNS:localhost"},
   136  		Issuer:          &trustedCACert,
   137  	}
   138  	trustedEnvoyCert := certyaml.Certificate{
   139  		Subject: "cn=trusted-envoy",
   140  		Issuer:  &trustedCACert,
   141  	}
   142  
   143  	// Create another CA and a client cert to test that untrusted clients are denied.
   144  	untrustedCACert := certyaml.Certificate{
   145  		Subject: "cn=untrusted-ca",
   146  	}
   147  	untrustedClientCert := certyaml.Certificate{
   148  		Subject: "cn=untrusted-client",
   149  		Issuer:  &untrustedCACert,
   150  	}
   151  
   152  	caCertPool := x509.NewCertPool()
   153  	ca, err := trustedCACert.X509Certificate()
   154  	checkFatalErr(t, err)
   155  	caCertPool.AddCert(&ca)
   156  
   157  	tests := map[string]struct {
   158  		serverCredentials *certyaml.Certificate
   159  		clientCredentials *certyaml.Certificate
   160  		expectError       bool
   161  	}{
   162  		"successful TLS connection established": {
   163  			serverCredentials: &contourCertBeforeRotation,
   164  			clientCredentials: &trustedEnvoyCert,
   165  			expectError:       false,
   166  		},
   167  		"rotating server credentials returns new server cert": {
   168  			serverCredentials: &contourCertAfterRotation,
   169  			clientCredentials: &trustedEnvoyCert,
   170  			expectError:       false,
   171  		},
   172  		"rotating server credentials again to ensure rotation can be repeated": {
   173  			serverCredentials: &contourCertBeforeRotation,
   174  			clientCredentials: &trustedEnvoyCert,
   175  			expectError:       false,
   176  		},
   177  		"fail to connect with client certificate which is not signed by correct CA": {
   178  			serverCredentials: &contourCertBeforeRotation,
   179  			clientCredentials: &untrustedClientCert,
   180  			expectError:       true,
   181  		},
   182  	}
   183  
   184  	// Create temporary directory to store certificates and key for the server.
   185  	configDir, err := os.MkdirTemp("", "contour-testdata-")
   186  	checkFatalErr(t, err)
   187  	defer os.RemoveAll(configDir)
   188  
   189  	contourTLS := &contour_api_v1alpha1.TLS{
   190  		CAFile:   filepath.Join(configDir, "CAcert.pem"),
   191  		CertFile: filepath.Join(configDir, "contourcert.pem"),
   192  		KeyFile:  filepath.Join(configDir, "contourkey.pem"),
   193  		Insecure: ref.To(false),
   194  	}
   195  
   196  	// Initial set of credentials must be written into temp directory before
   197  	// starting the tests to avoid error at server startup.
   198  	err = trustedCACert.WritePEM(contourTLS.CAFile, filepath.Join(configDir, "CAkey.pem"))
   199  	checkFatalErr(t, err)
   200  	err = contourCertBeforeRotation.WritePEM(contourTLS.CertFile, contourTLS.KeyFile)
   201  	checkFatalErr(t, err)
   202  
   203  	// Start a dummy server.
   204  	log := fixture.NewTestLogger(t)
   205  	opts := grpcOptions(log, contourTLS)
   206  	g := grpc.NewServer(opts...)
   207  	if g == nil {
   208  		t.Error("failed to create server")
   209  	}
   210  
   211  	address := "localhost:8001"
   212  	l, err := net.Listen("tcp", address)
   213  	checkFatalErr(t, err)
   214  
   215  	go func() {
   216  		err := g.Serve(l)
   217  		checkFatalErr(t, err)
   218  	}()
   219  	defer g.GracefulStop()
   220  
   221  	for name, tc := range tests {
   222  		t.Run(name, func(t *testing.T) {
   223  			// Store certificate and key to temp dir used by serveContext.
   224  			err = tc.serverCredentials.WritePEM(contourTLS.CertFile, contourTLS.KeyFile)
   225  			checkFatalErr(t, err)
   226  			clientCert, _ := tc.clientCredentials.TLSCertificate()
   227  			receivedCert, err := tryConnect(address, clientCert, caCertPool)
   228  			gotError := err != nil
   229  			if gotError != tc.expectError {
   230  				t.Errorf("Unexpected result when connecting to the server: %s", err)
   231  			}
   232  			if err == nil {
   233  				expectedCert, _ := tc.serverCredentials.X509Certificate()
   234  				assert.Equal(t, receivedCert, &expectedCert)
   235  			}
   236  		})
   237  	}
   238  }
   239  
   240  func TestTlsVersionDeprecation(t *testing.T) {
   241  	// To get tls.Config for the gRPC XDS server, we need to arrange valid TLS certificates and keys.
   242  	// Create temporary directory to store them for the server.
   243  	configDir, err := os.MkdirTemp("", "contour-testdata-")
   244  	checkFatalErr(t, err)
   245  	defer os.RemoveAll(configDir)
   246  
   247  	caCert := certyaml.Certificate{
   248  		Subject: "cn=ca",
   249  	}
   250  	contourCert := certyaml.Certificate{
   251  		Subject: "cn=contourBeforeRotation",
   252  		Issuer:  &caCert,
   253  	}
   254  
   255  	contourTLS := &contour_api_v1alpha1.TLS{
   256  		CAFile:   filepath.Join(configDir, "CAcert.pem"),
   257  		CertFile: filepath.Join(configDir, "contourcert.pem"),
   258  		KeyFile:  filepath.Join(configDir, "contourkey.pem"),
   259  		Insecure: ref.To(false),
   260  	}
   261  
   262  	err = caCert.WritePEM(contourTLS.CAFile, filepath.Join(configDir, "CAkey.pem"))
   263  	checkFatalErr(t, err)
   264  	err = contourCert.WritePEM(contourTLS.CertFile, contourTLS.KeyFile)
   265  	checkFatalErr(t, err)
   266  
   267  	// Get preliminary TLS config from the serveContext.
   268  	log := fixture.NewTestLogger(t)
   269  	preliminaryTLSConfig := tlsconfig(log, contourTLS)
   270  
   271  	// Get actual TLS config that will be used during TLS handshake.
   272  	tlsConfig, err := preliminaryTLSConfig.GetConfigForClient(nil)
   273  	checkFatalErr(t, err)
   274  
   275  	assert.Equal(t, tlsConfig.MinVersion, uint16(tls.VersionTLS13))
   276  }
   277  
   278  func checkFatalErr(t *testing.T, err error) {
   279  	t.Helper()
   280  	if err != nil {
   281  		t.Fatal(err)
   282  	}
   283  }
   284  
   285  // tryConnect tries to establish TLS connection to the server.
   286  // If successful, return the server certificate.
   287  func tryConnect(address string, clientCert tls.Certificate, caCertPool *x509.CertPool) (*x509.Certificate, error) {
   288  	clientConfig := &tls.Config{
   289  		ServerName:   "localhost",
   290  		MinVersion:   tls.VersionTLS13,
   291  		Certificates: []tls.Certificate{clientCert},
   292  		RootCAs:      caCertPool,
   293  	}
   294  	conn, err := tls.Dial("tcp", address, clientConfig)
   295  	if err != nil {
   296  		return nil, err
   297  	}
   298  	defer conn.Close()
   299  
   300  	err = peekError(conn)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  
   305  	return conn.ConnectionState().PeerCertificates[0], nil
   306  }
   307  
   308  // peekError is a workaround for TLS 1.3: due to shortened handshake, TLS alert
   309  // from server is received at first read from the socket.
   310  // To receive alert for bad certificate, this function tries to read one byte.
   311  // Adapted from https://golang.org/src/crypto/tls/handshake_client_test.go
   312  func peekError(conn net.Conn) error {
   313  	_ = conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond))
   314  	_, err := conn.Read(make([]byte, 1))
   315  	if err != nil {
   316  		if netErr, ok := err.(net.Error); !ok || !netErr.Timeout() {
   317  			return err
   318  		}
   319  	}
   320  	return nil
   321  }
   322  
   323  func TestParseHTTPVersions(t *testing.T) {
   324  	cases := map[string]struct {
   325  		versions      []contour_api_v1alpha1.HTTPVersionType
   326  		parseVersions []envoy_v3.HTTPVersionType
   327  	}{
   328  		"empty": {
   329  			versions:      []contour_api_v1alpha1.HTTPVersionType{},
   330  			parseVersions: nil,
   331  		},
   332  		"http/1.1": {
   333  			versions:      []contour_api_v1alpha1.HTTPVersionType{contour_api_v1alpha1.HTTPVersion1},
   334  			parseVersions: []envoy_v3.HTTPVersionType{envoy_v3.HTTPVersion1},
   335  		},
   336  		"http/1.1+http/2": {
   337  			versions:      []contour_api_v1alpha1.HTTPVersionType{contour_api_v1alpha1.HTTPVersion1, contour_api_v1alpha1.HTTPVersion2},
   338  			parseVersions: []envoy_v3.HTTPVersionType{envoy_v3.HTTPVersion1, envoy_v3.HTTPVersion2},
   339  		},
   340  		"http/1.1+http/2 duplicated": {
   341  			versions: []contour_api_v1alpha1.HTTPVersionType{
   342  				contour_api_v1alpha1.HTTPVersion1, contour_api_v1alpha1.HTTPVersion2,
   343  				contour_api_v1alpha1.HTTPVersion1, contour_api_v1alpha1.HTTPVersion2,
   344  			},
   345  			parseVersions: []envoy_v3.HTTPVersionType{envoy_v3.HTTPVersion1, envoy_v3.HTTPVersion2},
   346  		},
   347  	}
   348  
   349  	for name, testcase := range cases {
   350  		testcase := testcase
   351  		t.Run(name, func(t *testing.T) {
   352  			vers := parseDefaultHTTPVersions(testcase.versions)
   353  
   354  			// parseDefaultHTTPVersions doesn't guarantee a stable result, but the order doesn't matter.
   355  			sort.Slice(vers,
   356  				func(i, j int) bool { return vers[i] < vers[j] })
   357  			sort.Slice(testcase.parseVersions,
   358  				func(i, j int) bool { return testcase.parseVersions[i] < testcase.parseVersions[j] })
   359  
   360  			assert.Equal(t, testcase.parseVersions, vers)
   361  		})
   362  	}
   363  }
   364  
   365  func TestConvertServeContext(t *testing.T) {
   366  	defaultContext := func() *serveContext {
   367  		ctx := newServeContext()
   368  		ctx.ServerConfig = ServerConfig{
   369  			xdsAddr:     "127.0.0.1",
   370  			xdsPort:     8001,
   371  			caFile:      "/certs/ca.crt",
   372  			contourCert: "/certs/cert.crt",
   373  			contourKey:  "/certs/cert.key",
   374  		}
   375  		return ctx
   376  	}
   377  
   378  	defaultContourConfiguration := func() contour_api_v1alpha1.ContourConfigurationSpec {
   379  		return contour_api_v1alpha1.ContourConfigurationSpec{
   380  			XDSServer: &contour_api_v1alpha1.XDSServerConfig{
   381  				Type:    contour_api_v1alpha1.ContourServerType,
   382  				Address: "127.0.0.1",
   383  				Port:    8001,
   384  				TLS: &contour_api_v1alpha1.TLS{
   385  					CAFile:   "/certs/ca.crt",
   386  					CertFile: "/certs/cert.crt",
   387  					KeyFile:  "/certs/cert.key",
   388  					Insecure: ref.To(false),
   389  				},
   390  			},
   391  			Ingress: &contour_api_v1alpha1.IngressConfig{
   392  				ClassNames:    nil,
   393  				StatusAddress: "",
   394  			},
   395  			Debug: &contour_api_v1alpha1.DebugConfig{
   396  				Address: "127.0.0.1",
   397  				Port:    6060,
   398  			},
   399  			Health: &contour_api_v1alpha1.HealthConfig{
   400  				Address: "0.0.0.0",
   401  				Port:    8000,
   402  			},
   403  			Envoy: &contour_api_v1alpha1.EnvoyConfig{
   404  				Service: &contour_api_v1alpha1.NamespacedName{
   405  					Name:      "envoy",
   406  					Namespace: "projectcontour",
   407  				},
   408  				Listener: &contour_api_v1alpha1.EnvoyListenerConfig{
   409  					UseProxyProto:              ref.To(false),
   410  					DisableAllowChunkedLength:  ref.To(false),
   411  					DisableMergeSlashes:        ref.To(false),
   412  					ServerHeaderTransformation: contour_api_v1alpha1.OverwriteServerHeader,
   413  					TLS: &contour_api_v1alpha1.EnvoyTLS{
   414  						MinimumProtocolVersion: "",
   415  						MaximumProtocolVersion: "",
   416  					},
   417  					SocketOptions: &contour_api_v1alpha1.SocketOptions{
   418  						TOS:          0,
   419  						TrafficClass: 0,
   420  					},
   421  				},
   422  				HTTPListener: &contour_api_v1alpha1.EnvoyListener{
   423  					Address:   "0.0.0.0",
   424  					Port:      8080,
   425  					AccessLog: "/dev/stdout",
   426  				},
   427  				HTTPSListener: &contour_api_v1alpha1.EnvoyListener{
   428  					Address:   "0.0.0.0",
   429  					Port:      8443,
   430  					AccessLog: "/dev/stdout",
   431  				},
   432  				Health: &contour_api_v1alpha1.HealthConfig{
   433  					Address: "0.0.0.0",
   434  					Port:    8002,
   435  				},
   436  				Metrics: &contour_api_v1alpha1.MetricsConfig{
   437  					Address: "0.0.0.0",
   438  					Port:    8002,
   439  				},
   440  				ClientCertificate: nil,
   441  				Logging: &contour_api_v1alpha1.EnvoyLogging{
   442  					AccessLogFormat:       contour_api_v1alpha1.EnvoyAccessLog,
   443  					AccessLogFormatString: "",
   444  					AccessLogLevel:        contour_api_v1alpha1.LogLevelInfo,
   445  					AccessLogJSONFields: contour_api_v1alpha1.AccessLogJSONFields([]string{
   446  						"@timestamp",
   447  						"authority",
   448  						"bytes_received",
   449  						"bytes_sent",
   450  						"downstream_local_address",
   451  						"downstream_remote_address",
   452  						"duration",
   453  						"method",
   454  						"path",
   455  						"protocol",
   456  						"request_id",
   457  						"requested_server_name",
   458  						"response_code",
   459  						"response_flags",
   460  						"uber_trace_id",
   461  						"upstream_cluster",
   462  						"upstream_host",
   463  						"upstream_local_address",
   464  						"upstream_service_time",
   465  						"user_agent",
   466  						"x_forwarded_for",
   467  						"grpc_status",
   468  						"grpc_status_number",
   469  					}),
   470  				},
   471  				DefaultHTTPVersions: nil,
   472  				Timeouts: &contour_api_v1alpha1.TimeoutParameters{
   473  					ConnectionIdleTimeout: ref.To("60s"),
   474  					ConnectTimeout:        ref.To("2s"),
   475  				},
   476  				Cluster: &contour_api_v1alpha1.ClusterParameters{
   477  					DNSLookupFamily:              contour_api_v1alpha1.AutoClusterDNSFamily,
   478  					GlobalCircuitBreakerDefaults: nil,
   479  					UpstreamTLS: &contour_api_v1alpha1.EnvoyTLS{
   480  						MinimumProtocolVersion: "",
   481  						MaximumProtocolVersion: "",
   482  					},
   483  				},
   484  				Network: &contour_api_v1alpha1.NetworkParameters{
   485  					EnvoyAdminPort:    ref.To(9001),
   486  					XffNumTrustedHops: ref.To(uint32(0)),
   487  				},
   488  			},
   489  			Gateway: nil,
   490  			HTTPProxy: &contour_api_v1alpha1.HTTPProxyConfig{
   491  				DisablePermitInsecure: ref.To(false),
   492  				FallbackCertificate:   nil,
   493  			},
   494  			EnableExternalNameService:   ref.To(false),
   495  			RateLimitService:            nil,
   496  			GlobalExternalAuthorization: nil,
   497  			Policy: &contour_api_v1alpha1.PolicyConfig{
   498  				RequestHeadersPolicy:  &contour_api_v1alpha1.HeadersPolicy{},
   499  				ResponseHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{},
   500  				ApplyToIngress:        ref.To(false),
   501  			},
   502  			Metrics: &contour_api_v1alpha1.MetricsConfig{
   503  				Address: "0.0.0.0",
   504  				Port:    8000,
   505  			},
   506  		}
   507  	}
   508  
   509  	cases := map[string]struct {
   510  		getServeContext         func(ctx *serveContext) *serveContext
   511  		getContourConfiguration func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec
   512  	}{
   513  		"default ServeContext": {
   514  			getServeContext: func(ctx *serveContext) *serveContext {
   515  				return ctx
   516  			},
   517  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   518  				return cfg
   519  			},
   520  		},
   521  		"headers policy": {
   522  			getServeContext: func(ctx *serveContext) *serveContext {
   523  				ctx.Config.Policy = config.PolicyParameters{
   524  					RequestHeadersPolicy: config.HeadersPolicy{
   525  						Set:    map[string]string{"custom-request-header-set": "foo-bar", "Host": "request-bar.com"},
   526  						Remove: []string{"custom-request-header-remove"},
   527  					},
   528  					ResponseHeadersPolicy: config.HeadersPolicy{
   529  						Set:    map[string]string{"custom-response-header-set": "foo-bar", "Host": "response-bar.com"},
   530  						Remove: []string{"custom-response-header-remove"},
   531  					},
   532  					ApplyToIngress: true,
   533  				}
   534  				return ctx
   535  			},
   536  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   537  				cfg.Policy = &contour_api_v1alpha1.PolicyConfig{
   538  					RequestHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{
   539  						Set:    map[string]string{"custom-request-header-set": "foo-bar", "Host": "request-bar.com"},
   540  						Remove: []string{"custom-request-header-remove"},
   541  					},
   542  					ResponseHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{
   543  						Set:    map[string]string{"custom-response-header-set": "foo-bar", "Host": "response-bar.com"},
   544  						Remove: []string{"custom-response-header-remove"},
   545  					},
   546  					ApplyToIngress: ref.To(true),
   547  				}
   548  				return cfg
   549  			},
   550  		},
   551  		"ingress": {
   552  			getServeContext: func(ctx *serveContext) *serveContext {
   553  				ctx.ingressClassName = "coolclass"
   554  				ctx.Config.IngressStatusAddress = "1.2.3.4"
   555  				return ctx
   556  			},
   557  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   558  				cfg.Ingress = &contour_api_v1alpha1.IngressConfig{
   559  					ClassNames:    []string{"coolclass"},
   560  					StatusAddress: "1.2.3.4",
   561  				}
   562  				return cfg
   563  			},
   564  		},
   565  		"gatewayapi - controller": {
   566  			getServeContext: func(ctx *serveContext) *serveContext {
   567  				ctx.Config.GatewayConfig = &config.GatewayParameters{
   568  					ControllerName: "projectcontour.io/gateway-controller",
   569  				}
   570  				return ctx
   571  			},
   572  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   573  				cfg.Gateway = &contour_api_v1alpha1.GatewayConfig{
   574  					ControllerName: "projectcontour.io/gateway-controller",
   575  				}
   576  				return cfg
   577  			},
   578  		},
   579  		"gatewayapi - specific gateway": {
   580  			getServeContext: func(ctx *serveContext) *serveContext {
   581  				ctx.Config.GatewayConfig = &config.GatewayParameters{
   582  					GatewayRef: &config.NamespacedName{
   583  						Namespace: "gateway-namespace",
   584  						Name:      "gateway-name",
   585  					},
   586  				}
   587  				return ctx
   588  			},
   589  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   590  				cfg.Gateway = &contour_api_v1alpha1.GatewayConfig{
   591  					GatewayRef: &contour_api_v1alpha1.NamespacedName{
   592  						Namespace: "gateway-namespace",
   593  						Name:      "gateway-name",
   594  					},
   595  				}
   596  				return cfg
   597  			},
   598  		},
   599  		"client certificate": {
   600  			getServeContext: func(ctx *serveContext) *serveContext {
   601  				ctx.Config.TLS.ClientCertificate = config.NamespacedName{
   602  					Name:      "cert",
   603  					Namespace: "secretplace",
   604  				}
   605  				return ctx
   606  			},
   607  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   608  				cfg.Envoy.ClientCertificate = &contour_api_v1alpha1.NamespacedName{
   609  					Name:      "cert",
   610  					Namespace: "secretplace",
   611  				}
   612  				return cfg
   613  			},
   614  		},
   615  		"httpproxy": {
   616  			getServeContext: func(ctx *serveContext) *serveContext {
   617  				ctx.Config.DisablePermitInsecure = true
   618  				ctx.Config.TLS.FallbackCertificate = config.NamespacedName{
   619  					Name:      "fallbackname",
   620  					Namespace: "fallbacknamespace",
   621  				}
   622  				return ctx
   623  			},
   624  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   625  				cfg.HTTPProxy = &contour_api_v1alpha1.HTTPProxyConfig{
   626  					DisablePermitInsecure: ref.To(true),
   627  					FallbackCertificate: &contour_api_v1alpha1.NamespacedName{
   628  						Name:      "fallbackname",
   629  						Namespace: "fallbacknamespace",
   630  					},
   631  				}
   632  				return cfg
   633  			},
   634  		},
   635  		"ratelimit": {
   636  			getServeContext: func(ctx *serveContext) *serveContext {
   637  				ctx.Config.RateLimitService = config.RateLimitService{
   638  					ExtensionService:            "ratens/ratelimitext",
   639  					Domain:                      "contour",
   640  					FailOpen:                    true,
   641  					EnableXRateLimitHeaders:     true,
   642  					EnableResourceExhaustedCode: true,
   643  					DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{
   644  						Descriptors: []contour_api_v1.RateLimitDescriptor{
   645  							{
   646  								Entries: []contour_api_v1.RateLimitDescriptorEntry{
   647  									{
   648  										GenericKey: &contour_api_v1.GenericKeyDescriptor{
   649  											Key:   "foo",
   650  											Value: "bar",
   651  										},
   652  									},
   653  								},
   654  							},
   655  						},
   656  					},
   657  				}
   658  				return ctx
   659  			},
   660  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   661  				cfg.RateLimitService = &contour_api_v1alpha1.RateLimitServiceConfig{
   662  					ExtensionService: contour_api_v1alpha1.NamespacedName{
   663  						Name:      "ratelimitext",
   664  						Namespace: "ratens",
   665  					},
   666  					Domain:                      "contour",
   667  					FailOpen:                    ref.To(true),
   668  					EnableXRateLimitHeaders:     ref.To(true),
   669  					EnableResourceExhaustedCode: ref.To(true),
   670  					DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{
   671  						Descriptors: []contour_api_v1.RateLimitDescriptor{
   672  							{
   673  								Entries: []contour_api_v1.RateLimitDescriptorEntry{
   674  									{
   675  										GenericKey: &contour_api_v1.GenericKeyDescriptor{
   676  											Key:   "foo",
   677  											Value: "bar",
   678  										},
   679  									},
   680  								},
   681  							},
   682  						},
   683  					},
   684  				}
   685  				return cfg
   686  			},
   687  		},
   688  		"default http versions": {
   689  			getServeContext: func(ctx *serveContext) *serveContext {
   690  				ctx.Config.DefaultHTTPVersions = []config.HTTPVersionType{
   691  					config.HTTPVersion1,
   692  				}
   693  				return ctx
   694  			},
   695  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   696  				cfg.Envoy.DefaultHTTPVersions = []contour_api_v1alpha1.HTTPVersionType{
   697  					contour_api_v1alpha1.HTTPVersion1,
   698  				}
   699  				return cfg
   700  			},
   701  		},
   702  		"access log": {
   703  			getServeContext: func(ctx *serveContext) *serveContext {
   704  				ctx.Config.AccessLogFormat = config.JSONAccessLog
   705  				ctx.Config.AccessLogFormatString = "foo-bar-baz"
   706  				ctx.Config.AccessLogFields = []string{"custom_field"}
   707  				return ctx
   708  			},
   709  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   710  				cfg.Envoy.Logging = &contour_api_v1alpha1.EnvoyLogging{
   711  					AccessLogFormat:       contour_api_v1alpha1.JSONAccessLog,
   712  					AccessLogFormatString: "foo-bar-baz",
   713  					AccessLogLevel:        contour_api_v1alpha1.LogLevelInfo,
   714  					AccessLogJSONFields: contour_api_v1alpha1.AccessLogJSONFields([]string{
   715  						"custom_field",
   716  					}),
   717  				}
   718  				return cfg
   719  			},
   720  		},
   721  		"access log -- error": {
   722  			getServeContext: func(ctx *serveContext) *serveContext {
   723  				ctx.Config.AccessLogFormat = config.JSONAccessLog
   724  				ctx.Config.AccessLogFormatString = "foo-bar-baz"
   725  				ctx.Config.AccessLogFields = []string{"custom_field"}
   726  				ctx.Config.AccessLogLevel = config.LogLevelError
   727  				return ctx
   728  			},
   729  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   730  				cfg.Envoy.Logging = &contour_api_v1alpha1.EnvoyLogging{
   731  					AccessLogFormat:       contour_api_v1alpha1.JSONAccessLog,
   732  					AccessLogFormatString: "foo-bar-baz",
   733  					AccessLogLevel:        contour_api_v1alpha1.LogLevelError,
   734  					AccessLogJSONFields: contour_api_v1alpha1.AccessLogJSONFields([]string{
   735  						"custom_field",
   736  					}),
   737  				}
   738  				return cfg
   739  			},
   740  		},
   741  		"access log -- critical": {
   742  			getServeContext: func(ctx *serveContext) *serveContext {
   743  				ctx.Config.AccessLogFormat = config.JSONAccessLog
   744  				ctx.Config.AccessLogFormatString = "foo-bar-baz"
   745  				ctx.Config.AccessLogFields = []string{"custom_field"}
   746  				ctx.Config.AccessLogLevel = config.LogLevelCritical
   747  				return ctx
   748  			},
   749  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   750  				cfg.Envoy.Logging = &contour_api_v1alpha1.EnvoyLogging{
   751  					AccessLogFormat:       contour_api_v1alpha1.JSONAccessLog,
   752  					AccessLogFormatString: "foo-bar-baz",
   753  					AccessLogLevel:        contour_api_v1alpha1.LogLevelCritical,
   754  					AccessLogJSONFields: contour_api_v1alpha1.AccessLogJSONFields([]string{
   755  						"custom_field",
   756  					}),
   757  				}
   758  				return cfg
   759  			},
   760  		},
   761  		"disable merge slashes": {
   762  			getServeContext: func(ctx *serveContext) *serveContext {
   763  				ctx.Config.DisableMergeSlashes = true
   764  				return ctx
   765  			},
   766  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   767  				cfg.Envoy.Listener.DisableMergeSlashes = ref.To(true)
   768  				return cfg
   769  			},
   770  		},
   771  		"server header transformation": {
   772  			getServeContext: func(ctx *serveContext) *serveContext {
   773  				ctx.Config.ServerHeaderTransformation = config.AppendIfAbsentServerHeader
   774  				return ctx
   775  			},
   776  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   777  				cfg.Envoy.Listener.ServerHeaderTransformation = contour_api_v1alpha1.AppendIfAbsentServerHeader
   778  				return cfg
   779  			},
   780  		},
   781  		"global circuit breaker defaults": {
   782  			getServeContext: func(ctx *serveContext) *serveContext {
   783  				ctx.Config.Cluster.GlobalCircuitBreakerDefaults = &contour_api_v1alpha1.GlobalCircuitBreakerDefaults{
   784  					MaxConnections:     4,
   785  					MaxPendingRequests: 5,
   786  					MaxRequests:        6,
   787  					MaxRetries:         7,
   788  				}
   789  				return ctx
   790  			},
   791  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   792  				cfg.Envoy.Cluster.GlobalCircuitBreakerDefaults = &contour_api_v1alpha1.GlobalCircuitBreakerDefaults{
   793  					MaxConnections:     4,
   794  					MaxPendingRequests: 5,
   795  					MaxRequests:        6,
   796  					MaxRetries:         7,
   797  				}
   798  				return cfg
   799  			},
   800  		},
   801  		"global external authorization": {
   802  			getServeContext: func(ctx *serveContext) *serveContext {
   803  				ctx.Config.GlobalExternalAuthorization = config.GlobalExternalAuthorization{
   804  					ExtensionService: "extauthns/extauthtext",
   805  					FailOpen:         true,
   806  					AuthPolicy: &config.GlobalAuthorizationPolicy{
   807  						Context: map[string]string{
   808  							"foo": "bar",
   809  						},
   810  					},
   811  					WithRequestBody: &config.GlobalAuthorizationServerBufferSettings{
   812  						MaxRequestBytes:     512,
   813  						PackAsBytes:         true,
   814  						AllowPartialMessage: true,
   815  					},
   816  				}
   817  				return ctx
   818  			},
   819  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   820  				cfg.GlobalExternalAuthorization = &contour_api_v1.AuthorizationServer{
   821  					ExtensionServiceRef: contour_api_v1.ExtensionServiceReference{
   822  						Name:      "extauthtext",
   823  						Namespace: "extauthns",
   824  					},
   825  					FailOpen: true,
   826  					AuthPolicy: &contour_api_v1.AuthorizationPolicy{
   827  						Context: map[string]string{
   828  							"foo": "bar",
   829  						},
   830  						Disabled: false,
   831  					},
   832  					WithRequestBody: &contour_api_v1.AuthorizationServerBufferSettings{
   833  						MaxRequestBytes:     512,
   834  						PackAsBytes:         true,
   835  						AllowPartialMessage: true,
   836  					},
   837  				}
   838  				return cfg
   839  			},
   840  		},
   841  		"tracing config normal": {
   842  			getServeContext: func(ctx *serveContext) *serveContext {
   843  				ctx.Config.Tracing = &config.Tracing{
   844  					IncludePodDetail: ref.To(false),
   845  					ServiceName:      ref.To("contour"),
   846  					OverallSampling:  ref.To("100"),
   847  					MaxPathTagLength: ref.To(uint32(256)),
   848  					CustomTags: []config.CustomTag{
   849  						{
   850  							TagName: "literal",
   851  							Literal: "this is literal",
   852  						},
   853  						{
   854  							TagName:           "header",
   855  							RequestHeaderName: ":method",
   856  						},
   857  					},
   858  					ExtensionService: "otel/otel-collector",
   859  				}
   860  				return ctx
   861  			},
   862  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   863  				cfg.Tracing = &contour_api_v1alpha1.TracingConfig{
   864  					IncludePodDetail: ref.To(false),
   865  					ServiceName:      ref.To("contour"),
   866  					OverallSampling:  ref.To("100"),
   867  					MaxPathTagLength: ref.To(uint32(256)),
   868  					CustomTags: []*contour_api_v1alpha1.CustomTag{
   869  						{
   870  							TagName: "literal",
   871  							Literal: "this is literal",
   872  						},
   873  						{
   874  							TagName:           "header",
   875  							RequestHeaderName: ":method",
   876  						},
   877  					},
   878  					ExtensionService: &contour_api_v1alpha1.NamespacedName{
   879  						Name:      "otel-collector",
   880  						Namespace: "otel",
   881  					},
   882  				}
   883  				return cfg
   884  			},
   885  		},
   886  		"tracing config only extensionService": {
   887  			getServeContext: func(ctx *serveContext) *serveContext {
   888  				ctx.Config.Tracing = &config.Tracing{
   889  					ExtensionService: "otel/otel-collector",
   890  				}
   891  				return ctx
   892  			},
   893  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   894  				cfg.Tracing = &contour_api_v1alpha1.TracingConfig{
   895  					ExtensionService: &contour_api_v1alpha1.NamespacedName{
   896  						Name:      "otel-collector",
   897  						Namespace: "otel",
   898  					},
   899  				}
   900  				return cfg
   901  			},
   902  		},
   903  		"envoy listener settings": {
   904  			getServeContext: func(ctx *serveContext) *serveContext {
   905  				ctx.Config.Listener.MaxRequestsPerIOCycle = ref.To(uint32(10))
   906  				ctx.Config.Listener.HTTP2MaxConcurrentStreams = ref.To(uint32(30))
   907  				ctx.Config.Listener.MaxConnectionsPerListener = ref.To(uint32(50))
   908  				return ctx
   909  			},
   910  			getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec {
   911  				cfg.Envoy.Listener.MaxRequestsPerIOCycle = ref.To(uint32(10))
   912  				cfg.Envoy.Listener.HTTP2MaxConcurrentStreams = ref.To(uint32(30))
   913  				cfg.Envoy.Listener.MaxConnectionsPerListener = ref.To(uint32(50))
   914  				return cfg
   915  			},
   916  		},
   917  	}
   918  
   919  	for name, tc := range cases {
   920  		t.Run(name, func(t *testing.T) {
   921  			serveContext := tc.getServeContext(defaultContext())
   922  			want := tc.getContourConfiguration(defaultContourConfiguration())
   923  
   924  			assert.Equal(t, want, serveContext.convertToContourConfigurationSpec())
   925  		})
   926  	}
   927  }