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 }