Skip to content

Commit

Permalink
internal/dag: Implement TLSRoute mode:terminate (#3801)
Browse files Browse the repository at this point in the history
Implements support for GatewayAPI TLSRoute mode: terminate which terminates TLS
at the Gateway.

Updates #3440

Signed-off-by: Steve Sloka <slokas@vmware.com>
  • Loading branch information
stevesloka authored Jun 28, 2021
1 parent 71c4cb8 commit 1d4526a
Show file tree
Hide file tree
Showing 7 changed files with 788 additions and 389 deletions.
197 changes: 163 additions & 34 deletions internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ import (
gatewayapi_v1alpha1 "sigs.k8s.io/gateway-api/apis/v1alpha1"
)

var sec1 = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Type: v1.SecretTypeTLS,
Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY),
}

// Invalid cert in the secret
var secInvalid = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Type: v1.SecretTypeTLS,
Data: secretdata("wrong", "wronger"),
}

func gatewayPort(port int) *gatewayapi_v1alpha1.PortNumber {
p := gatewayapi_v1alpha1.PortNumber(port)
return &p
Expand Down Expand Up @@ -143,6 +162,15 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
}

sec1 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tlscert",
Namespace: "projectcontour",
},
Type: v1.SecretTypeTLS,
Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY),
}

hostname := gatewayapi_v1alpha1.Hostname("gateway.projectcontour.io")
wildcardHostname := gatewayapi_v1alpha1.Hostname("*.projectcontour.io")

Expand Down Expand Up @@ -215,6 +243,9 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModePassthrough),
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
Expand Down Expand Up @@ -254,10 +285,15 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Port: 443,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModeTerminate),
CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{
Group: "core",
Kind: "Secret",
Name: sec1.Name,
},
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Expand Down Expand Up @@ -297,8 +333,12 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModePassthrough),
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,

Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectSame),
},
Expand All @@ -317,15 +357,6 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
}

sec1 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "tlscert",
Namespace: "projectcontour",
},
Type: v1.SecretTypeTLS,
Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY),
}

gatewayWithOnlyTLS := &gatewayapi_v1alpha1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Expand Down Expand Up @@ -1071,8 +1102,125 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
want: listeners(),
},
"TLSRoute with TLS.Mode=Terminate is invalid when TLS is not defined": {
"TLSRoute with TLS.Mode=Terminate": {
gateway: gatewayTLSRouteModeTerminate,
objs: []interface{}{
kuardService,
sec1,
&gatewayapi_v1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha1.TLSRouteSpec{
Gateways: &gatewayapi_v1alpha1.RouteGateways{
Allow: gatewayAllowTypePtr(gatewayapi_v1alpha1.GatewayAllowAll),
},
Rules: []gatewayapi_v1alpha1.TLSRouteRule{{
Matches: []gatewayapi_v1alpha1.TLSRouteMatch{{
SNIs: []gatewayapi_v1alpha1.Hostname{
"test.projectcontour.io",
},
}},
ForwardTo: tcpRouteForwardTo("kuard", 8080, 0),
}},
},
},
},
want: listeners(
&Listener{
Port: 443,
VirtualHosts: virtualhosts(
&SecureVirtualHost{
VirtualHost: VirtualHost{
Name: "test.projectcontour.io",
ListenerName: "ingress_https",
},
TCPProxy: &TCPProxy{
Clusters: clusters(
service(kuardService),
),
},
Secret: secret(sec1),
},
),
},
),
},
"TLSRoute with TLS.Mode=Terminate, invalid cert": {
gateway: &gatewayapi_v1alpha1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 443,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModeTerminate),
CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{
Group: "core",
Kind: "Secret",
Name: secInvalid.Name,
},
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
},
objs: []interface{}{
kuardService,
secInvalid,
&gatewayapi_v1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha1.TLSRouteSpec{
Gateways: &gatewayapi_v1alpha1.RouteGateways{
Allow: gatewayAllowTypePtr(gatewayapi_v1alpha1.GatewayAllowAll),
},
Rules: []gatewayapi_v1alpha1.TLSRouteRule{{
Matches: []gatewayapi_v1alpha1.TLSRouteMatch{{
SNIs: []gatewayapi_v1alpha1.Hostname{
"test.projectcontour.io",
},
}},
ForwardTo: tcpRouteForwardTo("kuard", 8080, 0),
}},
},
},
},
want: listeners(),
},
"TLSRoute with TLS.Mode=Terminate is invalid when TLS is not defined": {
gateway: &gatewayapi_v1alpha1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModeTerminate),
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
},
objs: []interface{}{
&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -3253,25 +3401,6 @@ func TestDAGInsert(t *testing.T) {
// The DAG is sensitive to ordering, adding an ingress, then a service,
// should have the same result as adding a service, then an ingress.

sec1 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Type: v1.SecretTypeTLS,
Data: secretdata(fixture.CERTIFICATE, fixture.RSA_PRIVATE_KEY),
}

// Invalid cert in the secret
sec2 := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "secret",
Namespace: "default",
},
Type: v1.SecretTypeTLS,
Data: secretdata("wrong", "wronger"),
}

