Skip to content

Commit 3862cc4

Browse files
committed
Fixes #379
1 parent 912fad7 commit 3862cc4

File tree

8 files changed

+76
-14
lines changed

8 files changed

+76
-14
lines changed

docs/usage.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
2323
|delResHeader |Additional headers that will be deleted in the response before forwarding it to the client. Multiple headers should be separated with comma (`,`). Change the environment variable `SEPARATOR` if comma is to be used for other purposes. Please consult [Delete a header in the response](https://www.haproxy.com/doc/aloha/7.0/haproxy/http_rewriting.html#delete-a-header-in-the-response) for more info.<br>**Example:** `X-Varnish,X-Cache`|
2424
|distribute |Whether to distribute a request to all the instances of the proxy. Used only in the *swarm* mode.<br>**Example:** `true`|false|
2525
|httpsPort |The internal HTTPS port of a service that should be reconfigured. The port is used only in the `swarm` mode. If not specified, the `port` parameter will be used instead.<br>**Example:** `443`|
26-
|ignoreAuthorization|If set to true, the service destination will not require authorization. The parameter must be prefixed with the index of the service destion that should be excluded from authorization.<br>**Default:** `false`<br>**Example:** `true`|
26+
|ignoreAuthorization|If set to true, the service destination will not require authorization. The parameter must be prefixed with the index of the service destination that should be excluded from authorization.<br>**Default:** `false`<br>**Example:** `true`|
2727
|isDefaultBackend |If set to true, the service will be set to the default_backend rule, meaning it will catch all requests not matching any other rules.<br>**Default:** `false`<br>**Example:** `true`|
2828
|port |The internal port of a service that should be reconfigured. The port is used only in the `swarm` mode. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `port.1`, `port.2`, and so on). This field is **mandatory** when running in `swarm` or `service` mode.<br>**Example:** `8080`|
2929
|reqMode |The request mode. The proxy should be able to work with any mode supported by HAProxy. However, actively supported and tested modes are `http`, `tcp`, and `sni`. The `sni` mode implies TCP with an SNI-based routing. The parameter can be prefixed with an index thus allowing definition of multiple modes for a single service (e.g. `http`, `tcp`, and so on).<br>**Default:** value of the `DEFAULT_REQ_MODE` environment variable.<br>**Example:** `tcp`|
@@ -36,7 +36,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
3636
|timeoutServer |The server timeout in seconds.<br>**Default:** `20`<br>**Example:** `60`|
3737
|timeoutTunnel |The tunnel timeout in seconds.<br>**Default:** `3600`<br>**Example:** `3600`|
3838

39-
Multiple destinations for a single service can be specified by adding index as a suffix to `servicePath`, `srcPort`, `port`, `userAgent`, `ignoreAuthorization`, `serviceDomain``allowedMethods`, `deniedMethods`, `denyHttp`, `httpsOnly`, `redirectFromDomain`, `ReqMode`, or `outboundHostname` parameters. In that case, `srcPort` is required.
39+
Multiple destinations for a single service can be specified by adding index as a suffix to `servicePath`, `servicePathExclude`, `srcPort`, `port`, `userAgent`, `ignoreAuthorization`, `serviceDomain``allowedMethods`, `deniedMethods`, `denyHttp`, `httpsOnly`, `redirectFromDomain`, `ReqMode`, or `outboundHostname` parameters. In that case, `srcPort` is required.
4040

4141
### HTTP Mode Query Parameters
4242

