Skip to content

Commit

Permalink
Add extensionRef support for policy crd inclusion
Browse files Browse the repository at this point in the history
  • Loading branch information
bobzilladev committed May 17, 2024
1 parent ef27dd1 commit 9487f4f
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 35 deletions.
119 changes: 84 additions & 35 deletions internal/store/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1005,7 +1005,9 @@ func (d *Driver) calculateHTTPSEdgesFromGateway(edgeMap map[string]ingressv1alph
}

// TODO: set with values from rules.Filters + rules.Matches
policy, err := d.createEndpointPolicyForGateway(&rule)
// this HTTPRouteRule comes direct from gateway api yaml, and func returns the policy,
// which goes directly into the edge route in ngrok.
policy, err := d.createEndpointPolicyForGateway(&rule, httproute.Namespace)

if err != nil {
d.log.Error(err, "error creating policy from HTTPRouteRule", "rule", rule)
Expand Down Expand Up @@ -1061,10 +1063,13 @@ type Actions struct {
endpointActions []ingressv1alpha1.EndpointAction
}

func (d *Driver) createEndpointPolicyForGateway(rule *gatewayv1.HTTPRouteRule) (*ingressv1alpha1.EndpointPolicy, error) {
type EndpointRules struct {
rules []ingressv1alpha1.EndpointRule
}

func (d *Driver) createEndpointPolicyForGateway(rule *gatewayv1.HTTPRouteRule, namespace string) (*ingressv1alpha1.EndpointPolicy, error) {
inboundActions := Actions{}
outboundActions := Actions{}
expressions := []string{}
pathPrefixMatches := []string{}

// NOTE: matches are only defined on requests, and fitlers are only triggered by matches,
Expand Down Expand Up @@ -1100,6 +1105,36 @@ func (d *Driver) createEndpointPolicyForGateway(rule *gatewayv1.HTTPRouteRule) (
}
}

inboundRules := EndpointRules{}
outboundRules := EndpointRules{}
flushCount := 0

flushActionsToRules := func() {
if len(inboundActions.endpointActions) == 0 && len(outboundActions.endpointActions) == 0 {
return
}
// there are actions to flush
flushCount++
if len(inboundActions.endpointActions) > 0 {
// flush actions to a rule
inboundRules.rules = append(inboundRules.rules, ingressv1alpha1.EndpointRule{
Actions: inboundActions.endpointActions,
Name: fmt.Sprint("Inbound HTTPRouteRule ", flushCount),
})
// clear
inboundActions.endpointActions = []ingressv1alpha1.EndpointAction{}
}
if len(outboundActions.endpointActions) > 0 {
// flush actions to a rule
outboundRules.rules = append(outboundRules.rules, ingressv1alpha1.EndpointRule{
Actions: outboundActions.endpointActions,
Name: fmt.Sprint("Outbound HTTPRouteRule ", flushCount),
})
// clear
outboundActions.endpointActions = []ingressv1alpha1.EndpointAction{}
}
}

responseHeaders := make(map[string]string)
for _, filter := range rule.Filters {
switch filter.Type {
Expand Down Expand Up @@ -1127,46 +1162,29 @@ func (d *Driver) createEndpointPolicyForGateway(rule *gatewayv1.HTTPRouteRule) (
case gatewayv1.HTTPRouteFilterRequestMirror:
return nil, errors.NewErrorNotFound(fmt.Sprintf("Unsupported filter HTTPRouteFilterType %v found", filter.Type))
case gatewayv1.HTTPRouteFilterExtensionRef:
return nil, errors.NewErrorNotFound(fmt.Sprintf("Unsupported filter HTTPRouteFilterType %v found", filter.Type))
// if there are current actions outstanding, make a rule to hold them before we start a new rule for this PolicyCRD
flushActionsToRules()

// a PolicyCRD can have expressions, so send in rule pointers so expressions can be on those rules
err := d.handleExtensionRef(filter.ExtensionRef, namespace, &inboundRules, &outboundRules)
if err != nil {
return nil, err
}
default:
return nil, errors.NewErrorNotFound(fmt.Sprintf("Unknown filter HTTPRouteFilterType %v found", filter.Type))
}
}

// flush any leftover actions to rules
flushActionsToRules()

var policy *ingressv1alpha1.EndpointPolicy
enabled := true

if len(expressions) > 1 {
expressions = []string{strings.Join(expressions[:], " || ")}
}

if len(inboundActions.endpointActions) > 0 {
policy = &ingressv1alpha1.EndpointPolicy{
Enabled: &enabled,
// NOTE: Mapping each HTTPRouteRule to one Inbound endpoint rule
Inbound: []ingressv1alpha1.EndpointRule{
{
Expressions: expressions,
Actions: inboundActions.endpointActions,
Name: "Inbound HTTPRouteRule",
},
},
}
}
if len(outboundActions.endpointActions) > 0 {
if policy == nil {
policy = &ingressv1alpha1.EndpointPolicy{
Enabled: &enabled,
}
}

policy.Outbound = []ingressv1alpha1.EndpointRule{
{
Expressions: expressions,
Actions: outboundActions.endpointActions,
Name: "Outbound HTTPRouteRule",
},
}
policy = &ingressv1alpha1.EndpointPolicy{
Enabled: &enabled,
Inbound: inboundRules.rules,
Outbound: outboundRules.rules,
}

return policy, nil
Expand All @@ -1180,6 +1198,37 @@ type AddHeadersConfig struct {
Headers map[string]string `json:"headers"`
}

func (d *Driver) handleExtensionRef(extensionRef *gatewayv1.LocalObjectReference, namespace string, inboundRules *EndpointRules,
outboundRules *EndpointRules) error {

switch extensionRef.Kind {
case "NgrokTrafficPolicy":
// look up Policy CRD
policy, err := d.store.GetNgrokTrafficPolicyV1(string(extensionRef.Name), namespace)
if err != nil {
return err
}

// unmarshal the json into a policy struct
jsonMessage := policy.Spec.Policy
if jsonMessage == nil {
return errors.NewErrorNotFound(fmt.Sprintf("PolicyCRD %v found with no policy", extensionRef.Name))
}
var policyStruct ingressv1alpha1.EndpointPolicy
err = json.Unmarshal(jsonMessage, &policyStruct)
if err != nil {
return err
}

// copy the rules
inboundRules.rules = append(inboundRules.rules, policyStruct.Inbound...)
outboundRules.rules = append(outboundRules.rules, policyStruct.Outbound...)
default:
return errors.NewErrorNotFound(fmt.Sprintf("Unknown ExtensionRef Kind %v found, Name: %v", extensionRef.Kind, extensionRef.Name))
}
return nil
}

func (d *Driver) handleHTTPHeaderFilter(filter *gatewayv1.HTTPHeaderFilter, actions *Actions, requestRedirectHeaders map[string]string) error {
if filter == nil {
return nil
Expand Down
82 changes: 82 additions & 0 deletions internal/store/driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package store

import (
"context"
"encoding/json"

"github.com/go-logr/logr"
. "github.com/onsi/ginkgo/v2"
Expand All @@ -17,6 +18,7 @@ import (
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"

ingressv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/ingress/v1alpha1"
ngrokv1alpha1 "github.com/ngrok/kubernetes-ingress-controller/api/ngrok/v1alpha1"
)

const defaultManagerName = "ngrok-ingress-controller"
Expand Down Expand Up @@ -426,6 +428,86 @@ var _ = Describe("Driver", func() {
})
})

Describe("createEndpointPolicyForGateway", func() {
var rule *gatewayv1.HTTPRouteRule
var namespace string
var policyCrd *ngrokv1alpha1.NgrokTrafficPolicy

BeforeEach(func() {
rule = &gatewayv1.HTTPRouteRule{}
namespace = "test"
policyCrd = &ngrokv1alpha1.NgrokTrafficPolicy{
ObjectMeta: metav1.ObjectMeta{
Name: "test-policy",
Namespace: namespace,
},
Spec: ngrokv1alpha1.NgrokTrafficPolicySpec{
Policy: []byte(`{"inbound": [{"name":"t","actions":[{"type":"deny"}]}], "outbound": []}`),
},
}
driver.store.Add(policyCrd)
})

It("Should return an empty policy if the rule has nothing in it", func() {
policy, err := driver.createEndpointPolicyForGateway(rule, namespace)
Expect(err).To(BeNil())
Expect(policy).ToNot(BeNil())
Expect(len(policy.Inbound)).To(BeZero())
Expect(len(policy.Outbound)).To(BeZero())
})

It("Should return a merged policy if there rules with extensionRef", func() {
hostname := gatewayv1.PreciseHostname("test-hostname.com")
replacePrefixMatch := "/paprika"

rule.Filters = []gatewayv1.HTTPRouteFilter{
{
Type: "RequestHeaderModifier",
RequestHeaderModifier: &gatewayv1.HTTPHeaderFilter{
Add: []gatewayv1.HTTPHeader{
{
Name: "test-header",
Value: "test-value",
},
},
},
},
{
Type: "ExtensionRef",
ExtensionRef: &gatewayv1.LocalObjectReference{
Name: "test-policy",
Kind: "NgrokTrafficPolicy",
Group: "ngrok.k8s.ngrok.com",
},
},
{
Type: "URLRewrite",
URLRewrite: &gatewayv1.HTTPURLRewriteFilter{
Hostname: &hostname,
Path: &gatewayv1.HTTPPathModifier{
Type: "ReplacePrefixMatch",
ReplacePrefixMatch: &replacePrefixMatch,
},
},
},
}

expectedPolicy := `{"enabled":true,"inbound":[{"actions":[{"type":"add-headers","config":{"headers":{"test-header":"test-value"}}}],"name":"Inbound HTTPRouteRule 1"},{"actions":[{"type":"deny"}],"name":"t"},{"actions":[{"type":"add-headers","config":{"headers":{"Host":"test-hostname.com"}}}],"name":"Inbound HTTPRouteRule 2"}]}`

policy, err := driver.createEndpointPolicyForGateway(rule, namespace)
Expect(err).To(BeNil())
Expect(policy).ToNot(BeNil())

jsonString, err := json.Marshal(policy)
Expect(err).To(BeNil())
println("policy", string(jsonString))

Expect(len(policy.Inbound) == 3).To(BeTrue())
Expect(len(policy.Outbound)).To(BeZero())
Expect(string(jsonString)).To(Equal(expectedPolicy))
})
})

Describe("When not running concurrently", func() {
It("starts one", func() {
proceed, wait := driver.syncStart(false)
Expand Down

0 comments on commit 9487f4f

Please sign in to comment.