Some of the requested changes

- Rename pathReplacement to rewrite
 - Don't clone request for path rewriting
 - Move out path rewriting from proxyHTTPRequest to ProxyHTTP for better readability
 - Add proxy test for two back-reference replacements
This commit is contained in:
MoofMonkey 2022-10-01 17:45:53 +03:00
parent e5c248ff6f
commit 03129bdab0
8 changed files with 51 additions and 44 deletions

View File

@ -177,11 +177,11 @@ func ValidateUrl(c *cli.Context, allowURLFromArgs bool) (*url.URL, error) {
} }
type UnvalidatedIngressRule struct { type UnvalidatedIngressRule struct {
Hostname string `json:"hostname,omitempty"` Hostname string `json:"hostname,omitempty"`
Path string `json:"path,omitempty"` Path string `json:"path,omitempty"`
PathReplacement string `yaml:"pathReplacement" json:"pathReplacement,omitempty"` Rewrite string `json:"rewrite,omitempty"`
Service string `json:"service,omitempty"` Service string `json:"service,omitempty"`
OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"` OriginRequest OriginRequestConfig `yaml:"originRequest" json:"originRequest"`
} }
// OriginRequestConfig is a set of optional fields that users may set to // OriginRequestConfig is a set of optional fields that users may set to

View File