@@ -60,6 +60,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
6060
|serviceDomainAlgo|Algorithm that should be applied to domain ACLs. Any ACL works only with one flag: `-i : ignore case during matching of all subsequent patterns`. If not set, the value of the environment variable `SERVICE_DOMAIN_ALGO` will be used instead. If defaults to `hdr_beg(host)`<br>**Examples:**<br>`hdr(host)`: matches only if domain is the same as `serviceDomain`<br>`hdr_dom(host)`: matches the specified `serviceDomain` and any subdomain (a string either isolated or delimited by dots). **Example:** if `hdr_dom(host)` contains `www.ecme.com` and `serviceDomain` equals `ecme.com` the rule will be passed.<br>`req.ssl_sni`: matches Server Name TLS extension|
6161
|serviceHeader|Headers used to filter requests. If set, the proxy will allow access only to requests that contain specified headers. A header consists of a key and value separated with colon (e.g. `X-Version:3`). Multiple headers can be separated with comma (e.g. `X-Version:3,name:viktor`). The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `serviceHeader.1`, `serviceHeader.2`, and so on). <br>**Example:** `X-Version:3,name:viktor`|
6262
|servicePath |The URL path of the service. Multiple values should be separated with comma (`,`). The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `servicePath.1`, `servicePath.2`, and so on). This parameter **is mandatory** unless `serviceDomain` is specified.<br>**Example:** `/api/v1/books`|
63+
|servicePathExclude|The URL path that should be excluded from the rules. Multiple values should be separated with comma (`,`). The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `servicePathExclude.1`, `servicePathExclude.2`, and so on).<br>**Example:** `/metrics`|
6364
|sessionType |Determines the type of sticky sessions. If set to `sticky-server`, session cookie will be set by the proxy. Any other value means that sticky sessions are not used and load balancing is performed by Docker's Overlay network.<br>**Example:** `sticky-server`|
6465
|sslVerifyNone|If set to true, backend server certificates are not verified. This flag should be set for SSL enabled backend services.<br>**Example:** `true`<br>**Default Value:** `false`|
6566
|templateBePath|The path to the template representing a snippet of the backend configuration. If specified, the backend template will be loaded from the specified file. See the [Templates](#templates) section for more info.<br>**Example:** `/tmpl/be.tmpl`|
@@ -70,7 +71,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
7071
|usersPassEncrypted|Indicates whether passwords provided by `users` or `usersSecret` contain encrypted data. Passwords can be encrypted with the command `mkpasswd -m sha-512 password1`.<br>**Example:** `true`<br>**Default Value:** `false`|
7172
|verifyClientSsl|Whether to verify client SSL and, if it is not valid, deny request and return 403 Forbidden status code. SSL is validated against the `ca-file` specified through the environment variable `CA_FILE`.<br>**Example:** true<br>**Default Value:** `false`|
7273

73-
Multiple destinations for a single service can be specified by adding index as a suffix to `servicePath`, `srcPort`, `port`, `userAgent`, `ignoreAuthorization`, `serviceDomain`, `allowedMethods`, `deniedMethods`, `denyHttp`, `httpsOnly`, `redirectFromDomain`, `ReqMode`, or `outboundHostname` parameters. In that case, `srcPort` is required.
74+
Multiple destinations for a single service can be specified by adding index as a suffix to `servicePath`, `servicePathExclude`, `srcPort`, `port`, `userAgent`, `ignoreAuthorization`, `serviceDomain`, `allowedMethods`, `deniedMethods`, `denyHttp`, `httpsOnly`, `redirectFromDomain`, `ReqMode`, or `outboundHostname` parameters. In that case, `srcPort` is required.
7475

7576
### TCP Mode HTTP Query Parameters
7677

@@ -132,6 +133,7 @@ The map between the HTTP query parameters and environment variables is as follow
132133
|serviceDomain |SERVICE_DOMAIN |
133134
|serviceName |SERVICE_NAME |
134135
|servicePath |SERVICE_PATH |
136+
|servicePathExclude |SERVICE_PATH_EXCLUDE |
135137
|setReqHeader |SET_REQ_HEADER |
136138
|setResHeader |SET_RES_HEADER |
137139
|srcPort |SRC_PORT |

integration_tests/integration_swarm_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package integration_test
22

33
import (
44
"fmt"
5-
"github.com/stretchr/testify/suite"
65
"io/ioutil"
76
"log"
87
"net/http"
@@ -11,6 +10,8 @@ import (
1110
"strings"
1211
"testing"
1312
"time"
13+
14+
"github.com/stretchr/testify/suite"
1415
)
1516

1617
// Setup
@@ -73,6 +74,7 @@ func TestGeneralIntegrationSwarmTestSuite(t *testing.T) {
7374
suite.Run(t, s)
7475

7576
s.removeServices("go-demo-api", "go-demo-db", "proxy", "proxy-env", "redis")
77+
exec.Command("/bin/sh", "-c", "docker network rm proxy").Output()
7678
exec.Command("/bin/sh", "-c", "docker system prune -f").Output()
7779
}
7880

@@ -89,6 +91,17 @@ func (s IntegrationSwarmTestSuite) Test_Reconfigure() {
8991
}
9092
}
9193

94+
func (s IntegrationSwarmTestSuite) Test_ExcludePaths() {
95+
s.reconfigureGoDemo("&servicePathExclude=/demo/hello")
96+
97+
resp, err := s.sendHelloRequest()
98+
99+
s.NoError(err)
100+
if resp != nil {
101+
s.Equal(503, resp.StatusCode, s.getProxyConf(""))
102+
}
103+
}
104+
92105
func (s IntegrationSwarmTestSuite) Test_Domain() {
93106
s.reconfigureGoDemo("&serviceDomain=my-domain.com")
94107

main.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package main
22

33
import (
4-
"./logging"
54
"log"
65
"os"
76
"strings"
8-
// "sync"
9-
// "github.com/hashicorp/go-reap"
7+
8+
"./logging"
109
)
1110

1211
func main() {

proxy/ha_proxy_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,39 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_AddsContentFrontEnd() {
740740
s.Equal(expectedData, actualData)
741741
}
742742

743+
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_AddsServicePathExclude() {
744+
var actualData string
745+
tmpl := s.TemplateContent
746+
expectedData := fmt.Sprintf(
747+
`%s
748+
acl url_my-service-11111_0 path_beg /path-1
749+
acl url_exclude_my-service-11111_0 path_beg /path-2 path_beg /path-3
750+
acl http_my-service-1 dst_port 80
751+
acl https_my-service-1 dst_port 443
752+
use_backend my-service-1-be1111_0 if url_my-service-11111_0 !url_exclude_my-service-11111_0 http_my-service-1
753+
use_backend https-my-service-1-be1111_0 if url_my-service-11111_0 !url_exclude_my-service-11111_0 https_my-service-1%s`,
754+
tmpl,
755+
s.ServicesContent,
756+
)
757+
writeFile = func(filename string, data []byte, perm os.FileMode) error {
758+
actualData = string(data)
759+
return nil
760+
}
761+
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
762+
dataInstance.Services["my-service-1"] = Service{
763+
ServiceName: "my-service-1",
764+
PathType: "path_beg",
765+
HttpsPort: 2222,
766+
ServiceDest: []ServiceDest{
767+
{Port: "1111", ServicePath: []string{"/path-1"}, ServicePathExclude: []string{"/path-2", "/path-3"}},
768+
},
769+
}
770+
771+
p.CreateConfigFromTemplates()
772+
773+
s.Equal(expectedData, actualData)
774+
}
775+
743776
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_AddsSortedContentFrontEnd() {
744777
var actualData string
745778
tmpl := s.TemplateContent

proxy/template.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ func getFrontTemplate(s Service) string {
2121
{{- end}}
2222
{{- if ne .Port ""}}
2323
acl url_{{$.AclName}}{{.Port}}_{{.Index}}{{range .ServicePath}} {{if eq $.PathType ""}}path_beg{{end}}{{if ne $.PathType ""}}{{$.PathType}}{{end}} {{.}}{{end}}{{.SrcPortAcl}}
24+
{{- end}}
25+
{{- if .ServicePathExclude}}
26+
acl url_exclude_{{$.AclName}}{{.Port}}_{{.Index}}{{range .ServicePathExclude}} {{if eq $.PathType ""}}path_beg{{end}}{{if ne $.PathType ""}}{{$.PathType}}{{end}} {{.}}{{end}}{{.SrcPortAcl}}
2427
{{- end}}
2528
{{- $length := len .UserAgent.Value}}{{if gt $length 0}}
2629
acl user_agent_{{$.AclName}}_{{.UserAgent.AclName}}_{{.Index}} hdr_sub(User-Agent) -i{{range .UserAgent.Value}} {{.}}{{end}}
@@ -58,9 +61,9 @@ func getFrontTemplate(s Service) string {
5861
{{- end}}
5962
{{- range $sd := .ServiceDest}}
6063
{{- if eq .ReqMode "http"}}{{- if ne .Port ""}}
61-
use_backend {{$.ServiceName}}-be{{.Port}}_{{.Index}} if url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{if .ServiceHeader}}{{resetIndex}}{{range $key, $value := .ServiceHeader}} hdr_{{$.AclName}}{{$sd.Port}}_{{incIndex}}{{end}}{{end}}{{.SrcPortAclName}}
64+
use_backend {{$.ServiceName}}-be{{.Port}}_{{.Index}} if url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServicePathExclude}} !url_exclude_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{if .ServiceHeader}}{{resetIndex}}{{range $key, $value := .ServiceHeader}} hdr_{{$.AclName}}{{$sd.Port}}_{{incIndex}}{{end}}{{end}}{{.SrcPortAclName}}
6265
{{- if gt $.HttpsPort 0 }} http_{{$.ServiceName}}
63-
use_backend https-{{$.ServiceName}}-be{{.Port}}_{{.Index}} if url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}} https_{{$.ServiceName}}
66+
use_backend https-{{$.ServiceName}}-be{{.Port}}_{{.Index}} if url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServicePathExclude}} !url_exclude_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}} https_{{$.ServiceName}}
6467
{{- end}}
6568
{{- $length := len .UserAgent.Value}}{{if gt $length 0}} user_agent_{{$.AclName}}_{{.UserAgent.AclName}}_{{.Index}}{{end}}
6669
{{- if $.IsDefaultBackend}}

