Skip to content

Commit

Permalink
feat: add support for TLSRoute
Browse files Browse the repository at this point in the history
Signed-off-by: Matthew Penner <me@matthewp.io>
  • Loading branch information
matthewpi committed May 28, 2024
1 parent c0a52f6 commit 77c057d
Show file tree
Hide file tree
Showing 10 changed files with 571 additions and 62 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Support for missing resources is planned but not yet implemented.
- [x] [BackendTLSPolicy](https://gateway-api.sigs.k8s.io/api-types/backendtlspolicy/)
- [x] [HTTPRoute](https://gateway-api.sigs.k8s.io/api-types/httproute/)
- [ ] [GRPCRoute](https://gateway-api.sigs.k8s.io/api-types/grpcroute/)
- [ ] [TLSRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tlsroute)
- [x] [TLSRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tlsroute)
- [x] [TCPRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute)
- [x] [UDPRoute](https://gateway-api.sigs.k8s.io/concepts/api-overview/#tcproute-and-udproute)

Expand Down
16 changes: 7 additions & 9 deletions internal/caddy/caddy.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,13 @@ func (i *Input) handleListener(l gatewayv1.Listener) error {
case gatewayv1.HTTPProtocolType:
return i.handleHTTPListener(l)
case gatewayv1.HTTPSProtocolType:
// If TLS mode is not Terminate, then ignore the listener. We cannot do HTTP routing while
// doing TLS passthrough as we need to decrypt the request in order to route it.
if l.TLS != nil && l.TLS.Mode != nil && *l.TLS.Mode != gatewayv1.TLSModeTerminate {
return nil
}
return i.handleHTTPListener(l)
case gatewayv1.TLSProtocolType:
// If TLS mode is set to Terminate, treat it as an HTTP server.
if l.TLS == nil || l.TLS.Mode == nil || *l.TLS.Mode == gatewayv1.TLSModeTerminate {
return i.handleHTTPListener(l)
}
// Otherwise we need TLS passthrough, which is more complicated.
return i.handleLayer4Listener(l)
case gatewayv1.TCPProtocolType:
return i.handleLayer4Listener(l)
Expand Down Expand Up @@ -194,7 +194,7 @@ func (i *Input) handleLayer4Listener(l gatewayv1.Listener) error {
s, ok := i.layer4Servers[key]
if !ok {
s = &layer4.Server{
Listen: []string{proto + "/" + ":" + strconv.Itoa(int(l.Port))},
Listen: []string{proto + "/:" + strconv.Itoa(int(l.Port))},
}
}

Expand All @@ -204,9 +204,7 @@ func (i *Input) handleLayer4Listener(l gatewayv1.Listener) error {
)
switch l.Protocol {
case gatewayv1.TLSProtocolType:
// TODO: implement
// This TLS protocol is for passthrough, not terminate.
break
server, err = i.getTLSServer(s, l)
case gatewayv1.TCPProtocolType:
server, err = i.getTCPServer(s, l)
case gatewayv1.UDPProtocolType:
Expand Down
139 changes: 106 additions & 33 deletions internal/caddy/tls_passthrough.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,109 @@

package caddy

//// getTLSServer .
//// TODO: document
//func (i *Input) getTLSServer(l gatewayv1.Listener) (*layer4.Server, error) {
// // TODO: protocol may be either TLS or HTTPS, we should configure the host
// // matcher accordingly.
// var hostname string
// if l.Hostname != nil {
// hostname = string(*l.Hostname)
// }
//
// tls := map[string]any{"sni": []string{hostname}}
// tlsJson, err := json.Marshal(tls)
// if err != nil {
// return nil, err
// }
//
// return &layer4.Server{
// Listen: []string{":" + strconv.Itoa(int(l.Port))},
// Routes: layer4.RouteList{
// {
// MatcherSetsRaw: caddyhttp.RawMatcherSets{
// {
// // TODO: if no hostname was set can we just leave an empty matcher?
// "tls": tlsJson,
// },
// },
// HandlersRaw: []json.RawMessage{
// json.RawMessage(`{"handler":"proxy","upstreams":[{"dial":""}]}`),
// },
// },
// },
// }, nil
//}
import (
"net"
"strconv"

gateway "github.com/caddyserver/gateway/internal"
"github.com/caddyserver/gateway/internal/layer4"
"github.com/caddyserver/gateway/internal/layer4/l4proxy"
"github.com/caddyserver/gateway/internal/layer4/l4tls"
corev1 "k8s.io/api/core/v1"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
)

// getTLSServer .
// TODO: document
func (i *Input) getTLSServer(s *layer4.Server, l gatewayv1.Listener) (*layer4.Server, error) {
routes := []*layer4.Route{}
for _, tr := range i.TLSRoutes {
if !isRouteForListener(i.Gateway, l, tr.Namespace, tr.Status.RouteStatus) {
continue
}

matchers := []layer4.Match{}
// Match hostnames if any are specified.
if len(tr.Spec.Hostnames) > 0 {
// TODO: validate hostnames against listener hostnames, including
// a prefix match for wildcards.
//
// See godoc for HTTPRoute.Spec.Hostnames for more details.
matcher := layer4.Match{
TLS: &layer4.MatchTLS{
SNI: make(layer4.MatchSNI, len(tr.Spec.Hostnames)),
},
}
for i, h := range tr.Spec.Hostnames {
matcher.TLS.SNI[i] = string(h)
}
matchers = append(matchers, matcher)
}

var handlers []layer4.Handler
if l.TLS == nil || l.TLS.Mode == nil || *l.TLS.Mode == gatewayv1.TLSModeTerminate {
// Add a TLS handler to terminate TLS.
handlers = []layer4.Handler{&l4tls.Handler{}}
}

for _, rule := range tr.Spec.Rules {
// We only support a single backend ref as we don't support weights for layer4 proxy.
if len(rule.BackendRefs) != 1 {
continue
}

bf := rule.BackendRefs[0]
bor := bf.BackendObjectReference
if !gateway.IsService(bor) {
continue
}

// Safeguard against nil-pointer dereference.
if bor.Port == nil {
continue
}

// Get the service.
//
// TODO: is there a more efficient way to do this?
// We currently list all services and forward them to the input,
// then iterate over them.
//
// Should we just use the Kubernetes client instead?
var service corev1.Service
for _, s := range i.Services {
if s.Namespace != gateway.NamespaceDerefOr(bor.Namespace, tr.Namespace) {
continue
}
if s.Name != string(bor.Name) {
continue
}
service = s
break
}
if service.Name == "" {
// Invalid service reference.
continue
}

// Add a handler that proxies to the backend service.
handlers = append(handlers, &l4proxy.Handler{
Upstreams: l4proxy.UpstreamPool{
&l4proxy.Upstream{
Dial: []string{net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(*bor.Port)))},
},
},
})
}

// Add the route.
routes = append(routes, &layer4.Route{
MatcherSets: matchers,
Handlers: handlers,
})
}

// Update the routes on the server.
s.Routes = append(s.Routes, routes...)
return s, nil
}
Loading

0 comments on commit 77c057d

Please sign in to comment.