Skip to content

Commit

Permalink
handle tls route gateway.listener modes (projectcontour#3748)
Browse files Browse the repository at this point in the history
Fixup issue where the listener protocol wasn't checked for type=tls.
Also, adds logic to validate the TLS.Mode is valid.

Signed-off-by: Steve Sloka <slokas@vmware.com>
  • Loading branch information
stevesloka authored Jun 8, 2021
1 parent 1ddb0fb commit de4c7be
Show file tree
Hide file tree
Showing 4 changed files with 354 additions and 17 deletions.
274 changes: 272 additions & 2 deletions internal/dag/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,51 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.HTTPProtocolType,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
}

gatewayTLSRouteModePassthrough := &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.TLSModePassthrough),
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
}

gatewayTLSRouteModeTerminate := &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{
Expand Down Expand Up @@ -252,7 +296,7 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.HTTPProtocolType,
Protocol: gatewayapi_v1alpha1.TLSProtocolType,
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
Expand Down Expand Up @@ -926,6 +970,228 @@ func TestDAGInsertGatewayAPI(t *testing.T) {
},
want: listeners(),
},
"TLSRoute with TLS.Mode=Passthrough is valid": {
gateway: gatewayTLSRouteModePassthrough,
objs: []interface{}{
&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "custom",
Labels: map[string]string{
"app": "contour",
"type": "controller",
},
},
},
kuardServiceCustomNs,
&gatewayapi_v1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "custom",
},
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(kuardServiceCustomNs)),
},
},
),
},
),
},
"TLSRoute with TLS.Mode=Passthrough is invalid if certificateRef is specified": {
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.TLSModePassthrough),
CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{
Group: "core",
Kind: "Secret",
Name: sec1.Name,
},
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
},
objs: []interface{}{
kuardService,
&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: gatewayTLSRouteModeTerminate,
objs: []interface{}{
&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "custom",
Labels: map[string]string{
"app": "contour",
"type": "controller",
},
},
},
kuardServiceCustomNs,
&gatewayapi_v1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "custom",
},
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 invalid listener protocol of HTTP": {
gateway: &gatewayapi_v1alpha1.Gateway{
ObjectMeta: metav1.ObjectMeta{
Name: "contour",
Namespace: "projectcontour",
},
Spec: gatewayapi_v1alpha1.GatewaySpec{
Listeners: []gatewayapi_v1alpha1.Listener{{
Port: 80,
Protocol: gatewayapi_v1alpha1.HTTPProtocolType,
TLS: &gatewayapi_v1alpha1.GatewayTLSConfig{
Mode: tlsModeTypePtr(gatewayapi_v1alpha1.TLSModePassthrough),
CertificateRef: &gatewayapi_v1alpha1.LocalObjectReference{
Group: "core",
Kind: "Secret",
Name: sec1.Name,
},
},
Routes: gatewayapi_v1alpha1.RouteBindingSelector{
Kind: KindTLSRoute,
Namespaces: &gatewayapi_v1alpha1.RouteNamespaces{
From: routeSelectTypePtr(gatewayapi_v1alpha1.RouteSelectAll),
},
},
}},
},
},
objs: []interface{}{
kuardService,
&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 invalid listener kind": {
gateway: gatewayNoSelector,
objs: []interface{}{
&v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "custom",
Labels: map[string]string{
"app": "contour",
"type": "controller",
},
},
},
kuardServiceCustomNs,
&gatewayapi_v1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Name: "basic",
Namespace: "custom",
},
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 is in a different namespace than the Gateway,
// but will be allowed since "All" is set.
"TLSRoute: RouteGateways with GatewayAllowType: All": {
Expand Down Expand Up @@ -10187,3 +10453,7 @@ func withMirror(r *Route, mirror *Service) *Route {
func routeSelectTypePtr(rst gatewayapi_v1alpha1.RouteSelectType) *gatewayapi_v1alpha1.RouteSelectType {
return &rst
}

func tlsModeTypePtr(mode gatewayapi_v1alpha1.TLSModeType) *gatewayapi_v1alpha1.TLSModeType {
return &mode
}
37 changes: 29 additions & 8 deletions internal/dag/gatewayapi_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
var matchingTLSRoutes []*gatewayapi_v1alpha1.TLSRoute
var listenerSecret *Secret

// Validate the Kind on the selector is a supported type.
// Validate the Protocol on the selector is a supported type.
switch listener.Protocol {
case gatewayapi_v1alpha1.HTTPSProtocolType:
// Validate that if protocol is type HTTPS, that TLS is defined.
Expand All @@ -93,7 +93,27 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
// routes to be bound to this listener since it can't serve TLS traffic.
continue
}
case gatewayapi_v1alpha1.HTTPProtocolType, gatewayapi_v1alpha1.TLSProtocolType:
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
}
}
}
}
case gatewayapi_v1alpha1.HTTPProtocolType:
break
default:
p.Errorf("Listener.Protocol %q is not supported.", listener.Protocol)
Expand Down Expand Up @@ -157,6 +177,13 @@ func (p *GatewayAPIProcessor) Run(dag *DAG, source *KubernetesCache) {
}
}
case KindTLSRoute:

// Validate the listener protocol is type=TLS.
if listener.Protocol != gatewayapi_v1alpha1.TLSProtocolType {
p.Errorf("invalid listener protocol %q for Kind: TLSRoute", listener.Protocol)
continue
}

for _, route := range p.source.tlsroutes {
// Filter the TLSRoutes that match the gateway which Contour is configured to watch.
// RouteBindingSelector defines a schema for associating routes with the Gateway.
Expand Down Expand Up @@ -216,12 +243,6 @@ func (p *GatewayAPIProcessor) validGatewayTLS(listener gatewayapi_v1alpha1.Liste
return nil
}

// Validate the correct protocol is specified.
if listener.Protocol != gatewayapi_v1alpha1.HTTPSProtocolType {
p.Errorf("Spec.VirtualHost.Protocol %q is not valid.", listener.Protocol)
return nil
}

// Validate a v1.Secret is referenced which can be kind: secret & group: core.
// ref: https://github.com/kubernetes-sigs/gateway-api/pull/562
if !isSecretRef(listener.TLS.CertificateRef) {
Expand Down
5 changes: 2 additions & 3 deletions test/e2e/gateway/008_tlsroute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ import (
gatewayv1alpha1 "sigs.k8s.io/gateway-api/apis/v1alpha1"
)

func testTLSRoute(fx *e2e.Framework) {
func testTLSRoutePassthrough(fx *e2e.Framework, namespace string) {
t := fx.T()
namespace := "gateway-008-tlsroute"

fx.CreateNamespace(namespace)
defer fx.DeleteNamespace(namespace)

fx.Fixtures.EchoSecure.Deploy(namespace, "echo")
fx.Certs.CreateSelfSignedCert(namespace, "backend-server-cert", "backend-server-cert", "tlsroute.gatewayapi.projectcontour.io")

// TLSRoute that doesn't define the termination type.
route := &gatewayv1alpha1.TLSRoute{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Expand Down Expand Up @@ -99,5 +99,4 @@ func testTLSRoute(fx *e2e.Framework) {
})
assert.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode)
assert.Equal(t, "echo", fx.GetEchoResponseBody(res.Body).Service)

}
Loading

0 comments on commit de4c7be

Please sign in to comment.