Skip to content

Commit

Permalink
Merge pull request #754 from jcmoraisjr/jm-redirect
Browse files Browse the repository at this point in the history
Add server redirect options
  • Loading branch information
jcmoraisjr authored Mar 27, 2021
2 parents 9c04f61 + c905db5 commit 4203400
Show file tree
Hide file tree
Showing 12 changed files with 434 additions and 10 deletions.
26 changes: 26 additions & 0 deletions docs/content/en/docs/configuration/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,9 @@ The table below describes all supported configuration keys.
| [`secure-verify-hostname`](#secure-backend) | hostname | Backend | |
| [`server-alias`](#server-alias) | domain name | Host | |
| [`server-alias-regex`](#server-alias) | regex | Host | |
| [`server-redirect`](#server-redirect) | domain name | Host | |
| [`server-redirect-code`](#server-redirect) | http status code | Host | `302` |
| [`server-redirect-regex`](#server-redirect) | regex | Host | |
| [`service-upstream`](#service-upstream) | [true\|false] | Backend | `false` |
| [`session-cookie-dynamic`](#affinity) | [true\|false] | Backend | |
| [`session-cookie-keywords`](#affinity) | cookie options | Backend | `indirect nocache httponly` |
Expand Down Expand Up @@ -1967,6 +1970,29 @@ attribute in the same ACL, and any of them might be used to match SNI extensions

---

## Server redirect

| Configuration key | Scope | Default | Since |
|-------------------------|--------|---------|-------|
| `server-redirect` | `Host` | | v0.13 |
| `server-redirect-code` | `Host` | `302` | v0.13 |
| `server-redirect-regex` | `Host` | | v0.13 |

Configures hostname redirect. Requests that matches the configuration will be redirected
to the hostname of the ingress spec. Protocol, path and query string are preserved.

The same source domain can be configured just once, and a target domain can be assigned
just once as well, which means that this configuration can only be used on ingress
resources that defines just one hostname. The redirect configuration has the lesser
precedence, so if a source domain is also configured as a hostname in the ingress spec,
or as an alias using annotation, the redirect will not happen.

* `server-redirect`: Defines a source domain using hostname-like syntax, so wildcard domains can also be used.
* `server-redirect-regex`: Defines a POSIX extended regular expression used to match a source domain. The regex will be used verbatim, so add `^` and `$` if strict hostname is desired and escape `\.` dots in order to strictly match them.
* `server-redirect-code`: Which HTTP status code should be used in the redirect. A `302` response is used by default if not configured.

---

## Service upstream

| Configuration key | Scope | Default | Since |
Expand Down
19 changes: 19 additions & 0 deletions pkg/converters/ingress/annotations/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ func (c *updater) buildHostCertSigner(d *hostData) {
// just the warnings, ingress.syncIngress() has already added the domains
}

func (c *updater) buildHostRedirect(d *hostData) {
// TODO need a host<->host tracking if a target is found
redir := d.mapper.Get(ingtypes.HostServerRedirect)
if target := c.haproxy.Hosts().FindTargetRedirect(redir.Value, false); target != nil {
c.logger.Warn("ignoring redirect from '%s' on %v, it's already targeting to '%s'",
redir.Value, redir.Source, target.Hostname)
} else {
d.host.Redirect.RedirectHost = redir.Value
}
redirRegex := d.mapper.Get(ingtypes.HostServerRedirectRegex)
if target := c.haproxy.Hosts().FindTargetRedirect(redirRegex.Value, true); target != nil {
c.logger.Warn("ignoring regex redirect from '%s' on %v, it's already targeting to '%s'",
redirRegex.Value, redirRegex.Source, target.Hostname)
} else {
d.host.Redirect.RedirectHostRegex = redirRegex.Value
}
d.host.Redirect.RedirectCode = d.mapper.Get(ingtypes.HostServerRedirectCode).Int()
}

func (c *updater) buildHostSSLPassthrough(d *hostData) {
sslpassthrough := d.mapper.Get(ingtypes.HostSSLPassthrough)
if !sslpassthrough.Bool() {
Expand Down
93 changes: 93 additions & 0 deletions pkg/converters/ingress/annotations/host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,99 @@ import (
hatypes "github.com/jcmoraisjr/haproxy-ingress/pkg/haproxy/types"
)

func TestBuildHostRedirect(t *testing.T) {
testCases := []struct {
annPrev map[string]string
ann map[string]string
annDefault map[string]string
expected hatypes.HostRedirectConfig
logging string
}{
// 0
{
ann: map[string]string{
ingtypes.HostServerRedirect: "www.d.local",
},
expected: hatypes.HostRedirectConfig{RedirectHost: "www.d.local"},
},
// 1
{
ann: map[string]string{
ingtypes.HostServerRedirect: "www.d.local",
ingtypes.HostServerRedirectCode: "301",
},
expected: hatypes.HostRedirectConfig{RedirectHost: "www.d.local", RedirectCode: 301},
},
// 2
{
annPrev: map[string]string{
ingtypes.HostServerRedirect: "www.d.local",
},
ann: map[string]string{
ingtypes.HostServerRedirect: "www.d.local",
},
logging: `WARN ignoring redirect from 'www.d.local' on ingress 'default/ing1', it's already targeting to 'dprev.local'`,
},
// 3
{
annPrev: map[string]string{
ingtypes.HostServerRedirectRegex: "[a-z]+\\.d\\.local",
ingtypes.HostServerRedirectCode: "301",
},
ann: map[string]string{
ingtypes.HostServerRedirect: "www.d.local",
},
expected: hatypes.HostRedirectConfig{RedirectHost: "www.d.local"},
},
// 4
{
annPrev: map[string]string{
ingtypes.HostServerRedirectRegex: "[a-z]+\\.d\\.local",
},
ann: map[string]string{
ingtypes.HostServerRedirectRegex: "[a-z]+\\.d\\.local",
},
logging: `WARN ignoring regex redirect from '[a-z]+\.d\.local' on ingress 'default/ing1', it's already targeting to 'dprev.local'`,
},
// 5
{
ann: map[string]string{
ingtypes.HostServerRedirect: "*.d.local",
},
// haproxy/config's responsibility to convert wildcard hostnames to regex
expected: hatypes.HostRedirectConfig{RedirectHost: "*.d.local"},
},
// 6
{
annPrev: map[string]string{
ingtypes.HostServerRedirect: "*.d.local",
},
ann: map[string]string{
ingtypes.HostServerRedirect: "*.d.local",
},
logging: `WARN ignoring redirect from '*.d.local' on ingress 'default/ing1', it's already targeting to 'dprev.local'`,
},
}
sprev := &Source{Namespace: "prev", Name: "ingprev", Type: "ingress"}
source := &Source{Namespace: "default", Name: "ing1", Type: "ingress"}
for i, test := range testCases {
c := setup(t)
if test.annDefault == nil {
test.annDefault = map[string]string{}
}
dprev := c.createHostData(sprev, test.annPrev, test.annDefault)
d := c.createHostData(source, test.ann, test.annDefault)
dprev.host = c.haproxy.Hosts().AcquireHost("dprev.local")
d.host = c.haproxy.Hosts().AcquireHost("d.local")
updater := c.createUpdater()
updater.buildHostRedirect(dprev)
updater.buildHostRedirect(d)
c.compareObjects("host redirect", i, d.host.Redirect, test.expected)
c.logger.CompareLogging(test.logging)
c.teardown()
}
}

func TestTLSConfig(t *testing.T) {
testCases := []struct {
annDefault map[string]string
Expand Down
4 changes: 4 additions & 0 deletions pkg/converters/ingress/annotations/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func (c *updater) UpdateGlobalConfig(haproxyConfig haproxy.Config, mapper *Mappe
d.global.Master.WorkerMaxReloads = mapper.Get(ingtypes.GlobalWorkerMaxReloads).Int()
d.global.StrictHost = mapper.Get(ingtypes.GlobalStrictHost).Bool()
d.global.UseHTX = mapper.Get(ingtypes.GlobalUseHTX).Bool()
//
c.haproxy.Frontend().DefaultServerRedirectCode = mapper.Get(ingtypes.HostServerRedirectCode).Int()
//
c.buildGlobalAcme(d)
c.buildGlobalAuthProxy(d)
c.buildGlobalBind(d)
Expand Down Expand Up @@ -171,6 +174,7 @@ func (c *updater) UpdateHostConfig(host *hatypes.Host, mapper *Mapper) {
host.VarNamespace = mapper.Get(ingtypes.HostVarNamespace).Bool()
c.buildHostAuthTLS(data)
c.buildHostCertSigner(data)
c.buildHostRedirect(data)
c.buildHostSSLPassthrough(data)
c.buildHostTLSConfig(data)
}
Expand Down
11 changes: 6 additions & 5 deletions pkg/converters/ingress/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ const (

func createDefaults() map[string]string {
return map[string]string{
types.HostAuthTLSStrict: "false",
types.HostSSLCiphers: defaultSSLCiphers,
types.HostSSLCipherSuites: defaultSSLCipherSuites,
types.HostSSLOptionsHost: "",
types.HostTLSALPN: "h2,http/1.1",
types.HostAuthTLSStrict: "false",
types.HostServerRedirectCode: "302",
types.HostSSLCiphers: defaultSSLCiphers,
types.HostSSLCipherSuites: defaultSSLCipherSuites,
types.HostSSLOptionsHost: "",
types.HostTLSALPN: "h2,http/1.1",
//
types.BackBackendServerNaming: "sequence",
types.BackBackendServerSlotsInc: "1",
Expand Down
6 changes: 6 additions & 0 deletions pkg/converters/ingress/types/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const (
HostPathType = "path-type"
HostServerAlias = "server-alias"
HostServerAliasRegex = "server-alias-regex"
HostServerRedirect = "server-redirect"
HostServerRedirectCode = "server-redirect-code"
HostServerRedirectRegex = "server-redirect-regex"
HostSSLCiphers = "ssl-ciphers"
HostSSLCipherSuites = "ssl-cipher-suites"
HostSSLOptionsHost = "ssl-options-host"
Expand All @@ -48,6 +51,9 @@ var (
HostServerAlias: {},
HostPathType: {},
HostServerAliasRegex: {},
HostServerRedirect: {},
HostServerRedirectCode: {},
HostServerRedirectRegex: {},
HostSSLCiphers: {},
HostSSLCipherSuites: {},
HostSSLOptionsHost: {},
Expand Down
19 changes: 19 additions & 0 deletions pkg/haproxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package haproxy
import (
"fmt"
"reflect"
"strconv"
"strings"

"github.com/jinzhu/copier"
Expand Down Expand Up @@ -153,6 +154,8 @@ func (c *config) WriteFrontendMaps() error {
HTTPSSNIMap: mapBuilder.AddMap(mapsDir + "/_front_https_sni.map"),
//
RedirFromRootMap: mapBuilder.AddMap(mapsDir + "/_front_redir_fromroot.map"),
RedirSourceMap: mapBuilder.AddMap(mapsDir + "/_front_redir_source.map"),
RedirCodeMap: mapBuilder.AddMap(mapsDir + "/_front_redir_code.map"),
SSLPassthroughMap: mapBuilder.AddMap(mapsDir + "/_front_sslpassthrough.map"),
VarNamespaceMap: mapBuilder.AddMap(mapsDir + "/_front_namespace.map"),
//
Expand Down Expand Up @@ -212,6 +215,22 @@ func (c *config) WriteFrontendMaps() error {
fmaps.VarNamespaceMap.AddHostnamePathMapping(host.Hostname, path, ns)
}
}
var redirectCode string
if host.Redirect.RedirectCode > 0 && host.Redirect.RedirectCode != c.frontend.DefaultServerRedirectCode {
redirectCode = strconv.Itoa(host.Redirect.RedirectCode)
}
if host.Redirect.RedirectHost != "" {
fmaps.RedirSourceMap.AddHostnameMapping(host.Redirect.RedirectHost, host.Hostname)
if redirectCode != "" {
fmaps.RedirCodeMap.AddHostnameMapping(host.Redirect.RedirectHost, redirectCode)
}
}
if host.Redirect.RedirectHostRegex != "" {
fmaps.RedirSourceMap.AddHostnameMappingRegex(host.Redirect.RedirectHostRegex, host.Hostname)
if redirectCode != "" {
fmaps.RedirCodeMap.AddHostnameMappingRegex(host.Redirect.RedirectHostRegex, redirectCode)
}
}
if host.HasTLSAuth() {
fmaps.TLSAuthList.AddHostnameMapping(host.Hostname, "")
if !host.TLS.CAVerifyOptional {
Expand Down
Loading

0 comments on commit 4203400

Please sign in to comment.