Skip to content

Commit

Permalink
Fixes #346
Browse files Browse the repository at this point in the history
  • Loading branch information
vfarcic committed Oct 12, 2017
1 parent 3e4b051 commit 344b365
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 52 deletions.
8 changes: 2 additions & 6 deletions actions/reconfigure.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func (m *Reconfigure) Execute(reloadAfter bool) error {
defer mu.Unlock()
if strings.EqualFold(os.Getenv("SKIP_ADDRESS_VALIDATION"), "false") {
host := m.ServiceName
if len(m.OutboundHostname) > 0 {
host = m.OutboundHostname
if len(m.ServiceDest) > 0 && len(m.ServiceDest[0].OutboundHostname) > 0 {
host = m.ServiceDest[0].OutboundHostname
}
if _, err := lookupHost(host); err != nil {
logPrintf("Could not reach the service %s. Is the service running and connected to the same network as the proxy?", host)
Expand Down Expand Up @@ -142,10 +142,6 @@ func (m *Reconfigure) formatData(sr *proxy.Service) {
if len(sr.AclName) == 0 {
sr.AclName = sr.ServiceName
}
sr.Host = m.ServiceName
if len(m.OutboundHostname) > 0 {
sr.Host = m.OutboundHostname
}
if len(sr.PathType) == 0 {
sr.PathType = "path_beg"
}
Expand Down
9 changes: 3 additions & 6 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ The following query parameters can be used to send a *reconfigure* request to *D
|timeoutServer |The server timeout in seconds.<br>**Default:** `20`<br>**Example:** `60`|
|timeoutTunnel |The tunnel timeout in seconds.<br>**Default:** `3600`<br>**Example:** `3600`|

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

### HTTP Mode Query Parameters

Expand All @@ -51,7 +51,7 @@ The following query parameters can be used only when `reqMode` is set to `http`
|denyHttp |Whether to deny HTTP requests thus allowing only HTTPS. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `denyHttp.1`, `denyHttp.2`, and so on).<br>**Example:** `true`<br>**Default Value:** `false`|
|httpsRedirectCode|HTTP code for HTTP to HTTPS redirects. This parameter is used only if `httpsOnly` is set to `true`<br>**Example:** `301`|
|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`|
|outboundHostname|The hostname where the service is running, for instance on a separate swarm. If specified, the proxy will dispatch requests to that domain. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `outboundHostname.1`, `outboundHostname.2`, and so on).<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`|
Expand All @@ -70,15 +70,12 @@ 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`, `redirectFromDomain`, 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`, `ReqMode`, or `outboundHostname` parameters. In that case, `srcPort` is required.

### TCP Mode HTTP Query Parameters

The `reqMode` set to `tcp` does not have any specific parameters beyond those specified in the [Reconfigure General Parameters](#reconfigure-general-parameters) section.

!!! warning
If multiple TCP services are defined to use the same `srcPort`, `serviceDomain` must be set for those services.

Please consult the [Using TCP Request Mode](swarm-mode-auto.md#using-tcp-request-mode) section for an example of working with `tcp` request mode.

An example request is as follows.
Expand Down
8 changes: 0 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,6 @@ func main() {
go logging.StartLogging()
}

// NOTE: Does not seem to reap zombie processes.
// TODO: Uncomment after fixing it
// pids := make(reap.PidCh, 2)
// errors := make(reap.ErrorCh, 2)
// done := make(chan struct{})
// var reapLock sync.RWMutex
// go reap.ReapChildren(pids, errors, done, &reapLock)

// TODO: Change to serverImpl.Execute
newArgs().parse()
}
4 changes: 2 additions & 2 deletions proxy/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ backend {{$.ServiceName}}-be{{.Port}}_{{.Index}}
server {{$.ServiceName}}_{{$i}} {{$t}}:{{$sd.Port}} check cookie {{$.ServiceName}}_{{$i}}
{{- end}}
{{- if not $.Tasks}}
server {{$.ServiceName}} {{$.Host}}:{{$sd.Port}}{{if eq $.CheckResolvers true}} check resolvers docker{{end}}{{if eq $.SslVerifyNone true}} ssl verify none{{end}}
server {{$.ServiceName}} {{if ne $.ServiceName ""}}{{$.ServiceName}}{{end}}{{if eq $.ServiceName ""}}{{$sd.OutboundHostname}}{{end}}:{{$sd.Port}}{{if eq $.CheckResolvers true}} check resolvers docker{{end}}{{if eq $.SslVerifyNone true}} ssl verify none{{end}}
{{- end}}
{{- if not .IgnoreAuthorization}}
{{- if and ($.Users) (not .IgnoreAuthorization)}}
Expand Down Expand Up @@ -234,7 +234,7 @@ backend https-{{$.ServiceName}}-be{{.Port}}_{{.Index}}
server {{$.ServiceName}}_{{$i}} {{$t}}:{{$.HttpsPort}} check cookie {{$.ServiceName}}_{{$i}}
{{- end}}
{{- if not $.Tasks}}
server {{$.ServiceName}} {{$.Host}}:{{$.HttpsPort}}{{if eq $.CheckResolvers true}} check resolvers docker{{end}}{{if eq $.SslVerifyNone true}} ssl verify none{{end}}
server {{$.ServiceName}} {{if ne $.ServiceName ""}}{{$.ServiceName}}{{end}}{{if eq $.ServiceName ""}}{{$sd.OutboundHostname}}{{end}}:{{$.HttpsPort}}{{if eq $.CheckResolvers true}} check resolvers docker{{end}}{{if eq $.SslVerifyNone true}} ssl verify none{{end}}
{{- end}}
{{- if not .IgnoreAuthorization}}
{{- if $.Users}}
Expand Down
12 changes: 8 additions & 4 deletions proxy/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ type ServiceDest struct {
HttpsRedirectCode string
// Whether to ignore authorization for this service destination.
IgnoreAuthorization bool
// The hostname where the service is running, for instance on a separate swarm.
// If specified, the proxy will dispatch requests to that domain.
OutboundHostname string
// The internal port of a service that should be reconfigured.
// The port is used only in the *swarm* mode.
Port string
Expand Down Expand Up @@ -107,9 +110,6 @@ type Service struct {
HttpsPort int `split_words:"true"`
// If set to true, it will be the default_backend service.
IsDefaultBackend bool `split_words:"true"`
// The hostname where the service is running, for instance on a separate swarm.
// If specified, the proxy will dispatch requests to that domain.
OutboundHostname string `split_words:"true"`
// The ACL derivative. Defaults to path_beg.
// See https://cbonte.github.io/haproxy-dconv/configuration-1.5.html#7.3.6-path for more info.
PathType string `split_words:"true"`
Expand Down Expand Up @@ -157,7 +157,6 @@ type Service struct {
// The rest of variables are for internal use only
ServicePort string
AclCondition string
Host string
LookupRetry int
LookupRetryInterval int
ServiceDest []ServiceDest
Expand Down Expand Up @@ -388,13 +387,18 @@ func getServiceDest(sr *Service, provider ServiceParameterProvider, index int) S
if sdIndex < 0 {
sdIndex = 0
}
outboundHostname := provider.GetString(fmt.Sprintf("outboundHostname%s", suffix))
if len(outboundHostname) == 0 {
outboundHostname = provider.GetString("outboundHostname")
}
return ServiceDest{
AllowedMethods: getSliceFromString(provider, fmt.Sprintf("allowedMethods%s", suffix)),
DeniedMethods: getSliceFromString(provider, fmt.Sprintf("deniedMethods%s", suffix)),
DenyHttp: getBoolParam(provider, fmt.Sprintf("denyHttp%s", suffix)),
HttpsOnly: getBoolParam(provider, fmt.Sprintf("httpsOnly%s", suffix)),
HttpsRedirectCode: provider.GetString(fmt.Sprintf("httpsRedirectCode%s", suffix)),
IgnoreAuthorization: getBoolParam(provider, fmt.Sprintf("ignoreAuthorization%s", suffix)),
OutboundHostname: outboundHostname,
Port: provider.GetString(fmt.Sprintf("port%s", suffix)),
RedirectFromDomain: getSliceFromString(provider, fmt.Sprintf("redirectFromDomain%s", suffix)),
ReqMode: reqMode,
Expand Down
32 changes: 30 additions & 2 deletions proxy/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,34 @@ func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesServiceDomainToIndexed
s.Equal(expected, *actual)
}

func (s *TypesTestSuite) Test_GetServiceFromProvider_UsesNonIndexedOutboundHostname() {
expected := Service{
ServiceDest: []ServiceDest{{
AllowedMethods: []string{},
DeniedMethods: []string{},
Index: 1,
OutboundHostname: "my-outbound-host.com",
Port: "1234",
RedirectFromDomain: []string{},
ReqMode: "reqMode",
ServiceDomain: []string{},
ServiceHeader: map[string]string{},
ServicePath: []string{"/"},
}},
ServiceName: "serviceName",
}
serviceMap := map[string]string{
"outboundHostname": expected.ServiceDest[0].OutboundHostname,
"port.1": expected.ServiceDest[0].Port,
"reqMode.1": expected.ServiceDest[0].ReqMode,
"serviceName": expected.ServiceName,
"servicePath.1": strings.Join(expected.ServiceDest[0].ServicePath, ","),
}
provider := mapParameterProvider{&serviceMap}
actual := GetServiceFromProvider(&provider)
s.Equal(expected, *actual)
}

func (s *TypesTestSuite) Test_GetServiceFromProvider_MovesHttpsOnlyToIndexedEntries_WhenEmpty() {
expected := Service{
ServiceDest: []ServiceDest{{
Expand Down Expand Up @@ -300,7 +328,6 @@ func (s *TypesTestSuite) getServiceMap(expected Service, indexSuffix, separator
"distribute": strconv.FormatBool(expected.Distribute),
"httpsPort": strconv.Itoa(expected.HttpsPort),
"isDefaultBackend": strconv.FormatBool(expected.IsDefaultBackend),
"outboundHostname": expected.OutboundHostname,
"pathType": expected.PathType,
"redirectWhenHttpProto": strconv.FormatBool(expected.RedirectWhenHttpProto),
"reqPathReplace": expected.ReqPathReplace,
Expand All @@ -325,6 +352,7 @@ func (s *TypesTestSuite) getServiceMap(expected Service, indexSuffix, separator
"httpsOnly" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].HttpsOnly),
"httpsRedirectCode" + indexSuffix: expected.ServiceDest[0].HttpsRedirectCode,
"ignoreAuthorization" + indexSuffix: strconv.FormatBool(expected.ServiceDest[0].IgnoreAuthorization),
"outboundHostname" + indexSuffix: expected.ServiceDest[0].OutboundHostname,
"port" + indexSuffix: expected.ServiceDest[0].Port,
"redirectFromDomain" + indexSuffix: strings.Join(expected.ServiceDest[0].RedirectFromDomain, separator),
"reqMode" + indexSuffix: expected.ServiceDest[0].ReqMode,
Expand All @@ -347,7 +375,6 @@ func (s *TypesTestSuite) getExpectedService() Service {
Distribute: true,
HttpsPort: 1234,
IsDefaultBackend: true,
OutboundHostname: "outboundHostname",
PathType: "pathType",
RedirectWhenHttpProto: true,
ReqPathReplace: "reqPathReplace",
Expand All @@ -361,6 +388,7 @@ func (s *TypesTestSuite) getExpectedService() Service {
HttpsOnly: true,
HttpsRedirectCode: "302",
IgnoreAuthorization: true,
OutboundHostname: "outboundHostname",
Port: "1234",
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
ServiceDomain: []string{"domain1", "domain2"},
Expand Down
7 changes: 7 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,13 +263,15 @@ func (m *serve) getServiceFromEnvVars(prefix string) (proxy.Service, error) {
}
httpsOnly, _ := strconv.ParseBool(os.Getenv(prefix + "_HTTPS_ONLY"))
httpsRedirectCode := os.Getenv(prefix + "_HTTPS_REDIRECT_CODE")
globalOutboundHostname := os.Getenv(prefix + "_OUTBOUND_HOSTNAME")

if len(path) > 0 || len(port) > 0 {
sd = append(
sd,
proxy.ServiceDest{
HttpsOnly: httpsOnly,
HttpsRedirectCode: httpsRedirectCode,
OutboundHostname: globalOutboundHostname,
Port: port,
ReqMode: reqMode,
ServiceDomain: domain,
Expand All @@ -289,11 +291,16 @@ func (m *serve) getServiceFromEnvVars(prefix string) (proxy.Service, error) {
}
srcPort, _ := strconv.Atoi(os.Getenv(fmt.Sprintf("%s_SRC_PORT_%d", prefix, i)))
if len(path) > 0 && len(port) > 0 {
outboundHostname := os.Getenv(fmt.Sprintf("%s_OUTBOUND_HOSTNAME_%d", prefix, i))
if len(outboundHostname) == 0 {
outboundHostname = globalOutboundHostname
}
sd = append(
sd,
proxy.ServiceDest{
HttpsOnly: httpsOnly,
HttpsRedirectCode: httpsRedirectCode,
OutboundHostname: outboundHostname,
Port: port,
SrcPort: srcPort,
ServicePath: strings.Split(path, ","),
Expand Down
35 changes: 19 additions & 16 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,6 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
DelResHeader: []string{"add-header-1", "add-header-2"},
Distribute: true,
HttpsPort: 1234,
OutboundHostname: "outboundHostname",
PathType: "pathType",
RedirectWhenHttpProto: true,
ReqPathReplace: "reqPathReplace",
Expand All @@ -538,6 +537,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
DeniedMethods: []string{"PUT", "POST"},
HttpsOnly: true,
HttpsRedirectCode: "302",
OutboundHostname: "outboundHostname",
Port: "1234",
RedirectFromDomain: []string{"sub.domain1", "sub.domain2"},
ReqMode: "reqMode",
Expand Down Expand Up @@ -565,7 +565,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
true,
expected.AclName,
expected.ServiceCert,
expected.OutboundHostname,
expected.ServiceDest[0].OutboundHostname,
expected.PathType,
expected.ReqPathSearch,
expected.ReqPathReplace,
Expand Down Expand Up @@ -667,7 +667,6 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
Distribute: true,
HttpsPort: 1234,
IsDefaultBackend: true,
OutboundHostname: "my-OutboundHostname",
PathType: "my-PathType",
RedirectWhenHttpProto: true,
ReqPathReplace: "my-ReqPathReplace",
Expand All @@ -686,6 +685,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
{
HttpsOnly: true,
HttpsRedirectCode: "302",
OutboundHostname: "my-OutboundHostname",
Port: "1111",
ServiceDomain: []string{"my-domain-1.com", "my-domain-2.com"},
ServicePath: []string{"my-path-11", "my-path-12"},
Expand All @@ -707,7 +707,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
os.Setenv("DFP_SERVICE_HTTPS_REDIRECT_CODE", service.ServiceDest[0].HttpsRedirectCode)
os.Setenv("DFP_SERVICE_HTTPS_PORT", strconv.Itoa(service.HttpsPort))
os.Setenv("DFP_SERVICE_IS_DEFAULT_BACKEND", strconv.FormatBool(service.IsDefaultBackend))
os.Setenv("DFP_SERVICE_OUTBOUND_HOSTNAME", service.OutboundHostname)
os.Setenv("DFP_SERVICE_OUTBOUND_HOSTNAME", service.ServiceDest[0].OutboundHostname)
os.Setenv("DFP_SERVICE_PATH_TYPE", service.PathType)
os.Setenv("DFP_SERVICE_REDIRECT_WHEN_HTTP_PROTO", strconv.FormatBool(service.RedirectWhenHttpProto))
os.Setenv("DFP_SERVICE_REQ_MODE", service.ServiceDest[0].ReqMode)
Expand Down Expand Up @@ -765,6 +765,7 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
os.Unsetenv("DFP_SERVICE_X_FORWARDED_PROTO")
}()
srv := serve{}
println("000")
actual := srv.GetServicesFromEnvVars()

s.Len(*actual, 1)
Expand Down Expand Up @@ -802,22 +803,23 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_SetsServiceDomainAlgoToHdr
}

func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServicesWithIndexedData() {
service := proxy.Service{
expected := proxy.Service{
ServiceName: "my-ServiceName",
ServiceDest: []proxy.ServiceDest{
{Port: "1111", ServicePath: []string{"my-path-11", "my-path-12"}, SrcPort: 1112, HttpsOnly: true},
{Port: "2221", ServicePath: []string{"my-path-21", "my-path-22"}, SrcPort: 2222, HttpsOnly: false},
{Port: "2221", ServicePath: []string{"my-path-21", "my-path-22"}, SrcPort: 2222, HttpsOnly: false, OutboundHostname: "my-outbound-domain.com"},
},
}
os.Setenv("DFP_SERVICE_SERVICE_NAME", service.ServiceName)
os.Setenv("DFP_SERVICE_SERVICE_NAME", expected.ServiceName)
os.Setenv("DFP_SERVICE_HTTPS_ONLY_1", "true")
os.Setenv("DFP_SERVICE_PORT_1", service.ServiceDest[0].Port)
os.Setenv("DFP_SERVICE_SERVICE_PATH_1", strings.Join(service.ServiceDest[0].ServicePath, ","))
os.Setenv("DFP_SERVICE_SRC_PORT_1", strconv.Itoa(service.ServiceDest[0].SrcPort))
os.Setenv("DFP_SERVICE_PORT_1", expected.ServiceDest[0].Port)
os.Setenv("DFP_SERVICE_SERVICE_PATH_1", strings.Join(expected.ServiceDest[0].ServicePath, ","))
os.Setenv("DFP_SERVICE_SRC_PORT_1", strconv.Itoa(expected.ServiceDest[0].SrcPort))
os.Setenv("DFP_SERVICE_HTTPS_ONLY_2", "false")
os.Setenv("DFP_SERVICE_PORT_2", service.ServiceDest[1].Port)
os.Setenv("DFP_SERVICE_SERVICE_PATH_2", strings.Join(service.ServiceDest[1].ServicePath, ","))
os.Setenv("DFP_SERVICE_SRC_PORT_2", strconv.Itoa(service.ServiceDest[1].SrcPort))
os.Setenv("DFP_SERVICE_PORT_2", expected.ServiceDest[1].Port)
os.Setenv("DFP_SERVICE_SERVICE_PATH_2", strings.Join(expected.ServiceDest[1].ServicePath, ","))
os.Setenv("DFP_SERVICE_SRC_PORT_2", strconv.Itoa(expected.ServiceDest[1].SrcPort))
os.Setenv("DFP_SERVICE_OUTBOUND_HOSTNAME_2", expected.ServiceDest[1].OutboundHostname)

defer func() {
os.Unsetenv("DFP_SERVICE_SERVICE_NAME")
Expand All @@ -829,14 +831,15 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServicesWithIndexed
os.Unsetenv("DFP_SERVICE_PORT_2")
os.Unsetenv("DFP_SERVICE_SERVICE_PATH_2")
os.Unsetenv("DFP_SERVICE_SRC_PORT_2")
os.Unsetenv("DFP_SERVICE_OUTBOUND_HOSTNAME_2")
}()
srv := serve{}
actual := srv.GetServicesFromEnvVars()

service.ServiceDest[0].ReqMode = "http"
service.ServiceDest[1].ReqMode = "http"
expected.ServiceDest[0].ReqMode = "http"
expected.ServiceDest[1].ReqMode = "http"
s.Len(*actual, 1)
s.Contains(*actual, service)
s.Contains(*actual, expected)
}

func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsEmptyIfServiceNameIsNotSet() {
Expand Down
Loading

0 comments on commit 344b365

Please sign in to comment.