Skip to content

Commit

Permalink
Gateway API: support TLS termination with TLSRoute/TCPRoute (#5481)
Browse files Browse the repository at this point in the history
Adds support for TLS termination with the TLS
listener protocol. Envoy is configured to terminate
TLS and then to proxy TCP traffic to the backend.

Closes #5461.

Signed-off-by: Steve Kriss <krisss@vmware.com>
  • Loading branch information
skriss authored Jul 21, 2023
1 parent cca0691 commit be3ba06
Show file tree
Hide file tree
Showing 7 changed files with 498 additions and 110 deletions.
22 changes: 22 additions & 0 deletions changelogs/unreleased/5481-skriss-minor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Gateway API: Support TLS termination with TLSRoute and TCPRoute

Contour now supports using TLSRoute and TCPRoute in combination with TLS termination.
To use this feature, create a Gateway with a Listener like the following:

```yaml
- name: tls-listener
protocol: TLS
port: 5000
tls:
mode: Terminate
certificateRefs:
- name: tls-cert-secret
allowedRoutes:
namespaces:
from: All
---
```

It is then possible to attach either 1+ TLSRoutes, or a single TCPRoute, to this Listener.
If using TLSRoute, traffic can be routed to a different backend based on SNI.
If using TCPRoute, all traffic is forwarded to the backend referenced in the route.
5 changes: 1 addition & 4 deletions internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
want: listeners(),
},
"TLS Listener with TLS.Mode=Terminate is invalid": {
"TLS Listener with TLS.Mode=Terminate is invalid if certificateRef is not specified": {
gatewayclass: validClass,
gateway: &gatewayapi_v1beta1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Expand All @@ -935,9 +935,6 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
Protocol: gatewayapi_v1beta1.TLSProtocolType,
TLS: &gatewayapi_v1beta1.GatewayTLSConfig{
Mode: ref.To(gatewayapi_v1beta1.TLSModeTerminate),
CertificateRefs: []gatewayapi_v1beta1.SecretObjectReference{
gatewayapi.CertificateRef(sec1.Name, sec1.Namespace),
},
},
AllowedRoutes: &gatewayapi_v1beta1.AllowedRoutes{
Namespaces: &gatewayapi_v1beta1.RouteNamespaces{
Expand Down
42 changes: 25 additions & 17 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -544,25 +544,27 @@ func (p *GatewayAPIProcessor) computeListener(
case gatewayapi_v1beta1.TLSProtocolType:
// The TLS protocol is used for TCP traffic encrypted with TLS.
// Gateway API allows TLS to be either terminated at the proxy
// or passed through to the backend, but the former requires using
// TCPRoute to route traffic since the underlying protocol is TCP
// not HTTP, which Contour doesn't support. Therefore, we only
// support "Passthrough" with the TLS protocol, which requires
// the use of TLSRoute to route to backends since the traffic is
// still encrypted.

// or passed through to the backend.
if listener.TLS == nil {
addInvalidListenerCondition(fmt.Sprintf("Listener.TLS is required when protocol is %q.", listener.Protocol))
return false, nil
}

if listener.TLS.Mode == nil || *listener.TLS.Mode != gatewayapi_v1beta1.TLSModePassthrough {
addInvalidListenerCondition(fmt.Sprintf("Listener.TLS.Mode must be %q when protocol is %q.", gatewayapi_v1beta1.TLSModePassthrough, listener.Protocol))
return false, nil
}

if len(listener.TLS.CertificateRefs) != 0 {
addInvalidListenerCondition(fmt.Sprintf("Listener.TLS.CertificateRefs cannot be defined when Listener.TLS.Mode is %q.", gatewayapi_v1beta1.TLSModePassthrough))
switch {
case listener.TLS.Mode == nil || *listener.TLS.Mode == gatewayapi_v1beta1.TLSModeTerminate:
// Resolve the TLS secret.
if listenerSecret = p.resolveListenerSecret(listener.TLS.CertificateRefs, string(listener.Name), gwAccessor); listenerSecret == nil {
// If TLS was configured on the Listener, but the secret ref is invalid, don't allow any
// routes to be bound to this listener since it can't serve TLS traffic.
return false, nil
}
case *listener.TLS.Mode == gatewayapi_v1beta1.TLSModePassthrough:
if len(listener.TLS.CertificateRefs) != 0 {
addInvalidListenerCondition(fmt.Sprintf("Listener.TLS.CertificateRefs cannot be defined when Listener.TLS.Mode is %q.", gatewayapi_v1beta1.TLSModePassthrough))
return false, nil
}
default:
addInvalidListenerCondition(fmt.Sprintf("Listener.TLS.Mode must be %q or %q.", gatewayapi_v1beta1.TLSModeTerminate, gatewayapi_v1beta1.TLSModePassthrough))
return false, nil
}
}
Expand All @@ -588,7 +590,7 @@ func (p *GatewayAPIProcessor) getListenerRouteKinds(listener gatewayapi_v1beta1.
case gatewayapi_v1beta1.HTTPSProtocolType:
return []gatewayapi_v1beta1.Kind{KindHTTPRoute, KindGRPCRoute}
case gatewayapi_v1beta1.TLSProtocolType:
return []gatewayapi_v1beta1.Kind{KindTLSRoute}
return []gatewayapi_v1beta1.Kind{KindTLSRoute, KindTCPRoute}
case gatewayapi_v1beta1.TCPProtocolType:
return []gatewayapi_v1beta1.Kind{KindTCPRoute}
}
Expand Down Expand Up @@ -627,7 +629,7 @@ func (p *GatewayAPIProcessor) getListenerRouteKinds(listener gatewayapi_v1beta1.
)
continue
}
if routeKind.Kind == KindTCPRoute && listener.Protocol != gatewayapi_v1beta1.TCPProtocolType {
if routeKind.Kind == KindTCPRoute && listener.Protocol != gatewayapi_v1beta1.TCPProtocolType && listener.Protocol != gatewayapi_v1beta1.TLSProtocolType {
gwAccessor.AddListenerCondition(
string(listener.Name),
gatewayapi_v1beta1.ListenerConditionResolvedRefs,
Expand Down Expand Up @@ -1654,7 +1656,13 @@ func (p *GatewayAPIProcessor) computeTCPRouteForListener(route *gatewayapi_v1alp
return false
}

p.dag.Listeners[listener.dagListenerName].TCPProxy = &proxy
if listener.tlsSecret != nil {
secure := p.dag.EnsureSecureVirtualHost(listener.dagListenerName, "*")
secure.Secret = listener.tlsSecret
secure.TCPProxy = &proxy
} else {
p.dag.Listeners[listener.dagListenerName].TCPProxy = &proxy
}

return true
}
Expand Down
Loading

0 comments on commit be3ba06

Please sign in to comment.