Skip to content

Commit

Permalink
Fixes #340
Browse files Browse the repository at this point in the history
  • Loading branch information
vfarcic committed Sep 30, 2017
1 parent 1ad9856 commit 5bc9739
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 20 deletions.
1 change: 0 additions & 1 deletion actions/fetch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,6 @@ func (s *FetchTestSuite) Test_ReloadConfig_SendsARequestToSwarmListener_WhenList
reconfigureMock.AssertNumberOfCalls(s.T(), "Execute", 1)
proxyMock.AssertCalled(s.T(), "Reload")
proxyMock.AssertCalled(s.T(), "CreateConfigFromTemplates")

}

func (s *FetchTestSuite) Test_ReloadConfig_ReturnsError_WhenSwarmListenerReturnsWrongData() {
Expand Down
2 changes: 1 addition & 1 deletion actions/reconfigure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ func (s ReconfigureTestSuite) Test_Execute_WritesBeTemplateWithRedirectToHttps_W
`
backend %s-be%s_0
mode http
redirect scheme https if !{ ssl_fc }
http-request redirect scheme https if !{ ssl_fc }
server %s %s:%s`,
s.ServiceName,
s.reconfigure.ServiceDest[0].Port,
Expand Down
8 changes: 5 additions & 3 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
|timeoutTunnel |The tunnel timeout in seconds.<br>**Default:** `3600`<br>**Example:** `3600`|
|xForwardedProto|Whether to add "X-Forwarded-Proto https" header.<br>**Default:** `false`<br>**Example:** `true`|

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.
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.

### HTTP Mode Query Parameters

Expand All @@ -51,9 +51,10 @@ The following query parameters can be used only when `reqMode` is set to `http`
|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`|
|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`|
|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`|
|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`|
|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`|
|serviceCert |Content of the PEM-encoded certificate to be used by the proxy when serving traffic over SSL.|
|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`|
|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`|
|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|
|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`|
|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`|
Expand All @@ -67,7 +68,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
|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`|
|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`|

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.
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.

### TCP Mode HTTP Query Parameters

Expand Down Expand Up @@ -121,6 +122,7 @@ The map between the HTTP query parameters and environment variables is as follow
|outboundHostname |OUTBOUND_HOSTNAME |
|pathType |PATH_TYPE |
|port |PORT |
|redirectFromDomain |REDIRECT_FROM_DOMAIN |
|redirectWhenHttpProto|REDIRECT_WHEN_HTTP_PROTO|
|reqMode |REQ_MODE |
|reqPathReplace |REQ_PATH_REPLACE |
Expand Down
17 changes: 17 additions & 0 deletions integration_tests/integration_swarm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@ func (s IntegrationSwarmTestSuite) Test_Domain() {
}
}

func (s IntegrationSwarmTestSuite) Test_RedirectFromDomain() {
params := fmt.Sprintf("&serviceDomain=%s&redirectFromDomain=my-other-domain.com", s.hostIP)
s.reconfigureGoDemo(params)

client := new(http.Client)
url := fmt.Sprintf("http://%s/demo/hello", s.hostIP)
req, err := http.NewRequest("GET", url, nil)
s.NoError(err)
req.Host = "my-other-domain.com"
resp, err := client.Do(req)

s.NoError(err, s.getProxyConf(""))
if resp != nil {
s.Equal(200, resp.StatusCode, s.getProxyConf(""))
}
}

func (s IntegrationSwarmTestSuite) Test_Config() {
s.reconfigureGoDemo("")

Expand Down
39 changes: 38 additions & 1 deletion proxy/ha_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1502,7 +1502,7 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed
acl url_my-service1111_0 path_beg /path
acl domain_my-service1111_0 hdr(host) -i my-domain.com
acl is_my-service_http hdr(X-Forwarded-Proto) http
redirect scheme https if is_my-service_http url_my-service1111_0 domain_my-service1111_0
http-request redirect scheme https if is_my-service_http url_my-service1111_0 domain_my-service1111_0
use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0%s`,
tmpl,
s.ServicesContent,
Expand All @@ -1527,6 +1527,43 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRed
s.Equal(expectedData, actualData)
}

