Skip to content

Commit

Permalink
add path-type-order global config
Browse files Browse the repository at this point in the history
`path-type-order` creates a list of priority path types. First types has higher precedence to choose the correct path in the case distinct paths of distinct types overlaps.
  • Loading branch information
jcmoraisjr committed Sep 9, 2020
1 parent abcc471 commit 34d736f
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 12 deletions.
13 changes: 10 additions & 3 deletions docs/content/en/docs/configuration/keys.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ The table below describes all supported configuration keys.
| [`oauth-headers`](#oauth) | `<header>:<var>,...` | Backend | |
| [`oauth-uri-prefix`](#oauth) | URI prefix | Backend | |
| [`path-type`](#path-type) | path matching type | Host | `begin` |
| [`path-type-order`](#path-type) | comma-separated path type list | Global | `exact,prefix,begin,regex` |
| [`prometheus-port`](#bind-port) | port number | Global | |
| [`proxy-body-size`](#proxy-body-size) | size (bytes) | Backend | unlimited |
| [`proxy-protocol`](#proxy-protocol) | [v1\|v2\|v2-ssl\|v2-ssl-cn] | Backend | |
Expand Down Expand Up @@ -1300,13 +1301,19 @@ See also:

## Path type

| Configuration key | Scope | Default | Since |
|-------------------|--------|---------|-------|
| `path-type` | `Host` | `begin` | v0.11 |
| Configuration key | Scope | Default | Since |
|-------------------|----------|----------------------------|-------|
| `path-type` | `Host` | `begin` | v0.11 |
| `path-type-order` | `Global` | `exact,prefix,begin,regex` | v0.11 |

Defines how the path of an incoming request should match a declared path in the ingress object.

* `path-type`: Configures the path type. Case insensitive, so `Begin` and `begin` configures the same path type option. The ingress spec has priority, this option will only be used if the `pathType` attribute from the ingress spec is declared as `ImplementationSpecific` or is not declared.
* `path-type-order`: Defines a comma-separated list of priority path types. First types has higher precedence, which means that if two distinct paths of two distinct types overlaps, the first in the list will be used to handle the request. All path types must be provided. Case insensitive, use all path types in lowercase.

{{% alert title="Warning" color="warning" %}}
Wildcard hostnames and alias-regex match incoming requests using the regex path type, even if the path itself has a distinct one. Changing the precedence order of paths also changes the precedence order of hostnames. See also [server-alias-regex](#server-alias) and [strict host](#strict-host).
{{% /alert %}}

Supported `path-type` values:

Expand Down
27 changes: 27 additions & 0 deletions pkg/converters/ingress/annotations/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,33 @@ func (c *updater) buildGlobalBind(d *globalData) {
}
}

func (c *updater) buildGlobalPathTypeOrder(d *globalData) {
matchTypes := make(map[hatypes.MatchType]struct{}, len(hatypes.DefaultMatchOrder))
for _, match := range hatypes.DefaultMatchOrder {
matchTypes[match] = struct{}{}
}
orderStr := d.mapper.Get(ingtypes.GlobalPathTypeOrder).Value
orderSlice := strings.Split(orderStr, ",")
order := make([]hatypes.MatchType, len(orderSlice))
d.global.MatchOrder = hatypes.DefaultMatchOrder
for i, matchStr := range orderSlice {
// 1) filling final `order` slice, 2) identifying invalid and 3) not used matches
match := hatypes.MatchType(matchStr)
if _, found := matchTypes[match]; found {
order[i] = match
delete(matchTypes, match)
} else {
c.logger.Warn("invalid or duplicated path type '%s', using default order %v", matchStr, hatypes.DefaultMatchOrder)
return
}
}
if len(matchTypes) > 0 {
c.logger.Warn("all path types should be used in %v, using default order %v", order, hatypes.DefaultMatchOrder)
return
}
d.global.MatchOrder = order
}

func (c *updater) buildGlobalProc(d *globalData) {
balance := d.mapper.Get(ingtypes.GlobalNbprocBalance).Int()
if balance < 1 {
Expand Down
46 changes: 46 additions & 0 deletions pkg/converters/ingress/annotations/global_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,3 +383,49 @@ func TestDisableCpuMap(t *testing.T) {
c.teardown()
}
}

func TestPathTypeOrder(t *testing.T) {
testCases := []struct {
order string
expected []hatypes.MatchType
logging string
}{
// 0
{
order: "regex,begin",
expected: hatypes.DefaultMatchOrder,
logging: "WARN all path types should be used in [regex begin], using default order [exact prefix begin regex]",
},
// 1
{
order: "regex,begin,regex,begin",
expected: hatypes.DefaultMatchOrder,
logging: "WARN invalid or duplicated path type 'regex', using default order [exact prefix begin regex]",
},
// 2
{
order: "regex,begin,prefix,Exact",
expected: hatypes.DefaultMatchOrder,
logging: "WARN invalid or duplicated path type 'Exact', using default order [exact prefix begin regex]",
},
// 3
{
order: "prefix,invalid",
expected: hatypes.DefaultMatchOrder,
logging: "WARN invalid or duplicated path type 'invalid', using default order [exact prefix begin regex]",
},
// 4
{
order: "regex,begin,prefix,exact",
expected: []hatypes.MatchType{hatypes.MatchRegex, hatypes.MatchBegin, hatypes.MatchPrefix, hatypes.MatchExact},
},
}
for i, test := range testCases {
c := setup(t)
d := c.createGlobalData(map[string]string{ingtypes.GlobalPathTypeOrder: test.order})
c.createUpdater().buildGlobalPathTypeOrder(d)
c.compareObjects("path type order", i, d.global.MatchOrder, test.expected)
c.logger.CompareLogging(test.logging)
c.teardown()
}
}
1 change: 1 addition & 0 deletions pkg/converters/ingress/annotations/updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func (c *updater) UpdateGlobalConfig(haproxyConfig haproxy.Config, mapper *Mappe
c.buildGlobalForwardFor(d)
c.buildGlobalHTTPStoHTTP(d)
c.buildGlobalModSecurity(d)
c.buildGlobalPathTypeOrder(d)
c.buildGlobalProc(d)
c.buildGlobalSSL(d)
c.buildGlobalStats(d)
Expand Down
1 change: 1 addition & 0 deletions pkg/converters/ingress/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func createDefaults() map[string]string {
types.GlobalNbprocBalance: "1",
types.GlobalNbthread: "2",
types.GlobalNoTLSRedirectLocations: "/.well-known/acme-challenge",
types.GlobalPathTypeOrder: "exact,prefix,begin,regex",
types.GlobalSSLDHDefaultMaxSize: "2048",
types.GlobalSSLHeadersPrefix: "X-SSL",
types.GlobalSSLOptions: defaultSSLOptions,
Expand Down
1 change: 1 addition & 0 deletions pkg/converters/ingress/types/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const (
GlobalNbprocSSL = "nbproc-ssl"
GlobalNbthread = "nbthread"
GlobalNoTLSRedirectLocations = "no-tls-redirect-locations"
GlobalPathTypeOrder = "path-type-order"
GlobalPrometheusPort = "prometheus-port"
GlobalSSLDHDefaultMaxSize = "ssl-dh-default-max-size"
GlobalSSLDHParam = "ssl-dh-param"
Expand Down
11 changes: 2 additions & 9 deletions pkg/haproxy/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,10 @@ type config struct {
type options struct {
mapsTemplate *template.Config
mapsDir string
matchOrder []hatypes.MatchType
shardCount int
}

func createConfig(options options) *config {
options.matchOrder = []hatypes.MatchType{
hatypes.MatchExact,
hatypes.MatchPrefix,
hatypes.MatchBegin,
hatypes.MatchRegex,
}
if options.mapsTemplate == nil {
options.mapsTemplate = template.CreateConfig()
}
Expand Down Expand Up @@ -152,7 +145,7 @@ func (c *config) WriteFrontendMaps() error {
// hosts are clean, maps are updated
return nil
}
mapBuilder := hatypes.CreateMaps(c.options.matchOrder)
mapBuilder := hatypes.CreateMaps(c.global.MatchOrder)
mapsDir := c.options.mapsDir
fmaps := &hatypes.FrontendMaps{
HTTPHostMap: mapBuilder.AddMap(mapsDir + "/_front_http_host.map"),
Expand Down Expand Up @@ -313,7 +306,7 @@ func (c *config) WriteBackendMaps() error {
// backends are clean, maps are updated
return nil
}
mapBuilder := hatypes.CreateMaps(c.options.matchOrder)
mapBuilder := hatypes.CreateMaps(c.global.MatchOrder)
for _, backend := range c.backends.ItemsAdd() {
if backend.NeedACL() {
mapsPrefix := c.options.mapsDir + "/_back_" + backend.ID
Expand Down
1 change: 1 addition & 0 deletions pkg/haproxy/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3095,6 +3095,7 @@ func (c *testConfig) configGlobal(global *hatypes.Global) {
global.Bind.HTTPSBind = ":443"
global.Cookie.Key = "Ingress"
global.Healthz.Port = 10253
global.MatchOrder = hatypes.DefaultMatchOrder
global.MaxConn = 2000
global.SSL.ALPN = "h2,http/1.1"
global.SSL.BackendCiphers = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256"
Expand Down
4 changes: 4 additions & 0 deletions pkg/haproxy/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type Global struct {
LoadServerState bool
AdminSocket string
Healthz HealthzConfig
MatchOrder []MatchType
Prometheus PromConfig
Stats StatsConfig
StrictHost bool
Expand Down Expand Up @@ -342,6 +343,9 @@ const (
MatchEmpty = MatchType("")
)

// DefaultMatchOrder ...
var DefaultMatchOrder = []MatchType{MatchExact, MatchPrefix, MatchBegin, MatchRegex}

// PathLink is a unique identifier of a request
// configuration. Several request based configurations
// uses this identifier to distinguish if an acl should
Expand Down

0 comments on commit 34d736f

Please sign in to comment.