Skip to content

Commit

Permalink
ingressroute: add a tls.passthrough key to indicate TLS passthrough
Browse files Browse the repository at this point in the history
Updates projectcontour#850

TCP proxying requires one of a `tls.secretName` or `tls.passthrough` to
be present. The `tls.passthrough` option has no effect if
`spec.tcpproxy` is empty.

Signed-off-by: Dave Cheney <dave@cheney.net>
  • Loading branch information
davecheney committed Jan 25, 2019
1 parent 18f9c17 commit e8ef81e
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 19 deletions.
6 changes: 5 additions & 1 deletion apis/contour/v1beta1/ingressroute.go
Expand Up @@ -42,12 +42,16 @@ type VirtualHost struct {

// TLS describes tls properties. The CNI names that will be matched on
// are described in fqdn, the tls.secretName secret must contain a
// matching certificate
// matching certificate unless tls.passthrough is set to true.
type TLS struct {
// required, the name of a secret in the current namespace
SecretName string `json:"secretName"`
// Minimum TLS version this vhost should negotiate
MinimumProtocolVersion string `json:"minimumProtocolVersion,omitempty"`
// If Passthrough is set to true, the SecretName will be ignored
// and the encrypted handshake will be passed through to the
// backing cluster.
Passthrough bool `json:"passthrough,omitempty"`
}

// Route contains the set of routes for a virtual host
Expand Down
40 changes: 35 additions & 5 deletions docs/ingressroute.md
Expand Up @@ -746,9 +746,13 @@ IngressRoutes with a defined `virtualhost` field that are not in one of the allo
Ingressroute supports proxying of TLS encapsulated TCP sessions.
The TCP session must be encrypted with TLS.
_Note_: The TCP session must be encrypted with TLS.
This is necessary so that Envoy can use SNI to route the incoming request to the correct service.
### TLS Termination at the edge
If `spec.virtualhost.tls.secretName` is present then that secret will be used to decrypt the TCP traffic at the edge.
```
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
Expand All @@ -774,17 +778,43 @@ spec:
port: 80
```
The `spec.tcpproxy` key indicates that this _root_ IngressRoute will forward all de-encrypted TCP traffic to the backend service.
The `spec.tcpproxy` key indicates that this _root_ IngressRoute will forward the de-encrypted TCP traffic to the backend service.
### TLS passthrough to the backend service
If you wish to handle the TLS handshake at the backend service set `spec.virtualhost.tls.passthrough: true` indicates that once SNI demuxing is performed, the encrypted connection will be forwarded to the backend service. The backend service is expected to have a key which matches the SNI header received at the edge, and be capable of completing the TLS handshake. This is called SSL/TLS Passthrough.
In case `spec.virtualhost.tls` is not present, TLS is not going to be terminated
on Envoy and will be forwarded to the specified services, where the termination
would happen. This is called SSL/TLS Passthrough.
```
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
name: example
namespace: default
spec:
virtualhost:
fqdn: tcp.example.com
tls:
passthrough: true
tcpproxy:
services:
- name: tcpservice
port: 8080
- name: otherservice
port: 9999
weight: 20
routes:
- match: /
services:
- name: kuard
port: 80
```
### Limitations
The current limitations are present in Contour 0.8. These will be addressed in later Contour versions.
- TCP Proxying is not available on Kubernetes Ingress objects.
- A dummy `spec.routes` entry is required for input validation.
## Status Reporting
Expand Down
17 changes: 10 additions & 7 deletions internal/dag/builder.go
Expand Up @@ -426,7 +426,7 @@ func (b *builder) compute() *DAG {
continue
}

enforceTLS := false
var enforceTLS, passthrough bool
if tls := ir.Spec.VirtualHost.TLS; tls != nil {
// attach secrets to TLS enabled vhosts
m := meta{name: tls.SecretName, namespace: ir.Namespace}
Expand All @@ -446,11 +446,15 @@ func (b *builder) compute() *DAG {
svhost.MinProtoVersion = auth.TlsParameters_TLSv1_1
}
}
// passthrough is true if tls.secretName is not present, and
// tls.passthrough is set to true.
passthrough = tls.SecretName == "" && tls.Passthrough
}

if ir.Spec.TCPProxy != nil {
switch {
case ir.Spec.TCPProxy != nil && (passthrough || enforceTLS):
b.processTCPProxy(ir, nil, host)
} else if ir.Spec.Routes != nil {
case ir.Spec.Routes != nil:
b.processRoutes(ir, "", nil, host, enforceTLS)
}
}
Expand Down Expand Up @@ -677,19 +681,18 @@ func (b *builder) processTCPProxy(ir *ingressroutev1.IngressRoute, visited []*in
return
}