func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRedirectFromDomainIsSet() {
var actualData string
tmpl := s.TemplateContent
expectedData := fmt.Sprintf(
`%s
acl url_my-service1111_0 path_beg /path
acl domain_my-service1111_0 hdr(host) -i my-domain-1.com my-domain-2.com
http-request redirect code 301 prefix http://my-domain-1.com if { hdr(host) -i my-other-domain-1.com }
http-request redirect code 301 prefix http://my-domain-1.com if { hdr(host) -i my-other-domain-2.com }
use_backend my-service-be1111_0 if url_my-service1111_0 domain_my-service1111_0%s`,
tmpl,
s.ServicesContent,
)
writeFile = func(filename string, data []byte, perm os.FileMode) error {
actualData = string(data)
return nil
}
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
dataInstance.Services["my-service"] = Service{
ServiceName: "my-service",
PathType: "path_beg",
AclName: "my-service",
ServiceDest: []ServiceDest{
{
Port: "1111",
ServicePath: []string{"/path"},
ServiceDomain: []string{"my-domain-1.com", "my-domain-2.com"},
RedirectFromDomain: []string{"my-other-domain-1.com", "my-other-domain-2.com"},
},
},
}

p.CreateConfigFromTemplates()

s.Equal(expectedData, actualData)
}

func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_UsesServiceHeader() {
var actualData string
tmpl := s.TemplateContent
Expand Down
9 changes: 7 additions & 2 deletions proxy/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@ func getFrontTemplate(s Service) string {
acl http_{{.ServiceName}} src_port 80
acl https_{{.ServiceName}} src_port 443
{{- end}}
{{- range $sd := .ServiceDest}}
{{- range $rd := $sd.RedirectFromDomain}}
http-request redirect code 301 prefix http://{{index $sd.ServiceDomain 0}} if { hdr(host) -i {{$rd}} }
{{- end}}
{{- end}}
{{- if $.RedirectWhenHttpProto}}
{{- range .ServiceDest}}
{{- if eq .ReqMode "http"}}
{{- if ne .Port ""}}
acl is_{{$.AclName}}_http hdr(X-Forwarded-Proto) http
redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}}
http-request redirect scheme https if is_{{$.AclName}}_http url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}}
{{- end}}
{{- end}}
{{- end}}
Expand Down Expand Up @@ -147,7 +152,7 @@ backend {{$.ServiceName}}-be{{.Port}}_{{.Index}}
http-request deny if !{ ssl_fc }
{{- end}}
{{- if .HttpsOnly}}
redirect scheme https if !{ ssl_fc }
http-request redirect scheme https if !{ ssl_fc }
{{- end}}
{{- if eq $.SessionType "sticky-server"}}
balance roundrobin
Expand Down
5 changes: 4 additions & 1 deletion proxy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package proxy

import (
"fmt"
"os"
"strconv"
"strings"
"os"
)