@ -13,10 +13,10 @@ import (
func TestConfigFileSettings(t *testing.T) { func TestConfigFileSettings(t *testing.T) {
var ( var (
firstIngress = UnvalidatedIngressRule{ firstIngress = UnvalidatedIngressRule{
Hostname: "tunnel1.example.com", Hostname: "tunnel1.example.com",
Path: "/id", Path: "/id",
PathReplacement: "/id", Rewrite: "/id",
Service: "https://localhost:8000", Service: "https://localhost:8000",
} }
secondIngress = UnvalidatedIngressRule{ secondIngress = UnvalidatedIngressRule{
Hostname: "*", Hostname: "*",
@ -46,7 +46,7 @@ originRequest:
ingress: ingress:
- hostname: tunnel1.example.com - hostname: tunnel1.example.com
path: /id path: /id
pathReplacement: /id rewrite: /id
service: https://localhost:8000 service: https://localhost:8000
- hostname: "*" - hostname: "*"
service: https://localhost:8001 service: https://localhost:8001

View File

@ -294,8 +294,8 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
return Ingress{}, errors.Wrapf(err, "Rule #%d has an invalid regex", i+1) return Ingress{}, errors.Wrapf(err, "Rule #%d has an invalid regex", i+1)
} }
pathRegexp = &Regexp{Regexp: regex} pathRegexp = &Regexp{Regexp: regex}
} else if r.PathReplacement != "" { } else if r.Rewrite != "" {
return Ingress{}, fmt.Errorf("rule #%d has a path replacement without a path specified", i+1) return Ingress{}, fmt.Errorf("rule #%d has a rewrite without a path specified", i+1)
} }
rules[i] = Rule{ rules[i] = Rule{
@ -303,7 +303,7 @@ func validateIngress(ingress []config.UnvalidatedIngressRule, defaults OriginReq
punycodeHostname: punycodeHostname, punycodeHostname: punycodeHostname,
Service: service, Service: service,
Path: pathRegexp, Path: pathRegexp,
PathReplacement: r.PathReplacement, Rewrite: r.Rewrite,
Handlers: handlers, Handlers: handlers,
Config: cfg, Config: cfg,
} }

View File

@ -452,16 +452,16 @@ ingress:
- hostname: test.example.com - hostname: test.example.com
service: https://localhost:8000 service: https://localhost:8000
path: ^/api/(.*)$ path: ^/api/(.*)$
pathReplacement: /$1 rewrite: /$1
- service: http_status:404 - service: http_status:404
`}, `},
want: []Rule{ want: []Rule{
{ {
Hostname: "test.example.com", Hostname: "test.example.com",
Service: &httpService{url: localhost8000}, Service: &httpService{url: localhost8000},
Path: &Regexp{Regexp: regexp.MustCompile("^/api/(.*)$")}, Path: &Regexp{Regexp: regexp.MustCompile("^/api/(.*)$")},
PathReplacement: "/$1", Rewrite: "/$1",
Config: defaultConfig, Config: defaultConfig,
}, },
{ {
Service: &fourOhFour, Service: &fourOhFour,
@ -475,7 +475,7 @@ ingress:
ingress: ingress:
- hostname: test.example.com - hostname: test.example.com
service: https://localhost:8000 service: https://localhost:8000
pathReplacement: /$1 rewrite: /$1
- service: http_status:404 - service: http_status:404
`}, `},
wantErr: true, wantErr: true,

View File

@ -20,9 +20,9 @@ type Rule struct {
// Path is an optional regex that can specify path-driven ingress rules. // Path is an optional regex that can specify path-driven ingress rules.
Path *Regexp `json:"path"` Path *Regexp `json:"path"`
// PathReplacement is an optional regexp replacement string for Path, // Rewrite is an optional regexp replacement string for Path,
// that gets sent to the Service // that gets sent to the Service
PathReplacement string `json:"pathReplacement"` Rewrite string `json:"rewrite"`
// A (probably local) address. Requests for a hostname which matches this // A (probably local) address. Requests for a hostname which matches this
// rule's hostname pattern will be proxied to the service running on this // rule's hostname pattern will be proxied to the service running on this

View File

@ -194,25 +194,25 @@ func TestMarshalJSON(t *testing.T) {
{ {
name: "Nil", name: "Nil",
path: nil, path: nil,
expected: `{"hostname":"example.com","path":null,"pathReplacement":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":null,"rewrite":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Nil regex", name: "Nil regex",
path: &Regexp{Regexp: nil}, path: &Regexp{Regexp: nil},
expected: `{"hostname":"example.com","path":null,"pathReplacement":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":null,"rewrite":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Empty", name: "Empty",
path: &Regexp{Regexp: regexp.MustCompile("")}, path: &Regexp{Regexp: regexp.MustCompile("")},
expected: `{"hostname":"example.com","path":"","pathReplacement":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":"","rewrite":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
{ {
name: "Basic", name: "Basic",
path: &Regexp{Regexp: regexp.MustCompile("/echo")}, path: &Regexp{Regexp: regexp.MustCompile("/echo")},
expected: `{"hostname":"example.com","path":"/echo","pathReplacement":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`, expected: `{"hostname":"example.com","path":"/echo","rewrite":"","service":"https://localhost:8000","Handlers":null,"originRequest":{"connectTimeout":30,"tlsTimeout":10,"tcpKeepAlive":30,"noHappyEyeballs":false,"keepAliveTimeout":90,"keepAliveConnections":100,"httpHostHeader":"","originServerName":"","caPool":"","noTLSVerify":false,"disableChunkedEncoding":false,"bastionMode":false,"proxyAddress":"127.0.0.1","proxyPort":0,"proxyType":"","ipRules":null,"http2Origin":false,"access":{"teamName":"","audTag":null}}}`,
want: true, want: true,
}, },
} }

View File

@ -101,6 +101,11 @@ func (p *Proxy) ProxyHTTP(
p.logRequest(req, logFields) p.logRequest(req, logFields)
ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum)) ruleSpan.SetAttributes(attribute.Int("rule-num", ruleNum))
ruleSpan.End() ruleSpan.End()
if rule.Rewrite != "" {
req.URL.Path = rule.Path.ReplaceAllString(req.URL.Path, rule.Rewrite)
}
if err, applied := p.applyIngressMiddleware(rule, req, w); err != nil { if err, applied := p.applyIngressMiddleware(rule, req, w); err != nil {
if applied { if applied {
p.log.Error().Msg(err.Error()) p.log.Error().Msg(err.Error())
@ -117,8 +122,6 @@ func (p *Proxy) ProxyHTTP(
originProxy, originProxy,
isWebsocket, isWebsocket,
rule.Config.DisableChunkedEncoding, rule.Config.DisableChunkedEncoding,
rule.Path,
rule.PathReplacement,
logFields, logFields,
); err != nil { ); err != nil {
rule, srv := ruleField(p.ingressRules, ruleNum) rule, srv := ruleField(p.ingressRules, ruleNum)
@ -191,18 +194,11 @@ func (p *Proxy) proxyHTTPRequest(
httpService ingress.HTTPOriginProxy, httpService ingress.HTTPOriginProxy,
isWebsocket bool, isWebsocket bool,
disableChunkedEncoding bool, disableChunkedEncoding bool,
pathRegexp *ingress.Regexp,
pathReplacement string,
fields logFields, fields logFields,
) error { ) error {
roundTripReq := tr.Request roundTripReq := tr.Request
if isWebsocket || pathReplacement != "" {
roundTripReq = tr.Clone(tr.Request.Context())
}
if pathReplacement != "" {
roundTripReq.URL.Path = pathRegexp.ReplaceAllString(roundTripReq.URL.Path, pathReplacement)
}
if isWebsocket { if isWebsocket {
roundTripReq = tr.Clone(tr.Request.Context())
roundTripReq.Header.Set("Connection", "Upgrade") roundTripReq.Header.Set("Connection", "Upgrade")
roundTripReq.Header.Set("Upgrade", "websocket") roundTripReq.Header.Set("Upgrade", "websocket")
roundTripReq.Header.Set("Sec-Websocket-Version", "13") roundTripReq.Header.Set("Sec-Websocket-Version", "13")

View File

@ -296,16 +296,22 @@ func TestProxyMultipleOrigins(t *testing.T) {
unvalidatedIngress := []config.UnvalidatedIngressRule{ unvalidatedIngress := []config.UnvalidatedIngressRule{
{ {
Hostname: "api.example.com", Hostname: "api.example.com",
Service: api.URL, Service: api.URL,
Path: "^/service1/(.*)$", Path: "^/service1/(.*)$",
PathReplacement: "/$1", Rewrite: "/$1",
}, },
{ {
Hostname: "api.example.com", Hostname: "api.example.com",
Service: api.URL, Service: api.URL,
Path: "^/service2/(.*)$", Path: "^/service2/(.*)$",
PathReplacement: "/route2/$1", Rewrite: "/route2/$1",
},
{
Hostname: "api.example.com",
Service: api.URL,
Path: `^/service3/(.*)/(\w+)$`,
Rewrite: "/$2/$1",
}, },
{ {
Hostname: "api.example.com", Hostname: "api.example.com",
@ -337,6 +343,11 @@ func TestProxyMultipleOrigins(t *testing.T) {
expectedStatus: http.StatusCreated, expectedStatus: http.StatusCreated,
expectedBody: []byte(fmt.Sprintf("/route2%s", hello.HealthRoute)), expectedBody: []byte(fmt.Sprintf("/route2%s", hello.HealthRoute)),
}, },
{
url: fmt.Sprintf("http://api.example.com/service3/func%s", hello.HealthRoute),
expectedStatus: http.StatusCreated,
expectedBody: []byte(fmt.Sprintf("%s/func", hello.HealthRoute)),
},
{ {
url: "http://api.example.com/created", url: "http://api.example.com/created",
expectedStatus: http.StatusCreated, expectedStatus: http.StatusCreated,