diff --git a/pkg/api/rule.go b/pkg/api/rule.go index 2399023..31c0ad0 100644 --- a/pkg/api/rule.go +++ b/pkg/api/rule.go @@ -31,12 +31,17 @@ type RuleActions struct { DirectResponse RuleActionsDirectResponse `json:"directResponse" yaml:"directResponse"` } type RuleActionsProxy struct { - Hostname string `json:"hostname"` - Port int64 `json:"port"` - HealthCheck RuleActionHealthCheck `json:"healthCheck" yaml:"healthCheck"` - EnableWebsockets bool `json:"enableWebsockets" yaml:"enableWebsockets"` + Hostname string `json:"hostname"` + Port int64 `json:"port"` + HealthCheck RuleActionHealthCheck `json:"healthCheck" yaml:"healthCheck"` + EnableWebsockets bool `json:"enableWebsockets" yaml:"enableWebsockets"` + PrefixRewrite string `json:"prefixRewrite" yaml:"prefixRewrite"` + RegexRewrite RuleActionsRegexRewrite `json:"regexRewrite" yaml:"regexRewrite"` +} +type RuleActionsRegexRewrite struct { + Regex string `json:"regex" yaml:"regex"` + Substitution string `json:"substitution" yaml:"substitution"` } - type RuleActionHealthCheck struct { HTTPHealthCheck HTTPHealthCheck `json:"httpHealthCheck" yaml:"httpHealthCheck"` Timeout string `json:"timeout" yaml:"timeout"` diff --git a/pkg/envoy/listener.go b/pkg/envoy/listener.go index 4bb8043..4ffc04d 100644 --- a/pkg/envoy/listener.go +++ b/pkg/envoy/listener.go @@ -176,11 +176,12 @@ func (l *Listener) updateListenerWithChallenge(cache *WorkQueueCache, challenge return nil } -func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, targetPrefix, clusterName, virtualHostName string, methods []string, matchType string, directResponse DirectResponse, enableWebsocket bool) *route.VirtualHost { +func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, targetPrefix, clusterName, virtualHostName string, methods []string, matchType string, directResponse DirectResponse, enableWebsocket bool, prefixRewrite string, regexRewrite RegexRewrite) *route.VirtualHost { var hostRewriteSpecifier *route.RouteAction_HostRewriteLiteral var routes []*route.Route var routeAction *route.Route_Route var upgradeConfigs []*route.RouteAction_UpgradeConfig + var envoyRegexRewrite *matcher.RegexMatchAndSubstitute if hostname == "" { hostname = "*" } @@ -199,6 +200,15 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target }, } } + if regexRewrite.Regex != "" { + envoyRegexRewrite = &matcher.RegexMatchAndSubstitute{ + Pattern: &matcher.RegexMatcher{ + EngineType: &matcher.RegexMatcher_GoogleRe2{}, + Regex: regexRewrite.Regex, + }, + Substitution: regexRewrite.Substitution, + } + } routeAction = &route.Route_Route{ Route: &route.RouteAction{ HostRewriteSpecifier: hostRewriteSpecifier, @@ -206,6 +216,8 @@ func (l *Listener) getVirtualHost(listenerName, hostname, targetHostname, target Cluster: clusterName, }, UpgradeConfigs: upgradeConfigs, + PrefixRewrite: prefixRewrite, + RegexRewrite: envoyRegexRewrite, }, } } else { @@ -407,7 +419,7 @@ func (l *Listener) updateListener(cache *WorkQueueCache, params ListenerParams, } // create new virtualhost - v := l.getVirtualHost(ll.Name, params.Conditions.Hostname, params.TargetHostname, targetPrefix, params.Name, virtualHostname, params.Conditions.Methods, matchType, params.DirectResponse, params.EnableWebSockets) + v := l.getVirtualHost(ll.Name, params.Conditions.Hostname, params.TargetHostname, targetPrefix, params.Name, virtualHostname, params.Conditions.Methods, matchType, params.DirectResponse, params.EnableWebSockets, params.PrefixRewrite, params.RegexRewrite) // check if we need to overwrite the virtualhost virtualHostKey := -1 @@ -635,7 +647,7 @@ func (l *Listener) DeleteRoute(cache *WorkQueueCache, params ListenerParams, par return err } - v := l.getVirtualHost(ll.Name, params.Conditions.Hostname, params.TargetHostname, targetPrefix, params.Name, virtualHostname, params.Conditions.Methods, matchType, params.DirectResponse, params.EnableWebSockets) + v := l.getVirtualHost(ll.Name, params.Conditions.Hostname, params.TargetHostname, targetPrefix, params.Name, virtualHostname, params.Conditions.Methods, matchType, params.DirectResponse, params.EnableWebSockets, params.PrefixRewrite, params.RegexRewrite) virtualHostKey := -1 for k, curVirtualHost := range routeSpecifier.RouteConfig.VirtualHosts { diff --git a/pkg/envoy/testdata/test-prefixrewrite.yaml b/pkg/envoy/testdata/test-prefixrewrite.yaml new file mode 100644 index 0000000..c4ed83c --- /dev/null +++ b/pkg/envoy/testdata/test-prefixrewrite.yaml @@ -0,0 +1,13 @@ +api: proxy.in4it.io/v1 +kind: rule +metadata: + name: test-prefixrewrite +spec: + conditions: + - hostname: test-prefixrewrite.example.com + path: /test-prefixrewrite + actions: + - proxy: + hostname: target-example.com + port: 443 + prefixRewrite: /addthis diff --git a/pkg/envoy/testdata/test-regexrewrite.yaml b/pkg/envoy/testdata/test-regexrewrite.yaml new file mode 100644 index 0000000..dbfa0bb --- /dev/null +++ b/pkg/envoy/testdata/test-regexrewrite.yaml @@ -0,0 +1,15 @@ +api: proxy.in4it.io/v1 +kind: rule +metadata: + name: test-prefixrewrite +spec: + conditions: + - hostname: test-prefixrewrite.example.com + path: /test-prefixrewrite + actions: + - proxy: + hostname: target-example.com + port: 443 + regexRewrite: + regex: "^/service/([^/]+)(/.*)$" + substitution: "\\2/instance/\\1" diff --git a/pkg/envoy/types.go b/pkg/envoy/types.go index 51e73a4..789b0d9 100644 --- a/pkg/envoy/types.go +++ b/pkg/envoy/types.go @@ -53,6 +53,8 @@ type ListenerParams struct { Protocol string TargetHostname string EnableWebSockets bool + PrefixRewrite string + RegexRewrite RegexRewrite Conditions Conditions Auth Auth Authz Authz @@ -105,6 +107,13 @@ type ActionProxy struct { Port int64 HealthCheck HealthCheck EnableWebsockets bool + PrefixRewrite string + RegexRewrite RegexRewrite +} + +type RegexRewrite struct { + Regex string + Substitution string } type HealthCheck struct { diff --git a/pkg/envoy/xds.go b/pkg/envoy/xds.go index 8d3625c..28edc90 100644 --- a/pkg/envoy/xds.go +++ b/pkg/envoy/xds.go @@ -504,6 +504,13 @@ func (x *XDS) getAction(ruleName string, actions []pkgApi.RuleActions) Action { if ruleAction.Proxy.EnableWebsockets { action.Proxy.EnableWebsockets = ruleAction.Proxy.EnableWebsockets } + if ruleAction.Proxy.PrefixRewrite != "" { + action.Proxy.PrefixRewrite = ruleAction.Proxy.PrefixRewrite + } + if ruleAction.Proxy.RegexRewrite.Regex != "" { + action.Proxy.RegexRewrite.Regex = ruleAction.Proxy.RegexRewrite.Regex + action.Proxy.RegexRewrite.Substitution = ruleAction.Proxy.RegexRewrite.Substitution + } } else if ruleAction.DirectResponse.Status > 0 { action.Type = "directResponse" action.RuleName = ruleName @@ -520,6 +527,8 @@ func (x *XDS) getListenerParams(action Action, condition pkgApi.RuleConditions) Name: action.RuleName, TargetHostname: action.Proxy.TargetHostname, EnableWebSockets: action.Proxy.EnableWebsockets, + PrefixRewrite: action.Proxy.PrefixRewrite, + RegexRewrite: action.Proxy.RegexRewrite, Conditions: Conditions{ Hostname: condition.Hostname, Prefix: condition.Prefix, diff --git a/pkg/envoy/xds_test.go b/pkg/envoy/xds_test.go index 832d316..195f80f 100644 --- a/pkg/envoy/xds_test.go +++ b/pkg/envoy/xds_test.go @@ -10,6 +10,7 @@ import ( listenerAPI "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3" route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" als "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/grpc/v3" + matcher "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" "github.com/envoyproxy/go-control-plane/pkg/wellknown" "github.com/golang/protobuf/ptypes" "github.com/in4it/roxprox/pkg/storage" @@ -596,6 +597,115 @@ func TestClusterWithWebsockets(t *testing.T) { } } } +func TestClusterWithPathRewrite(t *testing.T) { + logger.SetLogLevel(loggo.DEBUG) + s, err := initStorage() + if err != nil { + t.Errorf("Couldn't initialize storage: %s", err) + return + } + x := NewXDS(s, "", "") + ObjectFileNames := []string{"test-prefixrewrite.yaml"} + for _, filename := range ObjectFileNames { + newItems, err := x.putObject(filename) + if err != nil { + t.Errorf("PutObject failed: %s", err) + return + } + _, err = x.workQueue.Submit(newItems) + if err != nil { + t.Errorf("WorkQueue error: %s", err) + return + } + } + + var prefixRewrite string + + for _, listener := range x.workQueue.cache.listeners { + ll := listener.(*listenerAPI.Listener) + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("Error while getting listener: %s", err) + return + } + routeSpecifier, err := getListenerRouteSpecifier(manager) + if err != nil { + t.Errorf("Error while getting routes: %s", err) + return + } + for _, virtualHost := range routeSpecifier.RouteConfig.VirtualHosts { + for _, virtualHostRoute := range virtualHost.Routes { + if virtualHostRoute.Action != nil { + switch reflect.TypeOf(virtualHostRoute.Action).String() { + case "*envoy_config_route_v3.Route_Route": + prefixRewrite = virtualHostRoute.Action.(*route.Route_Route).Route.GetPrefixRewrite() + } + } + } + } + if prefixRewrite != "/addthis" { + t.Errorf("Prefix rewrite not found") + return + } + } +} + +func TestClusterWithRegexRewrite(t *testing.T) { + logger.SetLogLevel(loggo.DEBUG) + s, err := initStorage() + if err != nil { + t.Errorf("Couldn't initialize storage: %s", err) + return + } + x := NewXDS(s, "", "") + ObjectFileNames := []string{"test-regexrewrite.yaml"} + for _, filename := range ObjectFileNames { + newItems, err := x.putObject(filename) + if err != nil { + t.Errorf("PutObject failed: %s", err) + return + } + _, err = x.workQueue.Submit(newItems) + if err != nil { + t.Errorf("WorkQueue error: %s", err) + return + } + } + + var regexRewrite *matcher.RegexMatchAndSubstitute + + for _, listener := range x.workQueue.cache.listeners { + ll := listener.(*listenerAPI.Listener) + manager, err := getListenerHTTPConnectionManager(ll) + if err != nil { + t.Errorf("Error while getting listener: %s", err) + return + } + routeSpecifier, err := getListenerRouteSpecifier(manager) + if err != nil { + t.Errorf("Error while getting routes: %s", err) + return + } + for _, virtualHost := range routeSpecifier.RouteConfig.VirtualHosts { + for _, virtualHostRoute := range virtualHost.Routes { + if virtualHostRoute.Action != nil { + switch reflect.TypeOf(virtualHostRoute.Action).String() { + case "*envoy_config_route_v3.Route_Route": + regexRewrite = virtualHostRoute.Action.(*route.Route_Route).Route.GetRegexRewrite() + } + } + } + } + if regexRewrite.Pattern.Regex != "^/service/([^/]+)(/.*)$" { + t.Errorf("Regex pattern in regex rewrite not found") + return + } + if regexRewrite.GetSubstitution() != "\\2/instance/\\1" { + t.Errorf("Regex substitution in regex rewrite not found") + return + } + } +} func TestCompressionObject(t *testing.T) { logger.SetLogLevel(loggo.DEBUG) s, err := initStorage()