Skip to content

Commit

Permalink
Redirect unless https proto (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
audunhalland authored and thomasjpfan committed Oct 9, 2018
1 parent 982aaaa commit 68dceba
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 43 deletions.
86 changes: 44 additions & 42 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
|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`|
|proxyInstanceName|When `FILTER_PROXY_INSTANCE_NAME` is set to `true`, only services with proxyInstanceName equal to `PROXY_INSTANCE_NAME` will be configured by this proxy.<br>**Example:** `docker-flow`|
|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`|
|redirectUnlessHttpsProto|Whether to redirect to https unless X-Forwarded-Proto is explicitly `https`.<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. 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_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|
Expand Down Expand Up @@ -123,48 +124,49 @@ The environment variables must apply the rules that follow.

The map between the HTTP query parameters and environment variables is as follows.

|Query |Environment variable |
|---------------------|------------------------|
|aclName |ACL_NAME |
|addReqHeader |ADD_REQ_HEADER |
|addResHeader |ADD_RES_HEADER |
|allowedMethods |ALLOWED_METHODS |
|backendExtra |BACKEND_EXTRA |
|compressionAlgo |COMPRESSION_ALGO |
|compressionType |COMPRESSION_TYPE |
|deniedMethods |DENIED_METHODS |
|denyHttp |DENY_HTTP |
|distribute |DISTRIBUTE |
|httpsOnly |HTTPS_ONLY |
|httpsPort |HTTPS_PORT |
|ignoreAuthorization |IGNORE_AUTHORIZATION |
|isDefaultBackend |IS_DEFAULT_BACKEND |
|outboundHostname |OUTBOUND_HOSTNAME |
|pathType |PATH_TYPE |
|port |PORT |
|redirectFromDomain |REDIRECT_FROM_DOMAIN |
|redirectWhenHttpProto|REDIRECT_WHEN_HTTP_PROTO|
|reqMode |REQ_MODE |
|reqPathSearchReplace |REQ_PATH_SEARCH_REPLACE |
|serviceCert |SERVICE_CERT |
|serviceDomain |SERVICE_DOMAIN |
|serviceName |SERVICE_NAME |
|servicePath |SERVICE_PATH |
|servicePathExclude |SERVICE_PATH_EXCLUDE |
|setReqHeader |SET_REQ_HEADER |
|setResHeader |SET_RES_HEADER |
|srcPort |SRC_PORT |
|srcHttpsPort |SRC_HTTPS_PORT |
|sslVerifyNone |SSL_VERIFY_NONE |
|templateBePath |TEMPLATE_BE_PATH |
|templateFePath |TEMPLATE_FE_PATH |
|timeoutServer |TIMEOUT_SERVER |
|timeoutClient |TIMEOUT_CLIENT |
|timeoutTunnel |TIMEOUT_TUNNEL |
|users       |**Not supported** |
|usersSecret |**Not supported** |
|usersPassEncrypted |**Not supported** |
|verifyClientSsl |VERIFY_CLIENT_SSL |
|Query |Environment variable |
|------------------------|---------------------------|
|aclName |ACL_NAME |
|addReqHeader |ADD_REQ_HEADER |
|addResHeader |ADD_RES_HEADER |
|allowedMethods |ALLOWED_METHODS |
|backendExtra |BACKEND_EXTRA |
|compressionAlgo |COMPRESSION_ALGO |
|compressionType |COMPRESSION_TYPE |
|deniedMethods |DENIED_METHODS |
|denyHttp |DENY_HTTP |
|distribute |DISTRIBUTE |
|httpsOnly |HTTPS_ONLY |
|httpsPort |HTTPS_PORT |
|ignoreAuthorization |IGNORE_AUTHORIZATION |
|isDefaultBackend |IS_DEFAULT_BACKEND |
|outboundHostname |OUTBOUND_HOSTNAME |
|pathType |PATH_TYPE |
|port |PORT |
|redirectFromDomain |REDIRECT_FROM_DOMAIN |
|redirectWhenHttpProto |REDIRECT_WHEN_HTTP_PROTO |
|redirectUnlessHttpsProto|REDIRECT_UNLESS_HTTPS_PROTO|
|reqMode |REQ_MODE |
|reqPathSearchReplace |REQ_PATH_SEARCH_REPLACE |
|serviceCert |SERVICE_CERT |
|serviceDomain |SERVICE_DOMAIN |
|serviceName |SERVICE_NAME |
|servicePath |SERVICE_PATH |
|servicePathExclude |SERVICE_PATH_EXCLUDE |
|setReqHeader |SET_REQ_HEADER |
|setResHeader |SET_RES_HEADER |
|srcPort |SRC_PORT |
|srcHttpsPort |SRC_HTTPS_PORT |
|sslVerifyNone |SSL_VERIFY_NONE |
|templateBePath |TEMPLATE_BE_PATH |
|templateFePath |TEMPLATE_FE_PATH |
|timeoutServer |TIMEOUT_SERVER |
|timeoutClient |TIMEOUT_CLIENT |
|timeoutTunnel |TIMEOUT_TUNNEL |
|users       |**Not supported** |
|usersSecret |**Not supported** |
|usersPassEncrypted |**Not supported** |
|verifyClientSsl |VERIFY_CLIENT_SSL |

Please explore the [Configuring Non-Swarm Services](non-swarm.md) tutorial for more info.

Expand Down
66 changes: 66 additions & 0 deletions proxy/ha_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,72 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_usesHttpsRedirectCode()
s.Equal(expectedData, actualData)
}

