Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting minimum TLS Version #125

Merged
merged 1 commit into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/v1alpha1/httpsedge_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ type HTTPSEdgeSpec struct {

// Routes is a list of routes served by this edge
Routes []HTTPSEdgeRouteSpec `json:"routes,omitempty"`

// TLSTermination is the TLS termination configuration for this edge
TLSTermination *EndpointTLSTerminationAtEdge `json:"tlsTermination,omitempty"`
}

type HTTPSEdgeRouteStatus struct {
Expand Down
5 changes: 5 additions & 0 deletions api/v1alpha1/ngrok_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,8 @@ type EndpointHeaders struct {
// Response headers are the response headers module configuration or null
Response *EndpointResponseHeaders `json:"response,omitempty"`
}

type EndpointTLSTerminationAtEdge struct {
// MinVersion is the minimum TLS version to allow for connections to the edge
MinVersion string `json:"minVersion,omitempty"`
}
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions docs/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ based on the `spec.rules` defined in the Ingress resource. It will then configur
- [Request Headers](#request-headers)
- [Response Headers](#response-headers)
- [IP Restriction](#ip-restriction)
- [TLS Termination](#tls-termination)
- [Setting Minimum TLS Version](#setting-minimum-tls-version)


## Compression
Expand Down Expand Up @@ -89,3 +91,21 @@ metadata:
spec:
...
```

## TLS Termination

### Setting Minimum TLS Version

The `k8s.ngrok.com/tls-min-version` annotation can be used to set the minimum TLS version for all routes belonging to an Edge. *Note*: **This setting applies to all routes
belonging to an Edge, not just the routes defined in the Ingress resource**. The annotation accepts a string value. If not specified(default), the `TLS Termination` module will be disabled in ngrok and TLS will be terminated at the edge using the ngrok default minimum TLS version.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: minimal-ingress
annotations:
k8s.ngrok.com/tls-min-version: "1.3"
spec:
...
```

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 9 additions & 6 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/headers"
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/ip_policies"
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser"
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/tls"
"github.com/ngrok/kubernetes-ingress-controller/internal/errors"
networking "k8s.io/api/networking/v1"
"k8s.io/klog/v2"
Expand All @@ -32,9 +33,10 @@ import (
const DeniedKeyName = "Denied"

type RouteModules struct {
Compression *ingressv1alpha1.EndpointCompression
Headers *ingressv1alpha1.EndpointHeaders
IPRestriction *ingressv1alpha1.EndpointIPPolicy
Compression *ingressv1alpha1.EndpointCompression
Headers *ingressv1alpha1.EndpointHeaders
IPRestriction *ingressv1alpha1.EndpointIPPolicy
TLSTermination *ingressv1alpha1.EndpointTLSTerminationAtEdge
}

type Extractor struct {
Expand All @@ -44,9 +46,10 @@ type Extractor struct {
func NewAnnotationsExtractor() Extractor {
return Extractor{
annotations: map[string]parser.IngressAnnotation{
"Compression": compression.NewParser(),
"Headers": headers.NewParser(),
"IPRestriction": ip_policies.NewParser(),
"Compression": compression.NewParser(),
"Headers": headers.NewParser(),
"IPRestriction": ip_policies.NewParser(),
"TLSTermination": tls.NewParser(),
},
}
}
Expand Down
27 changes: 27 additions & 0 deletions internal/annotations/tls/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package tls

import (
ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1"
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser"
networking "k8s.io/api/networking/v1"
)

type EndpointTLSTerminationAtEdge = ingressv1alpha1.EndpointTLSTerminationAtEdge

type tls struct{}

func NewParser() parser.IngressAnnotation {
return tls{}
}

// Parse parses the annotations contained in the ingress and returns a
// tls configuration or an error. If no tls annotations are
// found, the returned error an errors.ErrMissingAnnotations.
func (t tls) Parse(ing *networking.Ingress) (interface{}, error) {
v, err := parser.GetStringAnnotation("tls-min-version", ing)
if err != nil {
return nil, err
}

return &EndpointTLSTerminationAtEdge{MinVersion: v}, nil
}
36 changes: 36 additions & 0 deletions internal/annotations/tls/tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package tls

import (
"testing"

"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser"
"github.com/ngrok/kubernetes-ingress-controller/internal/annotations/testutil"
"github.com/ngrok/kubernetes-ingress-controller/internal/errors"
"github.com/stretchr/testify/assert"
)

func TestTLSTerminationWhenNotSupplied(t *testing.T) {
ing := testutil.NewIngress()
ing.SetAnnotations(map[string]string{})
parsed, err := NewParser().Parse(ing)

assert.Nil(t, parsed)
assert.Error(t, err)
assert.True(t, errors.IsMissingAnnotations(err))
}

func TestTLSTerminationWhenSupplied(t *testing.T) {
ing := testutil.NewIngress()
annotations := map[string]string{}
annotations[parser.GetAnnotationWithPrefix("tls-min-version")] = "1.3"
ing.SetAnnotations(annotations)

parsed, err := NewParser().Parse(ing)
assert.NoError(t, err)

tlsTermination, ok := parsed.(*EndpointTLSTerminationAtEdge)
if !ok {
t.Fatalf("expected *EndpointTLSTerminationAtEdge, got %T", parsed)
}
assert.Equal(t, "1.3", tlsTermination.MinVersion)
}
21 changes: 20 additions & 1 deletion internal/controllers/httpsedge_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ func (r *HTTPSEdgeReconciler) reconcileEdge(ctx context.Context, edge *ingressv1
return err
}

return r.reconcileRoutes(ctx, edge, remoteEdge)
if err = r.reconcileRoutes(ctx, edge, remoteEdge); err != nil {
return err
}

return r.setEdgeTLSTermination(ctx, edge.Status.ID, edge.Spec.TLSTermination)
}

// TODO: This is going to be a bit messy right now, come back and make this cleaner
Expand Down Expand Up @@ -315,6 +319,21 @@ func (r *HTTPSEdgeReconciler) setEdgeRouteResponseHeaders(ctx context.Context, e
return err
}

func (r *HTTPSEdgeReconciler) setEdgeTLSTermination(ctx context.Context, edgeID string, tlsTermination *ingressv1alpha1.EndpointTLSTerminationAtEdge) error {
client := r.NgrokClientset.EdgeModules().HTTPS().TLSTermination()
if tlsTermination == nil {
return client.Delete(ctx, edgeID)
}

_, err := client.Replace(ctx, &ngrok.EdgeTLSTerminationAtEdgeReplace{
ID: edgeID,
Module: ngrok.EndpointTLSTerminationAtEdge{
MinVersion: pointer.String(tlsTermination.MinVersion),
},
})
return err
}

func (r *HTTPSEdgeReconciler) findEdgeByHostports(ctx context.Context, hostports []string) (*ngrok.HTTPSEdge, error) {
iter := r.NgrokClientset.HTTPSEdges().List(&ngrok.Paging{})
for iter.Next(ctx) {
Expand Down
13 changes: 7 additions & 6 deletions internal/controllers/ingress_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,13 @@ func (irec *IngressReconciler) reconcileAll(ctx context.Context, ingress *netv1.
}

// Converts a k8s Ingress Rule to and ngrok Route configuration.
func (irec *IngressReconciler) routesPlanner(ctx context.Context, ingress *netv1.Ingress) ([]ingressv1alpha1.HTTPSEdgeRouteSpec, error) {
func (irec *IngressReconciler) routesPlanner(ctx context.Context, ingress *netv1.Ingress, parsedRouteModules *annotations.RouteModules) ([]ingressv1alpha1.HTTPSEdgeRouteSpec, error) {
namespace := ingress.Namespace
rule := ingress.Spec.Rules[0]

var matchType string
var ngrokRoutes []ingressv1alpha1.HTTPSEdgeRouteSpec

parsedRouteModules := irec.AnnotationsExtractor.Extract(ingress)

for _, httpIngressPath := range rule.HTTP.Paths {
switch *httpIngressPath.PathType {
case netv1.PathTypePrefix:
Expand Down Expand Up @@ -196,7 +194,9 @@ func (irec *IngressReconciler) ingressToEdge(ctx context.Context, ingress *netv1
return nil, nil
}

ngrokRoutes, err := irec.routesPlanner(ctx, ingress)
parsedRouteModules := irec.AnnotationsExtractor.Extract(ingress)

ngrokRoutes, err := irec.routesPlanner(ctx, ingress, parsedRouteModules)
if err != nil {
return nil, err
}
Expand All @@ -207,8 +207,9 @@ func (irec *IngressReconciler) ingressToEdge(ctx context.Context, ingress *netv1
Name: ingress.Name,
},
Spec: ingressv1alpha1.HTTPSEdgeSpec{
Hostports: []string{ingress.Spec.Rules[0].Host + ":443"},
Routes: ngrokRoutes,
Hostports: []string{ingress.Spec.Rules[0].Host + ":443"},
Routes: ngrokRoutes,
TLSTermination: parsedRouteModules.TLSTermination,
},
}, nil
}
Expand Down
4 changes: 4 additions & 0 deletions internal/controllers/ingress_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func TestIngressReconcilerIngressToEdge(t *testing.T) {
Annotations: map[string]string{
"k8s.ngrok.com/https-compression": "true",
"k8s.ngrok.com/ip-policy-ids": "policy-1,policy-2",
"k8s.ngrok.com/tls-min-version": "1.3",
},
},
Spec: netv1.IngressSpec{
Expand Down Expand Up @@ -157,6 +158,9 @@ func TestIngressReconcilerIngressToEdge(t *testing.T) {
},
Spec: ingressv1alpha1.HTTPSEdgeSpec{
Hostports: []string{"my-test-tunnel.ngrok.io:443"},
TLSTermination: &ingressv1alpha1.EndpointTLSTerminationAtEdge{
MinVersion: "1.3",
},
Routes: []ingressv1alpha1.HTTPSEdgeRouteSpec{
{
Match: "/",
Expand Down