From 3d9e93ce6a10753a63fe5536cbe35eeb49bbe104 Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 20:33:06 -0600 Subject: [PATCH 1/7] Remove enabled from IPPolicies and change how route modules are updated/deleted --- api/v1alpha1/ngrok_common.go | 3 +- api/v1alpha1/zz_generated.deepcopy.go | 12 +--- .../ingress.k8s.ngrok.com_httpsedges.yaml | 2 - .../crds/ingress.k8s.ngrok.com_tcpedges.yaml | 2 - internal/annotations/annotations_test.go | 6 +- .../annotations/compression/compression.go | 3 +- internal/annotations/ip_policies/ip_policy.go | 2 - internal/controllers/httpsedge_controller.go | 63 ++++++++++++------- .../controllers/ingress_controller_test.go | 10 ++- internal/controllers/tcpedge_controller.go | 34 ++++++---- 10 files changed, 74 insertions(+), 63 deletions(-) diff --git a/api/v1alpha1/ngrok_common.go b/api/v1alpha1/ngrok_common.go index 63b903bf..e6beab13 100644 --- a/api/v1alpha1/ngrok_common.go +++ b/api/v1alpha1/ngrok_common.go @@ -14,10 +14,9 @@ type ngrokAPICommon struct { type EndpointCompression struct { // Enabled is whether or not to enable compression for this endpoint - Enabled *bool `json:"enabled,omitempty"` + Enabled bool `json:"enabled,omitempty"` } type EndpointIPPolicy struct { - Enabled *bool `json:"enabled,omitempty"` IPPolicyIDs []string `json:"policyIDs,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index beba86c0..e2ba71a7 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -131,11 +131,6 @@ func (in *DomainStatus) DeepCopy() *DomainStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EndpointCompression) DeepCopyInto(out *EndpointCompression) { *out = *in - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) - **out = **in - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointCompression. @@ -151,11 +146,6 @@ func (in *EndpointCompression) DeepCopy() *EndpointCompression { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EndpointIPPolicy) DeepCopyInto(out *EndpointIPPolicy) { *out = *in - if in.Enabled != nil { - in, out := &in.Enabled, &out.Enabled - *out = new(bool) - **out = **in - } if in.IPPolicyIDs != nil { in, out := &in.IPPolicyIDs, &out.IPPolicyIDs *out = make([]string, len(*in)) @@ -240,7 +230,7 @@ func (in *HTTPSEdgeRouteSpec) DeepCopyInto(out *HTTPSEdgeRouteSpec) { if in.Compression != nil { in, out := &in.Compression, &out.Compression *out = new(EndpointCompression) - (*in).DeepCopyInto(*out) + **out = **in } if in.IPRestriction != nil { in, out := &in.IPRestriction, &out.IPRestriction diff --git a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml index 5dda8c24..9e2e5316 100644 --- a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml +++ b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml @@ -92,8 +92,6 @@ spec: description: IPRestriction is an IPRestriction to apply to this route properties: - enabled: - type: boolean policyIDs: items: type: string diff --git a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_tcpedges.yaml b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_tcpedges.yaml index f887679f..eef0568d 100644 --- a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_tcpedges.yaml +++ b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_tcpedges.yaml @@ -80,8 +80,6 @@ spec: ipRestriction: description: IPRestriction is an IPRestriction to apply to this route properties: - enabled: - type: boolean policyIDs: items: type: string diff --git a/internal/annotations/annotations_test.go b/internal/annotations/annotations_test.go index d0dbb4d8..1f5a0c66 100644 --- a/internal/annotations/annotations_test.go +++ b/internal/annotations/annotations_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" networking "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" ) func newIngressWithAnnotations(annotations map[string]string) *networking.Ingress { @@ -23,12 +22,12 @@ func TestCompression(t *testing.T) { modules := e.Extract(newIngressWithAnnotations(map[string]string{ "k8s.ngrok.com/https-compression": "false", })) - assert.False(t, *modules.Compression.Enabled) + assert.False(t, modules.Compression.Enabled) modules = e.Extract(newIngressWithAnnotations(map[string]string{ "k8s.ngrok.com/https-compression": "true", })) - assert.True(t, *modules.Compression.Enabled) + assert.True(t, modules.Compression.Enabled) modules = e.Extract(newIngressWithAnnotations(map[string]string{})) assert.Nil(t, modules.Compression) @@ -40,7 +39,6 @@ func TestIPPolicies(t *testing.T) { "k8s.ngrok.com/ip-policy-ids": "abc123,def456", })) assert.Equal(t, &ingressv1alpha1.EndpointIPPolicy{ - Enabled: pointer.Bool(true), IPPolicyIDs: []string{ "abc123", "def456", diff --git a/internal/annotations/compression/compression.go b/internal/annotations/compression/compression.go index aac4d882..5e1ca325 100644 --- a/internal/annotations/compression/compression.go +++ b/internal/annotations/compression/compression.go @@ -4,7 +4,6 @@ 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" - "k8s.io/utils/pointer" ) type compression struct{} @@ -20,6 +19,6 @@ func (c compression) Parse(ing *networking.Ingress) (interface{}, error) { } return &ingressv1alpha1.EndpointCompression{ - Enabled: pointer.Bool(v), + Enabled: v, }, nil } diff --git a/internal/annotations/ip_policies/ip_policy.go b/internal/annotations/ip_policies/ip_policy.go index 1981b253..110704f3 100644 --- a/internal/annotations/ip_policies/ip_policy.go +++ b/internal/annotations/ip_policies/ip_policy.go @@ -4,7 +4,6 @@ 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" - "k8s.io/utils/pointer" ) type ipPolicy struct{} @@ -20,7 +19,6 @@ func (p ipPolicy) Parse(ing *networking.Ingress) (interface{}, error) { } return &ingressv1alpha1.EndpointIPPolicy{ - Enabled: pointer.Bool(true), IPPolicyIDs: v, }, nil } diff --git a/internal/controllers/httpsedge_controller.go b/internal/controllers/httpsedge_controller.go index b078af0c..e358cd69 100644 --- a/internal/controllers/httpsedge_controller.go +++ b/internal/controllers/httpsedge_controller.go @@ -32,6 +32,7 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" + "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -161,6 +162,7 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress return err } + // TODO: clean this up. This is way too much nesting for i, routeSpec := range edge.Spec.Routes { backend, err := tunnelGroupReconciler.findOrCreate(ctx, routeSpec.Backend) if err != nil { @@ -180,17 +182,6 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress BackendID: backend.ID, }, } - if routeSpec.Compression != nil { - req.Compression = &ngrok.EndpointCompression{ - Enabled: routeSpec.Compression.Enabled, - } - } - if routeSpec.IPRestriction != nil { - req.IPRestriction = &ngrok.EndpointIPPolicyMutate{ - Enabled: routeSpec.IPRestriction.Enabled, - IPPolicyIDs: routeSpec.IPRestriction.IPPolicyIDs, - } - } route, err = r.NgrokClientset.HTTPSEdgeRoutes().Create(ctx, req) } else { r.Log.Info("Updating route", "edgeID", edge.Status.ID, "match", routeSpec.Match, "matchType", routeSpec.MatchType, "backendID", backend.ID) @@ -204,17 +195,6 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress BackendID: backend.ID, }, } - if routeSpec.Compression != nil { - req.Compression = &ngrok.EndpointCompression{ - Enabled: routeSpec.Compression.Enabled, - } - } - if routeSpec.IPRestriction != nil { - req.IPRestriction = &ngrok.EndpointIPPolicyMutate{ - Enabled: routeSpec.IPRestriction.Enabled, - IPPolicyIDs: routeSpec.IPRestriction.IPPolicyIDs, - } - } route, err = r.NgrokClientset.HTTPSEdgeRoutes().Update(ctx, req) } if err != nil { @@ -231,6 +211,13 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress ID: route.Backend.Backend.ID, } } + + if err := r.setEdgeRouteCompression(ctx, edge.Status.ID, route.ID, routeSpec.Compression); err != nil { + return err + } + if err := r.setEdgeRouteIPRestriction(ctx, edge.Status.ID, route.ID, routeSpec.IPRestriction); err != nil { + return err + } } edge.Status.Routes = routeStatuses @@ -238,6 +225,38 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress return r.Status().Update(ctx, edge) } +func (r *HTTPSEdgeReconciler) setEdgeRouteCompression(ctx context.Context, edgeID string, routeID string, compression *ingressv1alpha1.EndpointCompression) error { + client := r.NgrokClientset.EdgeModules().HTTPS().Routes().Compression() + + if compression == nil { + return client.Delete(ctx, &ngrok.EdgeRouteItem{EdgeID: edgeID, ID: routeID}) + } + + _, err := client.Replace(ctx, &ngrok.EdgeRouteCompressionReplace{ + EdgeID: edgeID, + ID: routeID, + Module: ngrok.EndpointCompression{ + Enabled: pointer.Bool(compression.Enabled), + }, + }) + return err +} + +func (r *HTTPSEdgeReconciler) setEdgeRouteIPRestriction(ctx context.Context, edgeID string, routeID string, ipRestriction *ingressv1alpha1.EndpointIPPolicy) error { + client := r.NgrokClientset.EdgeModules().HTTPS().Routes().IPRestriction() + if ipRestriction == nil || len(ipRestriction.IPPolicyIDs) == 0 { + return client.Delete(ctx, &ngrok.EdgeRouteItem{EdgeID: edgeID, ID: routeID}) + } + _, err := client.Replace(ctx, &ngrok.EdgeRouteIPRestrictionReplace{ + EdgeID: edgeID, + ID: routeID, + Module: ngrok.EndpointIPPolicyMutate{ + IPPolicyIDs: ipRestriction.IPPolicyIDs, + }, + }) + 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) { diff --git a/internal/controllers/ingress_controller_test.go b/internal/controllers/ingress_controller_test.go index 7cb7db83..5abbc92c 100644 --- a/internal/controllers/ingress_controller_test.go +++ b/internal/controllers/ingress_controller_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" ) func makeTestBackend(serviceName string, servicePort int32) netv1.IngressBackend { @@ -72,6 +71,9 @@ func TestIngressReconcilerIngressToEdge(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "test-ingress", Namespace: "test-namespace", + Annotations: map[string]string{ + "k8s.ngrok.com/https-compression": "false", + }, }, Spec: netv1.IngressSpec{ Rules: []netv1.IngressRule{ @@ -110,6 +112,9 @@ func TestIngressReconcilerIngressToEdge(t *testing.T) { "k8s.ngrok.com/port": "8080", }, }, + Compression: &ingressv1alpha1.EndpointCompression{ + Enabled: false, + }, }, }, }, @@ -164,10 +169,9 @@ func TestIngressReconcilerIngressToEdge(t *testing.T) { }, }, Compression: &ingressv1alpha1.EndpointCompression{ - Enabled: pointer.Bool(true), + Enabled: true, }, IPRestriction: &ingressv1alpha1.EndpointIPPolicy{ - Enabled: pointer.Bool(true), IPPolicyIDs: []string{"policy-1", "policy-2"}, }, }, diff --git a/internal/controllers/tcpedge_controller.go b/internal/controllers/tcpedge_controller.go index 0f5db483..3339fd09 100644 --- a/internal/controllers/tcpedge_controller.go +++ b/internal/controllers/tcpedge_controller.go @@ -167,14 +167,6 @@ func (r *TCPEdgeReconciler) reconcileTunnelGroupBackend(ctx context.Context, edg } func (r *TCPEdgeReconciler) reconcileEdge(ctx context.Context, edge *ingressv1alpha1.TCPEdge) error { - var ipRestriction *ngrok.EndpointIPPolicyMutate - if edge.Spec.IPRestriction != nil { - ipRestriction = &ngrok.EndpointIPPolicyMutate{ - Enabled: edge.Spec.IPRestriction.Enabled, - IPPolicyIDs: edge.Spec.IPRestriction.IPPolicyIDs, - } - } - if edge.Status.ID != "" { // An edge already exists, make sure everything matches resp, err := r.NgrokClientset.TCPEdges().Get(ctx, edge.Status.ID) @@ -191,8 +183,7 @@ func (r *TCPEdgeReconciler) reconcileEdge(ctx context.Context, edge *ingressv1al // If the backend or hostports do not match, update the edge with the desired backend and hostports if resp.Backend.Backend.ID != edge.Status.Backend.ID || - !reflect.DeepEqual(resp.Hostports, edge.Status.Hostports) || - !reflect.DeepEqual(resp.IpRestriction, ipRestriction) { + !reflect.DeepEqual(resp.Hostports, edge.Status.Hostports) { resp, err = r.NgrokClientset.TCPEdges().Update(ctx, &ngrok.TCPEdgeUpdate{ ID: resp.ID, Description: pointer.String(edge.Spec.Description), @@ -201,7 +192,6 @@ func (r *TCPEdgeReconciler) reconcileEdge(ctx context.Context, edge *ingressv1al Backend: &ngrok.EndpointBackendMutate{ BackendID: edge.Status.Backend.ID, }, - IPRestriction: ipRestriction, }) if err != nil { return err @@ -229,14 +219,18 @@ func (r *TCPEdgeReconciler) reconcileEdge(ctx context.Context, edge *ingressv1al Backend: &ngrok.EndpointBackendMutate{ BackendID: edge.Status.Backend.ID, }, - IPRestriction: ipRestriction, }) if err != nil { return err } r.Log.Info("Created new TCPEdge", "edge.ID", resp.ID, "name", edge.Name, "namespace", edge.Namespace) - return r.updateEdgeStatus(ctx, edge, resp) + if err := r.updateEdgeStatus(ctx, edge, resp); err != nil { + return err + } + + return r.updateIPRestrictionRouteModule(ctx, edge, resp) + } func (r *TCPEdgeReconciler) findEdgeByBackendLabels(ctx context.Context, backendLabels map[string]string) (*ngrok.TCPEdge, error) { @@ -321,3 +315,17 @@ func (r *TCPEdgeReconciler) metadataForEdge(edge *ingressv1alpha1.TCPEdge) strin func (r *TCPEdgeReconciler) descriptionForEdge(edge *ingressv1alpha1.TCPEdge) string { return fmt.Sprintf("Reserved for %s/%s", edge.Namespace, edge.Name) } + +func (r *TCPEdgeReconciler) updateIPRestrictionRouteModule(ctx context.Context, edge *ingressv1alpha1.TCPEdge, remoteEdge *ngrok.TCPEdge) error { + if edge.Spec.IPRestriction == nil { + return r.NgrokClientset.EdgeModules().TCP().IPRestriction().Delete(ctx, edge.Status.ID) + } else { + _, err := r.NgrokClientset.EdgeModules().TCP().IPRestriction().Replace(ctx, &ngrok.EdgeIPRestrictionReplace{ + ID: edge.Status.ID, + Module: ngrok.EndpointIPPolicyMutate{ + IPPolicyIDs: edge.Spec.IPRestriction.IPPolicyIDs, + }, + }) + return err + } +} From 30ac6d50b34fb7d63c5601e2e1f83ce38ff8e1d3 Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 02:47:34 -0600 Subject: [PATCH 2/7] Add tests for compression --- .../annotations/compression/compression.go | 3 + .../compression/compression_test.go | 58 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 internal/annotations/compression/compression_test.go diff --git a/internal/annotations/compression/compression.go b/internal/annotations/compression/compression.go index 5e1ca325..40c57669 100644 --- a/internal/annotations/compression/compression.go +++ b/internal/annotations/compression/compression.go @@ -12,6 +12,9 @@ func NewParser() parser.IngressAnnotation { return compression{} } +// Parse parses the annotations contained in the ingress and returns a +// compression configuration or an error. If no compression annotations are +// found, the returned error an errors.ErrMissingAnnotations. func (c compression) Parse(ing *networking.Ingress) (interface{}, error) { v, err := parser.GetBoolAnnotation("https-compression", ing) if err != nil { diff --git a/internal/annotations/compression/compression_test.go b/internal/annotations/compression/compression_test.go new file mode 100644 index 00000000..b9ac5f8d --- /dev/null +++ b/internal/annotations/compression/compression_test.go @@ -0,0 +1,58 @@ +package compression + +import ( + "testing" + + ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1" + "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser" + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + networking "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func createIngressWithAnnotations(annotations map[string]string) *networking.Ingress { + return &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: v1.NamespaceDefault, + Annotations: annotations, + }, + } +} + +func TestCompressionWhenNotSupplied(t *testing.T) { + ing := createIngressWithAnnotations(map[string]string{}) + parsed, err := NewParser().Parse(ing) + + assert.Nil(t, parsed) + assert.Error(t, err) +} + +func TestCompressionWhenSuppliedAndTrue(t *testing.T) { + ing := createIngressWithAnnotations(map[string]string{ + parser.GetAnnotationWithPrefix("https-compression"): "true", + }) + parsed, err := NewParser().Parse(ing) + assert.NoError(t, err) + + compression, ok := parsed.(*ingressv1alpha1.EndpointCompression) + if !ok { + t.Fatalf("expected *ingressv1alpha1.EndpointCompression, got %T", parsed) + } + assert.Equal(t, true, compression.Enabled) +} + +func TestCompressionWhenSuppliedAndFalse(t *testing.T) { + ing := createIngressWithAnnotations(map[string]string{ + parser.GetAnnotationWithPrefix("https-compression"): "false", + }) + parsed, err := NewParser().Parse(ing) + assert.NoError(t, err) + + compression, ok := parsed.(*ingressv1alpha1.EndpointCompression) + if !ok { + t.Fatalf("expected *ingressv1alpha1.EndpointCompression, got %T", parsed) + } + assert.Equal(t, false, compression.Enabled) +} From c1ac6bda6f34f26496afbdc9c80d7439f920118e Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 03:00:51 -0600 Subject: [PATCH 3/7] Add testutil for annotations parsing tests --- .../compression/compression_test.go | 35 ++++++------- internal/annotations/headers/headers.go | 52 +++++++++++++++++++ internal/annotations/testutil/ingress.go | 41 +++++++++++++++ 3 files changed, 108 insertions(+), 20 deletions(-) create mode 100644 internal/annotations/headers/headers.go create mode 100644 internal/annotations/testutil/ingress.go diff --git a/internal/annotations/compression/compression_test.go b/internal/annotations/compression/compression_test.go index b9ac5f8d..257e69a8 100644 --- a/internal/annotations/compression/compression_test.go +++ b/internal/annotations/compression/compression_test.go @@ -5,34 +5,27 @@ import ( ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1" "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" - v1 "k8s.io/api/core/v1" - networking "k8s.io/api/networking/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func createIngressWithAnnotations(annotations map[string]string) *networking.Ingress { - return &networking.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ingress", - Namespace: v1.NamespaceDefault, - Annotations: annotations, - }, - } -} - func TestCompressionWhenNotSupplied(t *testing.T) { - ing := createIngressWithAnnotations(map[string]string{}) + 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 TestCompressionWhenSuppliedAndTrue(t *testing.T) { - ing := createIngressWithAnnotations(map[string]string{ - parser.GetAnnotationWithPrefix("https-compression"): "true", - }) + ing := testutil.NewIngress() + annotations := map[string]string{} + annotations[parser.GetAnnotationWithPrefix("https-compression")] = "true" + ing.SetAnnotations(annotations) + parsed, err := NewParser().Parse(ing) assert.NoError(t, err) @@ -44,9 +37,11 @@ func TestCompressionWhenSuppliedAndTrue(t *testing.T) { } func TestCompressionWhenSuppliedAndFalse(t *testing.T) { - ing := createIngressWithAnnotations(map[string]string{ - parser.GetAnnotationWithPrefix("https-compression"): "false", - }) + ing := testutil.NewIngress() + annotations := map[string]string{} + annotations[parser.GetAnnotationWithPrefix("https-compression")] = "false" + ing.SetAnnotations(annotations) + parsed, err := NewParser().Parse(ing) assert.NoError(t, err) diff --git a/internal/annotations/headers/headers.go b/internal/annotations/headers/headers.go new file mode 100644 index 00000000..48507281 --- /dev/null +++ b/internal/annotations/headers/headers.go @@ -0,0 +1,52 @@ +package headers + +import ( + ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1" + "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser" + "github.com/ngrok/kubernetes-ingress-controller/internal/errors" + networking "k8s.io/api/networking/v1" + "k8s.io/utils/pointer" +) + +type headers struct{} + +func NewParser() parser.IngressAnnotation { + return headers{} +} + +func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { + parsed := &ingressv1alpha1.EndpointHeaders{ + Request: &ingressv1alpha1.EndpointRequestHeaders{ + Enabled: pointer.Bool(false), + }, + Response: &ingressv1alpha1.EndpointResponseHeaders{ + Enabled: pointer.Bool(false), + }, + } + + v, err := parser.GetStringSliceAnnotation("request-headers-remove", ing) + if err != nil { + if !errors.IsMissingAnnotations(err) { + return parsed, err + } + } + + if len(v) > 0 { + parsed.Request.Enabled = pointer.Bool(true) + parsed.Request.Remove = v + } + + v, err = parser.GetStringSliceAnnotation("response-headers-remove", ing) + if err != nil { + if !errors.IsMissingAnnotations(err) { + return parsed, err + } + } + + if len(v) > 0 { + parsed.Response.Enabled = pointer.Bool(true) + parsed.Response.Remove = v + } + + return parsed, nil +} diff --git a/internal/annotations/testutil/ingress.go b/internal/annotations/testutil/ingress.go new file mode 100644 index 00000000..64174469 --- /dev/null +++ b/internal/annotations/testutil/ingress.go @@ -0,0 +1,41 @@ +// Test Utilities for Ingress Annotations +package testutil + +import ( + v1 "k8s.io/api/core/v1" + networking "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func NewIngress() *networking.Ingress { + return &networking.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-ingress", + Namespace: v1.NamespaceDefault, + }, + Spec: networking.IngressSpec{ + Rules: []networking.IngressRule{ + { + Host: "test.ngrok.io", + IngressRuleValue: networking.IngressRuleValue{ + HTTP: &networking.HTTPIngressRuleValue{ + Paths: []networking.HTTPIngressPath{ + { + Path: "/foo", + Backend: networking.IngressBackend{ + Service: &networking.IngressServiceBackend{ + Name: "foo", + Port: networking.ServiceBackendPort{ + Number: 80, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} From f27a5f723b870069b81b915dc8a064beaadb5000 Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 03:39:00 -0600 Subject: [PATCH 4/7] Add Headers Route Module to HTTPSEdgeRouteSpec api --- api/v1alpha1/httpsedge_types.go | 3 + api/v1alpha1/ngrok_common.go | 29 +++++++ api/v1alpha1/zz_generated.deepcopy.go | 84 +++++++++++++++++++ .../ingress.k8s.ngrok.com_httpsedges.yaml | 42 ++++++++++ internal/annotations/headers/headers.go | 18 ++-- 5 files changed, 165 insertions(+), 11 deletions(-) diff --git a/api/v1alpha1/httpsedge_types.go b/api/v1alpha1/httpsedge_types.go index b99a0ed9..bd03bae2 100644 --- a/api/v1alpha1/httpsedge_types.go +++ b/api/v1alpha1/httpsedge_types.go @@ -53,6 +53,9 @@ type HTTPSEdgeRouteSpec struct { // IPRestriction is an IPRestriction to apply to this route IPRestriction *EndpointIPPolicy `json:"ipRestriction,omitempty"` + + // Headers are request/response headers to apply to this route + Headers *EndpointHeaders `json:"headers,omitempty"` } // HTTPSEdgeSpec defines the desired state of HTTPSEdge diff --git a/api/v1alpha1/ngrok_common.go b/api/v1alpha1/ngrok_common.go index e6beab13..58907bb3 100644 --- a/api/v1alpha1/ngrok_common.go +++ b/api/v1alpha1/ngrok_common.go @@ -20,3 +20,32 @@ type EndpointCompression struct { type EndpointIPPolicy struct { IPPolicyIDs []string `json:"policyIDs,omitempty"` } + +// EndpointRequestHeaders is the configuration for a HTTPSEdgeRoute's request headers +// to be added or removed from the request before it is sent to the backend service. +type EndpointRequestHeaders struct { + // a map of header key to header value that will be injected into the HTTP Request + // before being sent to the upstream application server + Add map[string]string `json:"add,omitempty"` + // a list of header names that will be removed from the HTTP Request before being + // sent to the upstream application server + Remove []string `json:"remove,omitempty"` +} + +// EndpointResponseHeaders is the configuration for a HTTPSEdgeRoute's response headers +// to be added or removed from the response before it is sent to the client. +type EndpointResponseHeaders struct { + // a map of header key to header value that will be injected into the HTTP Response + // returned to the HTTP client + Add map[string]string `json:"add,omitempty"` + // a list of header names that will be removed from the HTTP Response returned to + // the HTTP client + Remove []string `json:"remove,omitempty"` +} + +type EndpointHeaders struct { + // Request headers are the request headers module configuration or null + Request *EndpointRequestHeaders `json:"request,omitempty"` + // Response headers are the response headers module configuration or null + Response *EndpointResponseHeaders `json:"response,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e2ba71a7..114aa9f6 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -143,6 +143,31 @@ func (in *EndpointCompression) DeepCopy() *EndpointCompression { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointHeaders) DeepCopyInto(out *EndpointHeaders) { + *out = *in + if in.Request != nil { + in, out := &in.Request, &out.Request + *out = new(EndpointRequestHeaders) + (*in).DeepCopyInto(*out) + } + if in.Response != nil { + in, out := &in.Response, &out.Response + *out = new(EndpointResponseHeaders) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointHeaders. +func (in *EndpointHeaders) DeepCopy() *EndpointHeaders { + if in == nil { + return nil + } + out := new(EndpointHeaders) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EndpointIPPolicy) DeepCopyInto(out *EndpointIPPolicy) { *out = *in @@ -163,6 +188,60 @@ func (in *EndpointIPPolicy) DeepCopy() *EndpointIPPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointRequestHeaders) DeepCopyInto(out *EndpointRequestHeaders) { + *out = *in + if in.Add != nil { + in, out := &in.Add, &out.Add + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Remove != nil { + in, out := &in.Remove, &out.Remove + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointRequestHeaders. +func (in *EndpointRequestHeaders) DeepCopy() *EndpointRequestHeaders { + if in == nil { + return nil + } + out := new(EndpointRequestHeaders) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EndpointResponseHeaders) DeepCopyInto(out *EndpointResponseHeaders) { + *out = *in + if in.Add != nil { + in, out := &in.Add, &out.Add + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Remove != nil { + in, out := &in.Remove, &out.Remove + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointResponseHeaders. +func (in *EndpointResponseHeaders) DeepCopy() *EndpointResponseHeaders { + if in == nil { + return nil + } + out := new(EndpointResponseHeaders) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HTTPSEdge) DeepCopyInto(out *HTTPSEdge) { *out = *in @@ -237,6 +316,11 @@ func (in *HTTPSEdgeRouteSpec) DeepCopyInto(out *HTTPSEdgeRouteSpec) { *out = new(EndpointIPPolicy) (*in).DeepCopyInto(*out) } + if in.Headers != nil { + in, out := &in.Headers, &out.Headers + *out = new(EndpointHeaders) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTPSEdgeRouteSpec. diff --git a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml index 9e2e5316..05c124da 100644 --- a/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml +++ b/helm/ingress-controller/templates/crds/ingress.k8s.ngrok.com_httpsedges.yaml @@ -88,6 +88,48 @@ spec: description: Description is a human-readable description of the object in the ngrok API/Dashboard type: string + headers: + description: Headers are request/response headers to apply to + this route + properties: + request: + description: Request headers are the request headers module + configuration or null + properties: + add: + additionalProperties: + type: string + description: a map of header key to header value that + will be injected into the HTTP Request before being + sent to the upstream application server + type: object + remove: + description: a list of header names that will be removed + from the HTTP Request before being sent to the upstream + application server + items: + type: string + type: array + type: object + response: + description: Response headers are the response headers module + configuration or null + properties: + add: + additionalProperties: + type: string + description: a map of header key to header value that + will be injected into the HTTP Response returned to + the HTTP client + type: object + remove: + description: a list of header names that will be removed + from the HTTP Response returned to the HTTP client + items: + type: string + type: array + type: object + type: object ipRestriction: description: IPRestriction is an IPRestriction to apply to this route diff --git a/internal/annotations/headers/headers.go b/internal/annotations/headers/headers.go index 48507281..183b1efd 100644 --- a/internal/annotations/headers/headers.go +++ b/internal/annotations/headers/headers.go @@ -5,7 +5,6 @@ import ( "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser" "github.com/ngrok/kubernetes-ingress-controller/internal/errors" networking "k8s.io/api/networking/v1" - "k8s.io/utils/pointer" ) type headers struct{} @@ -15,14 +14,7 @@ func NewParser() parser.IngressAnnotation { } func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { - parsed := &ingressv1alpha1.EndpointHeaders{ - Request: &ingressv1alpha1.EndpointRequestHeaders{ - Enabled: pointer.Bool(false), - }, - Response: &ingressv1alpha1.EndpointResponseHeaders{ - Enabled: pointer.Bool(false), - }, - } + parsed := &ingressv1alpha1.EndpointHeaders{} v, err := parser.GetStringSliceAnnotation("request-headers-remove", ing) if err != nil { @@ -32,7 +24,9 @@ func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { } if len(v) > 0 { - parsed.Request.Enabled = pointer.Bool(true) + if parsed.Request == nil { + parsed.Request = &ingressv1alpha1.EndpointRequestHeaders{} + } parsed.Request.Remove = v } @@ -44,7 +38,9 @@ func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { } if len(v) > 0 { - parsed.Response.Enabled = pointer.Bool(true) + if parsed.Response == nil { + parsed.Response = &ingressv1alpha1.EndpointResponseHeaders{} + } parsed.Response.Remove = v } From 9e238308cd7c1731416ebb296a6289c1c61e2e6b Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 03:44:49 -0600 Subject: [PATCH 5/7] Add headers route module annotation parsing --- internal/annotations/annotations.go | 3 + internal/annotations/annotations_test.go | 26 ++---- internal/annotations/headers/headers.go | 49 ++++++++++- internal/annotations/headers/headers_test.go | 85 ++++++++++++++++++++ internal/annotations/parser/parser.go | 26 ++++++ 5 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 internal/annotations/headers/headers_test.go diff --git a/internal/annotations/annotations.go b/internal/annotations/annotations.go index 5170f561..05998e36 100644 --- a/internal/annotations/annotations.go +++ b/internal/annotations/annotations.go @@ -20,6 +20,7 @@ import ( "github.com/imdario/mergo" ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1" "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/compression" + "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/errors" @@ -32,6 +33,7 @@ const DeniedKeyName = "Denied" type RouteModules struct { Compression *ingressv1alpha1.EndpointCompression + Headers *ingressv1alpha1.EndpointHeaders IPRestriction *ingressv1alpha1.EndpointIPPolicy } @@ -43,6 +45,7 @@ func NewAnnotationsExtractor() Extractor { return Extractor{ annotations: map[string]parser.IngressAnnotation{ "Compression": compression.NewParser(), + "Headers": headers.NewParser(), "IPRestriction": ip_policies.NewParser(), }, } diff --git a/internal/annotations/annotations_test.go b/internal/annotations/annotations_test.go index 1f5a0c66..aae06ba7 100644 --- a/internal/annotations/annotations_test.go +++ b/internal/annotations/annotations_test.go @@ -4,6 +4,8 @@ import ( "testing" ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/v1alpha1" + "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/parser" + "github.com/ngrok/kubernetes-ingress-controller/internal/annotations/testutil" "github.com/stretchr/testify/assert" networking "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -17,27 +19,15 @@ func newIngressWithAnnotations(annotations map[string]string) *networking.Ingres } } -func TestCompression(t *testing.T) { +func TestParsesIPPolicies(t *testing.T) { e := NewAnnotationsExtractor() - modules := e.Extract(newIngressWithAnnotations(map[string]string{ - "k8s.ngrok.com/https-compression": "false", - })) - assert.False(t, modules.Compression.Enabled) + ing := testutil.NewIngress() + ing.SetAnnotations(map[string]string{ + parser.GetAnnotationWithPrefix("ip-policy-ids"): "abc123,def456", + }) - modules = e.Extract(newIngressWithAnnotations(map[string]string{ - "k8s.ngrok.com/https-compression": "true", - })) - assert.True(t, modules.Compression.Enabled) + modules := e.Extract(ing) - modules = e.Extract(newIngressWithAnnotations(map[string]string{})) - assert.Nil(t, modules.Compression) -} - -func TestIPPolicies(t *testing.T) { - e := NewAnnotationsExtractor() - modules := e.Extract(newIngressWithAnnotations(map[string]string{ - "k8s.ngrok.com/ip-policy-ids": "abc123,def456", - })) assert.Equal(t, &ingressv1alpha1.EndpointIPPolicy{ IPPolicyIDs: []string{ "abc123", diff --git a/internal/annotations/headers/headers.go b/internal/annotations/headers/headers.go index 183b1efd..0f30f86f 100644 --- a/internal/annotations/headers/headers.go +++ b/internal/annotations/headers/headers.go @@ -7,6 +7,10 @@ import ( networking "k8s.io/api/networking/v1" ) +type EndpointHeaders = ingressv1alpha1.EndpointHeaders +type EndpointRequestHeaders = ingressv1alpha1.EndpointRequestHeaders +type EndpointResponseHeaders = ingressv1alpha1.EndpointResponseHeaders + type headers struct{} func NewParser() parser.IngressAnnotation { @@ -14,7 +18,7 @@ func NewParser() parser.IngressAnnotation { } func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { - parsed := &ingressv1alpha1.EndpointHeaders{} + parsed := &EndpointHeaders{} v, err := parser.GetStringSliceAnnotation("request-headers-remove", ing) if err != nil { @@ -25,11 +29,31 @@ func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { if len(v) > 0 { if parsed.Request == nil { - parsed.Request = &ingressv1alpha1.EndpointRequestHeaders{} + parsed.Request = &EndpointRequestHeaders{} } parsed.Request.Remove = v } + m, err := parser.GetStringMapAnnotation("request-headers-add", ing) + if err != nil { + if !errors.IsMissingAnnotations(err) { + return parsed, err + } + } + + if len(m) > 0 { + if parsed.Request == nil { + parsed.Request = &EndpointRequestHeaders{} + } + parsed.Request.Add = m + } + + if err != nil { + if !errors.IsMissingAnnotations(err) { + return parsed, err + } + } + v, err = parser.GetStringSliceAnnotation("response-headers-remove", ing) if err != nil { if !errors.IsMissingAnnotations(err) { @@ -39,10 +63,29 @@ func (h headers) Parse(ing *networking.Ingress) (interface{}, error) { if len(v) > 0 { if parsed.Response == nil { - parsed.Response = &ingressv1alpha1.EndpointResponseHeaders{} + parsed.Response = &EndpointResponseHeaders{} } parsed.Response.Remove = v } + m, err = parser.GetStringMapAnnotation("response-headers-add", ing) + if err != nil { + if !errors.IsMissingAnnotations(err) { + return parsed, err + } + } + + if len(m) > 0 { + if parsed.Response == nil { + parsed.Response = &EndpointResponseHeaders{} + } + parsed.Response.Add = m + } + + // If none of the annotations are present, return the missing annotations error + if parsed.Request == nil && parsed.Response == nil { + return nil, errors.ErrMissingAnnotations + } + return parsed, nil } diff --git a/internal/annotations/headers/headers_test.go b/internal/annotations/headers/headers_test.go new file mode 100644 index 00000000..55a22ee6 --- /dev/null +++ b/internal/annotations/headers/headers_test.go @@ -0,0 +1,85 @@ +package headers + +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 TestHeadersWhenNotSupplied(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 TestHeadersWhenRequestHeadersSupplied(t *testing.T) { + ing := testutil.NewIngress() + annotations := map[string]string{} + annotations[parser.GetAnnotationWithPrefix("request-headers-remove")] = "Server" + annotations[parser.GetAnnotationWithPrefix("request-headers-add")] = `{"X-Request-Header": "value"}` + ing.SetAnnotations(annotations) + + parsed, err := NewParser().Parse(ing) + assert.NoError(t, err) + assert.NotNil(t, parsed) + + endpointHeaders, ok := parsed.(*EndpointHeaders) + if !ok { + t.Fatalf("expected *EndpointHeaders, got %T", parsed) + } + + assert.Nil(t, endpointHeaders.Response) + assert.Equal(t, []string{"Server"}, endpointHeaders.Request.Remove) + assert.Equal(t, map[string]string{"X-Request-Header": "value"}, endpointHeaders.Request.Add) +} + +func TestHeadersWhenResponseHeadersSupplied(t *testing.T) { + ing := testutil.NewIngress() + annotations := map[string]string{} + annotations[parser.GetAnnotationWithPrefix("response-headers-remove")] = "Server" + annotations[parser.GetAnnotationWithPrefix("response-headers-add")] = `{"X-Response-Header": "value"}` + ing.SetAnnotations(annotations) + + parsed, err := NewParser().Parse(ing) + assert.NoError(t, err) + assert.NotNil(t, parsed) + + endpointHeaders, ok := parsed.(*EndpointHeaders) + if !ok { + t.Fatalf("expected *EndpointHeaders, got %T", parsed) + } + assert.Nil(t, endpointHeaders.Request) + assert.Equal(t, []string{"Server"}, endpointHeaders.Response.Remove) + assert.Equal(t, map[string]string{"X-Response-Header": "value"}, endpointHeaders.Response.Add) +} + +func TestInvalidRequestHeadersAdd(t *testing.T) { + ing := testutil.NewIngress() + annotations := map[string]string{} + // Not valid JSON + annotations[parser.GetAnnotationWithPrefix("request-headers-add")] = `{X-Request-Header: value}` + ing.SetAnnotations(annotations) + + _, err := NewParser().Parse(ing) + assert.Error(t, err) + assert.True(t, errors.IsInvalidContent(err)) +} + +func TestInvalidResponseHeadersAdd(t *testing.T) { + ing := testutil.NewIngress() + annotations := map[string]string{} + // Not valid JSON + annotations[parser.GetAnnotationWithPrefix("response-headers-add")] = `{X-Response-Header: value}` + ing.SetAnnotations(annotations) + + _, err := NewParser().Parse(ing) + assert.Error(t, err) + assert.True(t, errors.IsInvalidContent(err)) +} diff --git a/internal/annotations/parser/parser.go b/internal/annotations/parser/parser.go index 375decd5..5802c0a6 100644 --- a/internal/annotations/parser/parser.go +++ b/internal/annotations/parser/parser.go @@ -17,6 +17,7 @@ limitations under the License. package parser import ( + "encoding/json" "fmt" "net/url" "strconv" @@ -86,6 +87,21 @@ func (a ingAnnotations) parseStringSlice(name string) ([]string, error) { return []string{}, errors.ErrMissingAnnotations } +func (a ingAnnotations) parseStringMap(name string) (map[string]string, error) { + val, ok := a[name] + if !ok { + return nil, errors.ErrMissingAnnotations + } + + m := map[string]string{} + err := json.Unmarshal([]byte(val), &m) + if err != nil { + return nil, errors.NewInvalidAnnotationContent(name, val) + } + + return m, nil +} + func (a ingAnnotations) parseInt(name string) (int, error) { val, ok := a[name] if ok { @@ -152,6 +168,16 @@ func GetStringSliceAnnotation(name string, ing *networking.Ingress) ([]string, e return ingAnnotations(ing.GetAnnotations()).parseStringSlice(v) } +func GetStringMapAnnotation(name string, ing *networking.Ingress) (map[string]string, error) { + v := GetAnnotationWithPrefix(name) + err := checkAnnotation(v, ing) + if err != nil { + return nil, err + } + + return ingAnnotations(ing.GetAnnotations()).parseStringMap(v) +} + // GetIntAnnotation extracts an int from an Ingress annotation func GetIntAnnotation(name string, ing *networking.Ingress) (int, error) { v := GetAnnotationWithPrefix(name) From 6e463347182a0714aed954550aed6e6fe40c296e Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 22:19:28 -0600 Subject: [PATCH 6/7] Pass parsed header configuration through ingress controller into the HTTPSEdgeRouteSpec --- internal/controllers/ingress_controller.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/controllers/ingress_controller.go b/internal/controllers/ingress_controller.go index 060a09bb..a61a99ef 100644 --- a/internal/controllers/ingress_controller.go +++ b/internal/controllers/ingress_controller.go @@ -174,6 +174,7 @@ func (irec *IngressReconciler) routesPlanner(ctx context.Context, ingress *netv1 }, Compression: parsedRouteModules.Compression, IPRestriction: parsedRouteModules.IPRestriction, + Headers: parsedRouteModules.Headers, } ngrokRoutes = append(ngrokRoutes, route) From 56ebc7e7840770308af2473080b3df256f81497c Mon Sep 17 00:00:00 2001 From: Jonathan Stacks Date: Tue, 24 Jan 2023 22:45:07 -0600 Subject: [PATCH 7/7] Support request/response Edge Modules in HTTPSEdgeController --- internal/controllers/httpsedge_controller.go | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/internal/controllers/httpsedge_controller.go b/internal/controllers/httpsedge_controller.go index e358cd69..636744c3 100644 --- a/internal/controllers/httpsedge_controller.go +++ b/internal/controllers/httpsedge_controller.go @@ -218,6 +218,20 @@ func (r *HTTPSEdgeReconciler) reconcileRoutes(ctx context.Context, edge *ingress if err := r.setEdgeRouteIPRestriction(ctx, edge.Status.ID, route.ID, routeSpec.IPRestriction); err != nil { return err } + var requestHeaders *ingressv1alpha1.EndpointRequestHeaders + if routeSpec.Headers != nil { + requestHeaders = routeSpec.Headers.Request + } + if err := r.setEdgeRouteRequestHeaders(ctx, edge.Status.ID, route.ID, requestHeaders); err != nil { + return err + } + var responseHeaders *ingressv1alpha1.EndpointResponseHeaders + if routeSpec.Headers != nil { + responseHeaders = routeSpec.Headers.Response + } + if err := r.setEdgeRouteResponseHeaders(ctx, edge.Status.ID, route.ID, responseHeaders); err != nil { + return err + } } edge.Status.Routes = routeStatuses @@ -257,6 +271,50 @@ func (r *HTTPSEdgeReconciler) setEdgeRouteIPRestriction(ctx context.Context, edg return err } +func (r *HTTPSEdgeReconciler) setEdgeRouteRequestHeaders(ctx context.Context, edgeID string, routeID string, requestHeaders *ingressv1alpha1.EndpointRequestHeaders) error { + client := r.NgrokClientset.EdgeModules().HTTPS().Routes().RequestHeaders() + if requestHeaders == nil { + return client.Delete(ctx, &ngrok.EdgeRouteItem{EdgeID: edgeID, ID: routeID}) + } + + module := ngrok.EndpointRequestHeaders{} + if len(requestHeaders.Add) > 0 { + module.Add = requestHeaders.Add + } + if len(requestHeaders.Remove) > 0 { + module.Remove = requestHeaders.Remove + } + + _, err := client.Replace(ctx, &ngrok.EdgeRouteRequestHeadersReplace{ + EdgeID: edgeID, + ID: routeID, + Module: module, + }) + return err +} + +func (r *HTTPSEdgeReconciler) setEdgeRouteResponseHeaders(ctx context.Context, edgeID string, routeID string, responseHeaders *ingressv1alpha1.EndpointResponseHeaders) error { + client := r.NgrokClientset.EdgeModules().HTTPS().Routes().ResponseHeaders() + if responseHeaders == nil { + return client.Delete(ctx, &ngrok.EdgeRouteItem{EdgeID: edgeID, ID: routeID}) + } + + module := ngrok.EndpointResponseHeaders{} + if len(responseHeaders.Add) > 0 { + module.Add = responseHeaders.Add + } + if len(responseHeaders.Remove) > 0 { + module.Remove = responseHeaders.Remove + } + + _, err := client.Replace(ctx, &ngrok.EdgeRouteResponseHeadersReplace{ + EdgeID: edgeID, + ID: routeID, + Module: module, + }) + 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) {