func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToHttps_WhenRedirectUnlessHttpsProtoIsTrue() {
var actualData string
tmpl := s.TemplateContent
expectedData := fmt.Sprintf(
`%s
acl url_my-service1111_0 path_beg /path
acl domain_my-service1111_0 hdr_beg(host) -i my-domain.com
acl is_my-service_https hdr(X-Forwarded-Proto) https
http-request redirect scheme https if !is_my-service_https 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,
)
writeFile = func(filename string, data []byte, perm os.FileMode) error {
actualData = string(data)
return nil
}
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
service1 := Service{
ServiceName: "my-service",
RedirectUnlessHttpsProto: true,
AclName: "my-service",
ServiceDest: []ServiceDest{
{Port: "1111", ServicePath: []string{"/path"}, ServiceDomain: []string{"my-domain.com"}, PathType: "path_beg"},
},
}
p.AddService(service1)

p.CreateConfigFromTemplates()

s.Equal(expectedData, actualData)
}

func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_usesHttpsRedirectCode_3WhenRedirectUnlessHttpsProtoIsTrue() {
var actualData string
tmpl := s.TemplateContent
expectedData := fmt.Sprintf(
`%s
acl url_my-service1111_0 path_beg /path
acl domain_my-service1111_0 hdr_beg(host) -i my-domain.com
acl is_my-service_https hdr(X-Forwarded-Proto) https
http-request redirect scheme https code 301 if !is_my-service_https 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,
)
writeFile = func(filename string, data []byte, perm os.FileMode) error {
actualData = string(data)
return nil
}
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
service1 := Service{
ServiceName: "my-service",
RedirectUnlessHttpsProto: true,
AclName: "my-service",
ServiceDest: []ServiceDest{
{Port: "1111", ServicePath: []string{"/path"}, ServiceDomain: []string{"my-domain.com"}, HttpsRedirectCode: "301", PathType: "path_beg"},
},
}
p.AddService(service1)

p.CreateConfigFromTemplates()

s.Equal(expectedData, actualData)
}

