Skip to content

Commit 5bc9739

Browse files
committed
Fixes #340
1 parent 1ad9856 commit 5bc9739

File tree

10 files changed

+90
-20
lines changed

10 files changed

+90
-20
lines changed

actions/fetch_test.go

-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ func (s *FetchTestSuite) Test_ReloadConfig_SendsARequestToSwarmListener_WhenList
160160
reconfigureMock.AssertNumberOfCalls(s.T(), "Execute", 1)
161161
proxyMock.AssertCalled(s.T(), "Reload")
162162
proxyMock.AssertCalled(s.T(), "CreateConfigFromTemplates")
163-
164163
}
165164

166165
func (s *FetchTestSuite) Test_ReloadConfig_ReturnsError_WhenSwarmListenerReturnsWrongData() {

actions/reconfigure_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@ func (s ReconfigureTestSuite) Test_Execute_WritesBeTemplateWithRedirectToHttps_W
510510
`
511511
backend %s-be%s_0
512512
mode http
513-
redirect scheme https if !{ ssl_fc }
513+
http-request redirect scheme https if !{ ssl_fc }
514514
server %s %s:%s`,
515515
s.ServiceName,
516516
s.reconfigure.ServiceDest[0].Port,

docs/usage.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
3737
|timeoutTunnel |The tunnel timeout in seconds.<br>**Default:** `3600`<br>**Example:** `3600`|
3838
|xForwardedProto|Whether to add "X-Forwarded-Proto https" header.<br>**Default:** `false`<br>**Example:** `true`|
3939

40-
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`, or `ReqMode` parameters. In that case, `srcPort` is required.
40+
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`, or `ReqMode` parameters. In that case, `srcPort` is required.
4141

4242
### HTTP Mode Query Parameters
4343

@@ -51,9 +51,10 @@ The following query parameters can be used only when `reqMode` is set to `http`
5151
|httpsOnly |If set to true, HTTP requests to the service will be redirected to HTTPS. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `httpsOnly.1`, `httpsOnly.2`, and so on).<br>**Example:** `true`<br>**Default Value:** `false`|
5252
|outboundHostname|The hostname where the service is running, for instance on a separate swarm. If specified, the proxy will dispatch requests to that domain.<br>**Example:** `ecme.com`|
5353
|pathType |The ACL derivative. Defaults to *path_beg*. See [HAProxy path](https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.6-path) for more info.<br>**Example:** `path_beg`|
54+
|redirectFromDomain|If a request is sent to one of the domains in this list, it will be redirected to one of the values of the `ServiceDomain`. Multiple domains can be separated with comma (e.g. `acme.com,something.acme.com`). The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service.<br>**Example:** `acme.com,something.acme.com`|
5455
|redirectWhenHttpProto|Whether to redirect to https when X-Forwarded-Proto is set and the request is made over an HTTP port.<br>**Example:** `true`<br>**Default Value:** `false`|
5556
|serviceCert |Content of the PEM-encoded certificate to be used by the proxy when serving traffic over SSL.|
56-
|serviceDomain |The domain of the service. If set, the proxy will allow access only to requests coming to that domain. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `serviceDomain.1`, `serviceDomain.2`, and so on). Asterisk sign can be placed to begining of value and in this case **serviceDomainAlgo** parameter will be **replaced** to `hdr_end(host)`. This parameter is **mandatory** if `servicePath` is not specified.<br>**Example:** `ecme.com`|
57+
|serviceDomain |The domain of the service. If set, the proxy will allow access only to requests coming to that domain. Multiple domains can be separated with comma (e.g. `acme.com,something.else.com`). The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `serviceDomain.1`, `serviceDomain.2`, and so on). Asterisk sign can be placed to beginning of value and in this case **serviceDomainAlgo** parameter will be **replaced** to `hdr_end(host)`. This parameter is **mandatory** if `servicePath` is not specified.<br>**Example:** `ecme.com`|
5758
|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(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|
5859
|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`|
5960
|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`|
@@ -67,7 +68,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
6768
|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`|
6869
|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`|
6970

70-
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`, or `ReqMode` parameters. In that case, `srcPort` is required.
71+
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`, or `ReqMode` parameters. In that case, `srcPort` is required.
7172

7273
### TCP Mode HTTP Query Parameters
7374

@@ -121,6 +122,7 @@ The map between the HTTP query parameters and environment variables is as follow
121122
|outboundHostname |OUTBOUND_HOSTNAME |
122123
|pathType |PATH_TYPE |
123124
|port |PORT |
125+
|redirectFromDomain |REDIRECT_FROM_DOMAIN |
124126
|redirectWhenHttpProto|REDIRECT_WHEN_HTTP_PROTO|
125127
|reqMode |REQ_MODE |
126128
|reqPathReplace |REQ_PATH_REPLACE |

integration_tests/integration_swarm_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,23 @@ func (s IntegrationSwarmTestSuite) Test_Domain() {
105105
}
106106
}
107107