proxy/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ type ServiceDest struct {
4343
ServiceHeader map[string]string
4444
// The URL path of the service.
4545
ServicePath []string
46+
// The URL path that should be excluded from the rules.
47+
ServicePathExclude []string
4648
// The source (entry) port of a service.
4749
// Useful only when specifying multiple destinations of a single service.
4850
SrcPort int
@@ -408,6 +410,7 @@ func getServiceDest(sr *Service, provider ServiceParameterProvider, index int) S
408410
ServiceDomain: getSliceFromString(provider, fmt.Sprintf("serviceDomain%s", suffix)),
409411
ServiceHeader: header,
410412
ServicePath: getSliceFromString(provider, fmt.Sprintf("servicePath%s", suffix)),
413+
ServicePathExclude: getSliceFromString(provider, fmt.Sprintf("servicePathExclude%s", suffix)),
411414
SrcPort: srcPort,
412415
VerifyClientSsl: getBoolParam(provider, fmt.Sprintf("verifyClientSsl%s", suffix)),
413416
UserAgent: userAgent,

proxy/types_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesServiceDomainToIndexed
237237
ServiceDomain: []string{"domain1", "domain2"},
238238
ServiceHeader: map[string]string{},
239239
ServicePath: []string{"/"},
240+
ServicePathExclude: []string{},
240241
}},
241242
ServiceName: "serviceName",
242243
}
@@ -265,6 +266,7 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_UsesNonIndexedOutboundHostn
265266
ServiceDomain: []string{},
266267
ServiceHeader: map[string]string{},
267268
ServicePath: []string{"/"},
269+
ServicePathExclude: []string{},
268270
}},
269271
ServiceName: "serviceName",
270272
}
@@ -293,6 +295,7 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesHttpsOnlyToIndexedEntr
293295
ServiceDomain: []string{},
294296
ServiceHeader: map[string]string{},
295297
ServicePath: []string{"/"},
298+
ServicePathExclude: []string{},
296299
}},
297300
ServiceName: "serviceName",
298301
}
@@ -322,6 +325,7 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_UsesHttpsOnlyFromEnvVar() {
322325
ServiceDomain: []string{},
323326
ServiceHeader: map[string]string{},
324327
ServicePath: []string{"/"},
328+
ServicePathExclude: []string{},
325329
}},
326330
ServiceName: "serviceName",
327331
}
@@ -421,6 +425,7 @@ func (s *TypesTestSuite) getExpectedService() Service {
421425
ServiceDomain: []string{"domain1", "domain2"},
422426
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
423427
ServicePath: []string{"/"},
428+
ServicePathExclude: []string{},
424429
ReqMode: "reqMode",
425430
UserAgent: UserAgent{Value: []string{"agent-1", "agent-2/replace-with_"}, AclName: "agent_1_agent_2_replace_with_"},
426431
VerifyClientSsl: true,

server/server_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
package server
22

33
import (
4-
"../actions"
5-
"../proxy"
64
"encoding/json"
75
"fmt"
8-
"github.com/stretchr/testify/mock"
9-
"github.com/stretchr/testify/suite"
106
"net/http"
117
"os"
128
"strconv"
139
"strings"
1410
"testing"
11+
12+
"../actions"
13+
"../proxy"
14+
"github.com/stretchr/testify/mock"
15+
"github.com/stretchr/testify/suite"
1516
)
1617

1718
type ServerTestSuite struct {
@@ -544,6 +545,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
544545
ServiceDomain: []string{"domain1", "domain2"},
545546
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
546547
ServicePath: []string{"/"},
548+
ServicePathExclude: []string{"/excluded-path"},
547549
}},
548550
ServiceDomainAlgo: "hdr_dom",
549551
ServiceName: "serviceName",
@@ -558,7 +560,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
558560
{Username: "user2", Password: "pass2", PassEncrypted: true}},
559561
}
560562
addr := fmt.Sprintf(
561-
"%s?serviceName=%s&users=%s&usersPassEncrypted=%t&aclName=%s&serviceCert=%s&outboundHostname=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&redirectFromDomain=%s&distribute=%t&sslVerifyNone=%t&serviceDomainAlgo=%s&addReqHeader=%s&addResHeader=%s&setReqHeader=%s&setResHeader=%s&delReqHeader=%s&delResHeader=%s&servicePath=/&port=1234&connectionMode=%s&serviceHeader=X-Version:3,name:Viktor&allowedMethods=GET,DELETE&deniedMethods=PUT,POST&compressionAlgo=%s&compressionType=%s",
563+
"%s?serviceName=%s&users=%s&usersPassEncrypted=%t&aclName=%s&serviceCert=%s&outboundHostname=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&redirectFromDomain=%s&distribute=%t&sslVerifyNone=%t&serviceDomainAlgo=%s&addReqHeader=%s&addResHeader=%s&setReqHeader=%s&setResHeader=%s&delReqHeader=%s&delResHeader=%s&servicePath=/&servicePathExclude=%s&port=1234&connectionMode=%s&serviceHeader=X-Version:3,name:Viktor&allowedMethods=GET,DELETE&deniedMethods=PUT,POST&compressionAlgo=%s&compressionType=%s",
562564
s.BaseUrl,
563565
expected.ServiceName,
564566
"user1:pass1,user2:pass2",
@@ -590,6 +592,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
590592
strings.Join(expected.SetResHeader, ","),
591593
strings.Join(expected.DelReqHeader, ","),
592594
strings.Join(expected.DelResHeader, ","),
595+
strings.Join(expected.ServiceDest[0].ServicePathExclude, ","),
593596
expected.ConnectionMode,
594597
expected.CompressionAlgo,
595598
expected.CompressionType,
@@ -633,6 +636,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_SetsServicePathToSlash_WhenDoma
633636
ServiceDomain: []string{"domain1", "domain2"},
634637
ServiceHeader: map[string]string{},
635638
ServicePath: []string{"/"},
639+
ServicePathExclude: []string{},
636640
Index: 0,
637641
},
638642
},

0 commit comments

Comments
 (0)