var usersBasePath string = "/run/secrets/dfp_users_%s"
Expand All @@ -24,6 +24,8 @@ type ServiceDest struct {
// The internal port of a service that should be reconfigured.
// The port is used only in the *swarm* mode.
Port string
// 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`.
RedirectFromDomain []string
// 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*.
ReqMode string
Expand Down Expand Up @@ -388,6 +390,7 @@ func getServiceDest(sr *Service, provider ServiceParameterProvider, index int) S
HttpsOnly: getBoolParam(provider, fmt.Sprintf("httpsOnly%s", suffix)),
IgnoreAuthorization: getBoolParam(provider, fmt.Sprintf("ignoreAuthorization%s", suffix)),
Port: provider.GetString(fmt.Sprintf("port%s", suffix)),
RedirectFromDomain: getSliceFromString(provider, fmt.Sprintf("redirectFromDomain%s", suffix)),
ReqMode: reqMode,
ServiceDomain: getSliceFromString(provider, fmt.Sprintf("serviceDomain%s", suffix)),
ServiceHeader: header,
Expand Down
20 changes: 12 additions & 8 deletions proxy/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package proxy

import (
"github.com/stretchr/testify/suite"
"os"
"strconv"
"strings"
"testing"
"os"
)

type TypesTestSuite struct {
Expand Down Expand Up @@ -229,12 +229,13 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesServiceDomainToIndexed
ServiceDest: []ServiceDest{{
AllowedMethods: []string{},
DeniedMethods: []string{},
Index: 1,
Port: "1234",
RedirectFromDomain: []string{},
ReqMode: "reqMode",
ServiceDomain: []string{"domain1", "domain2"},
ServiceHeader: map[string]string{},
ServicePath: []string{"/"},
Port: "1234",
ReqMode: "reqMode",
Index: 1,
}},
ServiceName: "serviceName",
}
Expand All @@ -256,12 +257,13 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesHttpsOnlyToIndexedEntr
AllowedMethods: []string{},
DeniedMethods: []string{},
HttpsOnly: true,
Index: 1,
Port: "1234",
RedirectFromDomain: []string{},
ReqMode: "reqMode",
ServiceDomain: []string{},
ServiceHeader: map[string]string{},
ServicePath: []string{"/"},
Port: "1234",
ReqMode: "reqMode",
Index: 1,
}},
ServiceName: "serviceName",
}
Expand Down Expand Up @@ -322,6 +324,7 @@ func (s *TypesTestSuite) getServiceMap(expected Service, indexSuffix, separator
"httpsOnly" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].HttpsOnly),
"ignoreAuthorization" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].IgnoreAuthorization),
"port" + indexSuffix: expected.ServiceDest[0].Port,
"redirectFromDomain" + indexSuffix: strings.Join(expected.ServiceDest[0].RedirectFromDomain, separator),
"reqMode" + indexSuffix: expected.ServiceDest[0].ReqMode,
"serviceDomain" + indexSuffix: strings.Join(expected.ServiceDest[0].ServiceDomain, separator),
"serviceHeader" + indexSuffix: header,
Expand Down Expand Up @@ -355,10 +358,11 @@ func (s *TypesTestSuite) getExpectedService() Service {
DenyHttp: true,
HttpsOnly: true,
IgnoreAuthorization: true,
Port: "1234",
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
ServiceDomain: []string{"domain1", "domain2"},
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
ServicePath: []string{"/"},
Port: "1234",
ReqMode: "reqMode",
UserAgent: UserAgent{Value: []string{"agent-1", "agent-2/replace-with_"}, AclName: "agent_1_agent_2_replace_with_"},
VerifyClientSsl: true,
Expand Down
5 changes: 4 additions & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
DeniedMethods: []string{"PUT", "POST"},
HttpsOnly: true,
Port: "1234",
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
ReqMode: "reqMode",
ServiceDomain: []string{"domain1", "domain2"},
ServiceHeader: map[string]string{"X-Version": "3", "name": "Viktor"},
Expand All @@ -555,7 +556,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
{Username: "user2", Password: "pass2", PassEncrypted: true}},
}
addr := fmt.Sprintf(
"%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",
"%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",
s.BaseUrl,
expected.ServiceName,
"user1:pass1,user2:pass2",
Expand All @@ -577,6 +578,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
expected.RedirectWhenHttpProto,
expected.HttpsPort,
strings.Join(expected.ServiceDest[0].ServiceDomain, ","),
strings.Join(expected.ServiceDest[0].RedirectFromDomain, ","),
expected.Distribute,
expected.SslVerifyNone,
expected.ServiceDomainAlgo,
Expand Down Expand Up @@ -622,6 +624,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_SetsServicePathToSlash_WhenDoma
AllowedMethods: []string{},
DeniedMethods: []string{},
Port: "1234",
RedirectFromDomain: []string{},
ReqMode: "http",
ServiceDomain: []string{"domain1", "domain2"},
ServiceHeader: map[string]string{},
Expand Down
4 changes: 2 additions & 2 deletions stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ services:
delay: 10s
resources:
reservations:
memory: 10M
limits:
memory: 20M
limits:
memory: 50M

docs:
image: vfarcic/docker-flow-proxy-docs:${TAG:-latest}
Expand Down

0 comments on commit 5bc9739

Please sign in to comment.