108+
func (s IntegrationSwarmTestSuite) Test_RedirectFromDomain() {
109+
params := fmt.Sprintf("&serviceDomain=%s&redirectFromDomain=my-other-domain.com", s.hostIP)
110+
s.reconfigureGoDemo(params)
111+
112+
client := new(http.Client)
113+
url := fmt.Sprintf("http://%s/demo/hello", s.hostIP)
114+
req, err := http.NewRequest("GET", url, nil)
115+
s.NoError(err)
116+
req.Host = "my-other-domain.com"
117+
resp, err := client.Do(req)
118+
119+
s.NoError(err, s.getProxyConf(""))
120+
if resp != nil {
121+
s.Equal(200, resp.StatusCode, s.getProxyConf(""))
122+
}
123+
}
124+
108125
func (s IntegrationSwarmTestSuite) Test_Config() {
109126
s.reconfigureGoDemo("")
110127

proxy/ha_proxy_test.go

+38-1
Original file line numberDiff line numberDiff line change
@@ -1502,7 +1502,7 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed
15021502
acl url_my-service1111_0 path_beg /path
15031503
acl domain_my-service1111_0 hdr(host) -i my-domain.com
15041504
acl is_my-service_http hdr(X-Forwarded-Proto) http
1505-
redirect scheme https if is_my-service_http url_my-service1111_0 domain_my-service1111_0
1505+
http-request redirect scheme https if is_my-service_http url_my-service1111_0 domain_my-service1111_0
15061506
use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0%s`,
15071507
tmpl,
15081508
s.ServicesContent,
@@ -1527,6 +1527,43 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed
15271527
s.Equal(expectedData, actualData)
15281528
}
15291529

1530+
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRedirectFromDomainIsSet() {
1531+
var actualData string
1532+
tmpl := s.TemplateContent
1533+
expectedData := fmt.Sprintf(
1534+
`%s
1535+
acl url_my-service1111_0 path_beg /path
1536+
acl domain_my-service1111_0 hdr(host) -i my-domain-1.com my-domain-2.com
1537+
http-request redirect code 301 prefix http://my-domain-1.com if { hdr(host) -i my-other-domain-1.com }
1538+
http-request redirect code 301 prefix http://my-domain-1.com if { hdr(host) -i my-other-domain-2.com }
1539+
use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0%s`,
1540+
tmpl,
1541+
s.ServicesContent,
1542+
)
1543+
writeFile = func(filename string, data []byte, perm os.FileMode) error {
1544+
actualData = string(data)
1545+
return nil
1546+
}
1547+
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
1548+
dataInstance.Services["my-service"] = Service{
1549+
ServiceName: "my-service",
1550+
PathType: "path_beg",
1551+
AclName: "my-service",
1552+
ServiceDest: []ServiceDest{
1553+
{
1554+
Port: "1111",
1555+
ServicePath: []string{"/path"},
1556+
ServiceDomain: []string{"my-domain-1.com", "my-domain-2.com"},
1557+
RedirectFromDomain: []string{"my-other-domain-1.com", "my-other-domain-2.com"},
1558+
},
1559+
},
1560+
}
1561+
1562+
p.CreateConfigFromTemplates()
1563+
1564+
s.Equal(expectedData, actualData)
1565+
}
1566+
15301567
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_UsesServiceHeader() {
15311568
var actualData string
15321569
tmpl := s.TemplateContent

proxy/template.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,17 @@ func getFrontTemplate(s Service) string {
3434
acl http_{{.ServiceName}} src_port 80
3535
acl https_{{.ServiceName}} src_port 443
3636
{{- end}}
37+
{{- range $sd := .ServiceDest}}
38+
{{- range $rd := $sd.RedirectFromDomain}}
39+
http-request redirect code 301 prefix http://{{index $sd.ServiceDomain 0}} if { hdr(host) -i {{$rd}} }
40+
{{- end}}
41+
{{- end}}
3742
{{- if $.RedirectWhenHttpProto}}
3843
{{- range .ServiceDest}}
3944
{{- if eq .ReqMode "http"}}
4045
{{- if ne .Port ""}}
4146
acl is_{{$.AclName}}_http hdr(X-Forwarded-Proto) http
42-
redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}}
47+
http-request redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}}
4348
{{- end}}
4449
{{- end}}
4550
{{- end}}
@@ -147,7 +152,7 @@ backend {{$.ServiceName}}-be{{.Port}}_{{.Index}}
147152
http-request deny if !{ ssl_fc }
148153
{{- end}}
149154
{{- if .HttpsOnly}}
150-
redirect scheme https if !{ ssl_fc }
155+
http-request redirect scheme https if !{ ssl_fc }
151156
{{- end}}
152157
{{- if eq $.SessionType "sticky-server"}}
153158
balance roundrobin

proxy/types.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package proxy
22

33
import (
44
"fmt"
5+
"os"
56
"strconv"
67
"strings"
7-
"os"
88
)
99

1010
var usersBasePath string = "/run/secrets/dfp_users_%s"
@@ -24,6 +24,8 @@ type ServiceDest struct {
2424
// The internal port of a service that should be reconfigured.
2525
// The port is used only in the *swarm* mode.
2626
Port string
27+
// If a request is sent to one of the domains in this list, it will be redirected to one of the values of the `ServiceDomain`.
28+
RedirectFromDomain []string
2729
// The request mode. The proxy should be able to work with any mode supported by HAProxy.
2830
// However, actively supported and tested modes are *http*, *tcp*, and *sni*.
2931
ReqMode string
@@ -388,6 +390,7 @@ func getServiceDest(sr *Service, provider ServiceParameterProvider, index int) S
388390
HttpsOnly: getBoolParam(provider, fmt.Sprintf("httpsOnly%s", suffix)),
389391
IgnoreAuthorization: getBoolParam(provider, fmt.Sprintf("ignoreAuthorization%s", suffix)),
390392
Port: provider.GetString(fmt.Sprintf("port%s", suffix)),
393+
RedirectFromDomain: getSliceFromString(provider, fmt.Sprintf("redirectFromDomain%s", suffix)),
391394
ReqMode: reqMode,
392395
ServiceDomain: getSliceFromString(provider, fmt.Sprintf("serviceDomain%s", suffix)),
393396
ServiceHeader: header,

proxy/types_test.go

+12-8
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package proxy
22

33
import (
44
"github.com/stretchr/testify/suite"
5+
"os"
56
"strconv"
67
"strings"
78
"testing"
8-
"os"
99
)
1010

1111
type TypesTestSuite struct {
@@ -229,12 +229,13 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesServiceDomainToIndexed
229229
ServiceDest: []ServiceDest{{
230230
AllowedMethods: []string{},
231231
DeniedMethods: []string{},
232+
Index: 1,
233+
Port: "1234",
234+
RedirectFromDomain: []string{},
235+
ReqMode: "reqMode",
232236
ServiceDomain: []string{"domain1", "domain2"},
233237
ServiceHeader: map[string]string{},
234238
ServicePath: []string{"/"},
235-
Port: "1234",
236-
ReqMode: "reqMode",
237-
Index: 1,
238239
}},
239240
ServiceName: "serviceName",
240241
}
@@ -256,12 +257,13 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesHttpsOnlyToIndexedEntr
256257
AllowedMethods: []string{},
257258
DeniedMethods: []string{},
258259
HttpsOnly: true,
260+
Index: 1,
261+
Port: "1234",
262+
RedirectFromDomain: []string{},
263+
ReqMode: "reqMode",
259264
ServiceDomain: []string{},
260265
ServiceHeader: map[string]string{},
261266
ServicePath: []string{"/"},
262-
Port: "1234",
263-
ReqMode: "reqMode",
264-
Index: 1,
265267
}},
266268
ServiceName: "serviceName",
267269
}
@@ -322,6 +324,7 @@ func (s *TypesTestSuite) getServiceMap(expected Service, indexSuffix, separator
322324
"httpsOnly" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].HttpsOnly),
323325
"ignoreAuthorization" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].IgnoreAuthorization),
324326
"port" + indexSuffix: expected.ServiceDest[0].Port,
327+
"redirectFromDomain" + indexSuffix: strings.Join(expected.ServiceDest[0].RedirectFromDomain, separator),
325328
"reqMode" + indexSuffix: expected.ServiceDest[0].ReqMode,
326329
"serviceDomain" + indexSuffix: strings.Join(expected.ServiceDest[0].ServiceDomain, separator),
327330
"serviceHeader" + indexSuffix: header,
@@ -355,10 +358,11 @@ func (s *TypesTestSuite) getExpectedService() Service {
355358
DenyHttp: true,
356359
HttpsOnly: true,
357360
IgnoreAuthorization: true,
361+
Port: "1234",
362+
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
358363
ServiceDomain: []string{"domain1", "domain2"},
359364
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
360365
ServicePath: []string{"/"},
361-
Port: "1234",
362366
ReqMode: "reqMode",
363367
UserAgent: UserAgent{Value: []string{"agent-1", "agent-2/replace-with_"}, AclName: "agent_1_agent_2_replace_with_"},
364368
VerifyClientSsl: true,

server/server_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
536536
DeniedMethods: []string{"PUT", "POST"},
537537
HttpsOnly: true,
538538
Port: "1234",
539+
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
539540
ReqMode: "reqMode",
540541
ServiceDomain: []string{"domain1", "domain2"},
541542
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
@@ -555,7 +556,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
555556
{Username: "user2", Password: "pass2", PassEncrypted: true}},
556557
}
557558
addr := fmt.Sprintf(
558-
"%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&isDefaultBackend=%t&xForwardedProto=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%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",
559+
"%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&isDefaultBackend=%t&xForwardedProto=%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",
559560
s.BaseUrl,
560561
expected.ServiceName,
561562
"user1:pass1,user2:pass2",
@@ -577,6 +578,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
577578
expected.RedirectWhenHttpProto,
578579
expected.HttpsPort,
579580
strings.Join(expected.ServiceDest[0].ServiceDomain, ","),
581+
strings.Join(expected.ServiceDest[0].RedirectFromDomain, ","),
580582
expected.Distribute,
581583
expected.SslVerifyNone,
582584
expected.ServiceDomainAlgo,
@@ -622,6 +624,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_SetsServicePathToSlash_WhenDoma
622624
AllowedMethods: []string{},
623625
DeniedMethods: []string{},
624626
Port: "1234",
627+
RedirectFromDomain: []string{},
625628
ReqMode: "http",
626629
ServiceDomain: []string{"domain1", "domain2"},
627630
ServiceHeader: map[string]string{},

stack.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ services:
2929
delay: 10s
3030
resources:
3131
reservations:
32-
memory: 10M
33-
limits:
3432
memory: 20M
33+
limits:
34+
memory: 50M
3535

3636
docs:
3737
image: vfarcic/docker-flow-proxy-docs:${TAG:-latest}

0 commit comments

Comments
 (0)