// base case: The route points to services, so we add them to the vhost
if len(tcpproxy.Services) > 0 {
var ts []*TCPService
var proxy TCPProxy
for _, service := range tcpproxy.Services {
m := meta{name: service.Name, namespace: ir.Namespace}
s := b.lookupTCPService(m, intstr.FromInt(service.Port), service.Weight, service.Strategy, service.HealthCheck)
if s == nil {
b.setStatus(Status{Object: ir, Status: StatusInvalid, Description: fmt.Sprintf("tcpproxy: service %s/%s/%d: not found", ir.Namespace, service.Name, service.Port), Vhost: host})
return
}
ts = append(ts, s)
proxy.Services = append(proxy.Services, s)
}
b.lookupSecureVirtualHost(host, 443).VirtualHost.TCPProxy = &TCPProxy{Services: ts}
b.lookupSecureVirtualHost(host, 443).VirtualHost.TCPProxy = &proxy
b.setStatus(Status{Object: ir, Status: StatusValid, Description: "valid IngressRoute", Vhost: host})
return
}
Expand Down
9 changes: 8 additions & 1 deletion internal/dag/builder_test.go
Expand Up @@ -655,6 +655,9 @@ func TestDAGInsert(t *testing.T) {
Spec: ingressroutev1.IngressRouteSpec{
VirtualHost: &ingressroutev1.VirtualHost{
Fqdn: "kuard.example.com",
TLS: &ingressroutev1.TLS{
Passthrough: true,
},
},
TCPProxy: &ingressroutev1.TCPProxy{
Services: []ingressroutev1.Service{{
Expand All @@ -675,6 +678,9 @@ func TestDAGInsert(t *testing.T) {
Spec: ingressroutev1.IngressRouteSpec{
VirtualHost: &ingressroutev1.VirtualHost{
Fqdn: "kuard.example.com",
TLS: &ingressroutev1.TLS{
Passthrough: true,
},
},
TCPProxy: &ingressroutev1.TCPProxy{
Delegate: &ingressroutev1.Delegate{
Expand Down Expand Up @@ -1799,7 +1805,7 @@ func TestDAGInsert(t *testing.T) {
},
},
},
"insert ingressroute with tcp forward without TLS termination": {
"insert ingressroute with tcp forward without TLS termination w/ passthrough": {
objs: []interface{}{
ir1b, s1,
},
Expand All @@ -1817,6 +1823,7 @@ func TestDAGInsert(t *testing.T) {
},
},
},

"insert root ingress route and delegate ingress route for a tcp proxy": {
objs: []interface{}{
ir1d, s6, ir1c,
Expand Down
4 changes: 3 additions & 1 deletion internal/dag/dag.go
Expand Up @@ -140,7 +140,9 @@ type SecureVirtualHost struct {

func (s *SecureVirtualHost) Visit(f func(Vertex)) {
s.VirtualHost.Visit(f)
f(s.Secret)
if s.Secret != nil {
f(s.Secret) // secret is not required if vhost is using tls passthrough
}
}

type Visitable interface {
Expand Down
10 changes: 6 additions & 4 deletions internal/e2e/lds_test.go
Expand Up @@ -967,10 +967,9 @@ func TestIngressRouteHTTPS(t *testing.T) {
}, streamLDS(t, cc))
}

// Assert that when a spec.vhost.tls spec is present _without_
// a cert section but _does_ have a tcpproxy session we configure
// envoy to forward the TLS session to the cluster after using SNI
// to determine the target.
// Assert that when a spec.vhost.tls spec is present with tls.passthrough
// set to true we configure envoy to forward the TLS session to the cluster
// after using SNI to determine the target.
func TestLDSIngressRouteTCPProxyTLSPassthrough(t *testing.T) {
rh, cc, done := setup(t)
defer done()
Expand All @@ -983,6 +982,9 @@ func TestLDSIngressRouteTCPProxyTLSPassthrough(t *testing.T) {
Spec: ingressroutev1.IngressRouteSpec{
VirtualHost: &ingressroutev1.VirtualHost{
Fqdn: "kuard-tcp.example.com",
TLS: &ingressroutev1.TLS{
Passthrough: true,
},
},
Routes: []ingressroutev1.Route{{
Match: "/",
Expand Down

0 comments on commit e8ef81e

Please sign in to comment.