// weird secret with a blank ca.crt that
// cert manager creates. #1644
sec3 := &v1.Secret{
Expand Down Expand Up @@ -6605,15 +6734,15 @@ func TestDAGInsert(t *testing.T) {
},
"ingressv1: insert invalid secret then ingress w/o tls": {
objs: []interface{}{
sec2,
secInvalid,
i1V1,
},
want: listeners(),
},
"ingressv1: insert service, invalid secret then ingress w/o tls": {
objs: []interface{}{
s1,
sec2,
secInvalid,
i1V1,
},
want: listeners(
Expand All @@ -6627,15 +6756,15 @@ func TestDAGInsert(t *testing.T) {
},
"ingressv1: insert invalid secret then ingress w/ tls": {
objs: []interface{}{
sec2,
secInvalid,
i3V1,
},
want: listeners(),
},
"ingressv1: insert service, invalid secret then ingress w/ tls": {
objs: []interface{}{
s1,
sec2,
secInvalid,
i3V1,
},
want: listeners(
Expand Down
45 changes: 27 additions & 18 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,21 +95,25 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
}
case gatewayapi_v1alpha1.TLSProtocolType:

if tlsConfig := listener.TLS; tlsConfig != nil {
if tlsConfig.Mode != nil {
switch *tlsConfig.Mode {
case gatewayapi_v1alpha1.TLSModeTerminate:
// Check for TLS on the Gateway.
if listenerSecret = p.validGatewayTLS(listener); listenerSecret == nil {
// If TLS was configured on the Listener, but it's invalid, don't allow any
// routes to be bound to this listener since it can't serve TLS traffic.
continue
}
case gatewayapi_v1alpha1.TLSModePassthrough:
if listener.TLS.CertificateRef != nil {
p.Errorf("Listener.TLS cannot be defined when TLS Mode is %q.", tlsConfig.Mode)
continue
}
// TLS is required for the type TLS.
if listener.TLS == nil {
p.Errorf("Listener.TLS is required when protocol is %q.", listener.Protocol)
continue
}

if listener.TLS.Mode != nil {
switch *listener.TLS.Mode {
case gatewayapi_v1alpha1.TLSModeTerminate:
// Check for TLS on the Gateway.
if listenerSecret = p.validGatewayTLS(listener); listenerSecret == nil {
// If TLS was configured on the Listener, but it's invalid, don't allow any
// routes to be bound to this listener since it can't serve TLS traffic.
continue
}
case gatewayapi_v1alpha1.TLSModePassthrough:
if listener.TLS.CertificateRef != nil {
p.Errorf("Listener.TLS.CertificateRef cannot be defined when TLS Mode is %q.", *listener.TLS.Mode)
continue
}
}
}
Expand Down Expand Up @@ -230,15 +234,15 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {

// Process all the routes that match this Gateway.
for _, matchingRoute := range matchingTLSRoutes {
p.computeTLSRoute(matchingRoute)
p.computeTLSRoute(matchingRoute, listenerSecret)
}
}
}

func (p *GatewayAPIProcessor) validGatewayTLS(listener gatewayapi_v1alpha1.Listener) *Secret {

// Validate the CertificateRef is configured.
if listener.TLS.CertificateRef == nil {
if listener.TLS == nil || listener.TLS.CertificateRef == nil {
p.Errorf("Spec.VirtualHost.TLS.CertificateRef is not configured.")
return nil
}
Expand Down Expand Up @@ -445,7 +449,7 @@ func selectorMatches(selector *metav1.LabelSelector, objLabels map[string]string
return true, nil
}

func (p *GatewayAPIProcessor) computeTLSRoute(route *gatewayapi_v1alpha1.TLSRoute) {
func (p *GatewayAPIProcessor) computeTLSRoute(route *gatewayapi_v1alpha1.TLSRoute, listenerSecret *Secret) {

routeAccessor, commit := p.dag.StatusCache.ConditionsAccessor(k8s.NamespacedNameOf(route), route.Generation, status.ResourceTLSRoute, route.Status.Gateways)
defer commit()
Expand Down Expand Up @@ -513,6 +517,11 @@ func (p *GatewayAPIProcessor) computeTLSRoute(route *gatewayapi_v1alpha1.TLSRout

for _, host := range hosts {
secure := p.dag.EnsureSecureVirtualHost(ListenerName{Name: host, ListenerName: "ingress_https"})

if listenerSecret != nil {
secure.Secret = listenerSecret
}

secure.TCPProxy = &proxy
}
}
Expand Down
Loading

0 comments on commit 1d4526a

Please sign in to comment.