func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRedirectFromDomainIsSet() {
var actualData string
tmpl := s.TemplateContent
Expand Down
10 changes: 10 additions & 0 deletions proxy/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ func getFrontTemplate(s Service) string {
{{- end}}
{{- end}}
{{- end}}
{{- if $.RedirectUnlessHttpsProto}}
{{- range .ServiceDest}}
{{- if eq .ReqMode "http"}}
{{- if ne .Port ""}}
acl is_{{$.AclName}}_https hdr(X-Forwarded-Proto) https
http-request redirect scheme https{{if .HttpsRedirectCode}} code {{.HttpsRedirectCode}}{{end}} if !is_{{$.AclName}}_https url_{{$.AclName}}{{.Port}}_{{.Index}}{{if .ServiceDomain}} domain_{{$.AclName}}{{.Port}}_{{.Index}}{{end}}{{.SrcPortAclName}}
{{- end}}
{{- end}}
{{- end}}
{{- end}}
{{- range $sd := .ServiceDest}}
{{- if eq .ReqMode "http" }}
{{- if ne .Port ""}}
Expand Down
2 changes: 2 additions & 0 deletions proxy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ type Service struct {
ProxyInstanceName string `split_words:"true"`
// Whether to redirect to https when X-Forwarded-Proto is http
RedirectWhenHttpProto bool `split_words:"true"`
// Whether to redirect to https unless X-Forwarded-Proto is https
RedirectUnlessHttpsProto bool `split_words:"true"`
// The number of replicas of a service.
// This parameter is used if `DiscoveryType` is set to `DNS`.
// Non-Global services with 0 replicas will not be added to the HAproxy config.
Expand Down
2 changes: 2 additions & 0 deletions proxy/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,7 @@ func (s *TypesTestSuite) getServiceMap(expected Service, indexSuffix, separator
"isDefaultBackend": strconv.FormatBool(expected.IsDefaultBackend),
"proxyInstanceName": expected.ProxyInstanceName,
"redirectWhenHttpProto": strconv.FormatBool(expected.RedirectWhenHttpProto),
"redirectUnlessHttpsProto": strconv.FormatBool(expected.RedirectUnlessHttpsProto),
"reqPathReplace": expected.ReqPathReplace,
"reqPathSearch": expected.ReqPathSearch,
"replicas": strconv.Itoa(expected.Replicas),
Expand Down Expand Up @@ -438,6 +439,7 @@ func (s *TypesTestSuite) getExpectedService() Service {
IsDefaultBackend: true,
ProxyInstanceName: "docker-flow",
RedirectWhenHttpProto: true,
RedirectUnlessHttpsProto: true,
Replicas: 3,
ReqPathReplace: "reqPathReplace",
ReqPathSearch: "reqPathSearch",
Expand Down
6 changes: 5 additions & 1 deletion server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
Distribute: true,
ProxyInstanceName: "docker-flow",
RedirectWhenHttpProto: true,
RedirectUnlessHttpsProto: true,
Replicas: 83,
ReqPathReplace: "reqPathReplace",
ReqPathSearch: "reqPathSearch",
Expand Down Expand Up @@ -629,7 +630,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&balanceGroup=%s&checkTcp=%t&clitcpka=%t&serviceCert=%s&outboundHostname=%s&pathType=%s&proxyInstanceName=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutClient=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&httpsPort=%d&srcPort=%d&srcHttpsPort=%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&checkResolvers=%t&replicas=%d&discoveryType=%s",
"%s?serviceName=%s&users=%s&usersPassEncrypted=%t&aclName=%s&balanceGroup=%s&checkTcp=%t&clitcpka=%t&serviceCert=%s&outboundHostname=%s&pathType=%s&proxyInstanceName=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutClient=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&redirectUnlessHttpsProto=%t&httpsPort=%d&srcPort=%d&srcHttpsPort=%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&checkResolvers=%t&replicas=%d&discoveryType=%s",

s.BaseUrl,
expected.ServiceName,
Expand All @@ -655,6 +656,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
expected.ServiceDest[0].HttpsRedirectCode,
expected.IsDefaultBackend,
expected.RedirectWhenHttpProto,
expected.RedirectUnlessHttpsProto,
expected.ServiceDest[0].HttpsPort,
expected.ServiceDest[0].SrcPort,
expected.ServiceDest[0].SrcHttpsPort,
Expand Down Expand Up @@ -843,6 +845,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
os.Setenv("DFP_SERVICE_PATH_TYPE", service.ServiceDest[0].PathType)
os.Setenv("DFP_SERVICE_REDIRECT_FROM_DOMAIN", strings.Join(service.ServiceDest[0].RedirectFromDomain, ","))
os.Setenv("DFP_SERVICE_REDIRECT_WHEN_HTTP_PROTO", strconv.FormatBool(service.RedirectWhenHttpProto))
os.Setenv("DFP_SERVICE_REDIRECT_UNLESS_HTTPS_PROTO", strconv.FormatBool(service.RedirectUnlessHttpsProto))
os.Setenv("DFP_SERVICE_REQ_MODE", service.ServiceDest[0].ReqMode)
os.Setenv("DFP_SERVICE_REQ_PATH_SEARCH_REPLACE", service.ServiceDest[0].ReqPathSearchReplace)
os.Setenv("DFP_SERVICE_SERVICE_CERT", service.ServiceCert)
Expand Down Expand Up @@ -888,6 +891,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
os.Unsetenv("DFP_SERVICE_PORT")
os.Unsetenv("DFP_SERVICE_REDIRECT_FROM_DOMAIN")
os.Unsetenv("DFP_SERVICE_REDIRECT_WHEN_HTTP_PROTO")
os.Unsetenv("DFP_SERVICE_REDIRECT_UNLESS_HTTPS_PROTO")
os.Unsetenv("DFP_SERVICE_REQ_MODE")
os.Unsetenv("DFP_SERVICE_REQ_PATH_SEARCH_REPLACE")
os.Unsetenv("DFP_SERVICE_SERVICE_CERT")
Expand Down

0 comments on commit 68dceba

Please sign in to comment.