From d60959ec98d46df61de6127464677fa571fd2dae Mon Sep 17 00:00:00 2001
From: Sotiris Nanopoulos
Date: Fri, 28 Jul 2023 12:02:14 -0400
Subject: [PATCH] Adds support for treating missing headers as empty (#5584)
`TreatMissingHeadersAsEmpty` specifies if the header match rule specified header does not exist,
this header value will be treated as empty. Defaults to false.
Unlike the underlying Envoy implementation this is **only** supported
for negative matches (e.g. NotContains, NotExact).
The reason that I implemented only for negative matches is that is
substantially simpler to reason about. I am open to implementing it
for all cases if you think it is going to be useful.
Signed-off-by: Sotiris Nanopoulos
---
Makefile | 4 +-
apis/projectcontour/v1/httpproxy.go | 9 ++
changelogs/unreleased/5584-davinci26-minor.md | 4 +
examples/contour/01-crds.yaml | 83 +++++++++++++++--
examples/render/contour-deployment.yaml | 83 +++++++++++++++--
.../render/contour-gateway-provisioner.yaml | 83 +++++++++++++++--
examples/render/contour-gateway.yaml | 83 +++++++++++++++--
examples/render/contour.yaml | 83 +++++++++++++++--
internal/dag/conditions.go | 22 +++--
internal/dag/dag.go | 12 ++-
internal/dag/httpproxy_processor.go | 3 +
internal/envoy/v3/route.go | 2 +
internal/envoy/v3/route_test.go | 48 ++++++++++
.../featuretests/v3/headercondition_test.go | 91 ++++++++++++++-----
internal/featuretests/v3/httpproxy.go | 18 ++--
.../docs/main/config/api-reference.html | 18 ++++
.../docs/main/config/request-routing.md | 11 ++-
.../httpproxy/header_condition_match_test.go | 64 +++++++++++++
18 files changed, 631 insertions(+), 90 deletions(-)
create mode 100644 changelogs/unreleased/5584-davinci26-minor.md
diff --git a/Makefile b/Makefile
index fa855eb79e8..70ea501f8e4 100644
--- a/Makefile
+++ b/Makefile
@@ -256,7 +256,7 @@ generate-crd-yaml:
generate-gateway-yaml:
@echo "Generating Gateway API CRD YAML documents..."
@GATEWAY_API_VERSION=$(GATEWAY_API_VERSION) ./hack/generate-gateway-yaml.sh
-
+
.PHONY: generate-api-docs
generate-api-docs:
@@ -306,7 +306,7 @@ setup-kind-cluster: ## Make a kind cluster for testing
install-contour-working: | setup-kind-cluster ## Install the local working directory version of Contour into a kind cluster
./test/scripts/install-contour-working.sh
-.PHONY: install-contour-release
+.PHONY: install-contour-release
install-contour-release: | setup-kind-cluster ## Install the release version of Contour in CONTOUR_UPGRADE_FROM_VERSION, defaults to latest
./test/scripts/install-contour-release.sh $(CONTOUR_UPGRADE_FROM_VERSION)
diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go
index c05da51adef..02f6cd4c3b6 100644
--- a/apis/projectcontour/v1/httpproxy.go
+++ b/apis/projectcontour/v1/httpproxy.go
@@ -90,6 +90,8 @@ type MatchCondition struct {
// HeaderMatchCondition specifies how to conditionally match against HTTP
// headers. The Name field is required, only one of Present, NotPresent,
// Contains, NotContains, Exact, NotExact and Regex can be set.
+// For negative matching rules only (e.g. NotContains or NotExact) you can set
+// TreatMissingAsEmpty.
// IgnoreCase has no effect for Regex.
type HeaderMatchCondition struct {
// Name is the name of the header to match against. Name is required.
@@ -137,6 +139,13 @@ type HeaderMatchCondition struct {
// value.
// +optional
Regex string `json:"regex,omitempty"`
+
+ // TreatMissingAsEmpty specifies if the header match rule specified header
+ // does not exist, this header value will be treated as empty. Defaults to false.
+ // Unlike the underlying Envoy implementation this is **only** supported for
+ // negative matches (e.g. NotContains, NotExact).
+ // +optional
+ TreatMissingAsEmpty bool `json:"treatMissingAsEmpty,omitempty"`
}
// QueryParameterMatchCondition specifies how to conditionally match against HTTP
diff --git a/changelogs/unreleased/5584-davinci26-minor.md b/changelogs/unreleased/5584-davinci26-minor.md
new file mode 100644
index 00000000000..c9988ac1bed
--- /dev/null
+++ b/changelogs/unreleased/5584-davinci26-minor.md
@@ -0,0 +1,4 @@
+## Adds support for treating missing headers as empty when they are not present as part of header matching
+
+`TreatMissingAsEmpty` specifies if the header match rule specified header does not exist, this header value will be treated as empty. Defaults to false.
+Unlike the underlying Envoy implementation this is **only** supported for negative matches (e.g. NotContains, NotExact).
diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml
index bcc1a3eb050..68283476307 100644
--- a/examples/contour/01-crds.yaml
+++ b/examples/contour/01-crds.yaml
@@ -727,8 +727,10 @@ spec:
headers. The Name field is required, only
one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect for
- Regex.
+ can be set. For negative matching rules
+ only (e.g. NotContains or NotExact) you
+ can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies a substring
@@ -783,6 +785,16 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies
+ if the header match rule specified
+ header does not exist, this header
+ value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy
+ implementation this is **only** supported
+ for negative matches (e.g. NotContains,
+ NotExact).
+ type: boolean
required:
- name
type: object
@@ -4121,8 +4133,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -4180,6 +4194,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -4978,6 +5003,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5140,6 +5173,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5670,8 +5711,10 @@ spec:
is required, only one of Present,
NotPresent, Contains, NotContains,
Exact, NotExact and Regex can be
- set. IgnoreCase has no effect for
- Regex.
+ set. For negative matching rules
+ only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty.
+ IgnoreCase has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -5732,6 +5775,17 @@ spec:
regular expression pattern that
must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is
+ **only** supported for negative
+ matches (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -7039,8 +7093,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -7098,6 +7154,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml
index 4697f85d47d..c488e79ade6 100644
--- a/examples/render/contour-deployment.yaml
+++ b/examples/render/contour-deployment.yaml
@@ -940,8 +940,10 @@ spec:
headers. The Name field is required, only
one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect for
- Regex.
+ can be set. For negative matching rules
+ only (e.g. NotContains or NotExact) you
+ can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies a substring
@@ -996,6 +998,16 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies
+ if the header match rule specified
+ header does not exist, this header
+ value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy
+ implementation this is **only** supported
+ for negative matches (e.g. NotContains,
+ NotExact).
+ type: boolean
required:
- name
type: object
@@ -4334,8 +4346,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -4393,6 +4407,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5191,6 +5216,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5353,6 +5386,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5883,8 +5924,10 @@ spec:
is required, only one of Present,
NotPresent, Contains, NotContains,
Exact, NotExact and Regex can be
- set. IgnoreCase has no effect for
- Regex.
+ set. For negative matching rules
+ only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty.
+ IgnoreCase has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -5945,6 +5988,17 @@ spec:
regular expression pattern that
must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is
+ **only** supported for negative
+ matches (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -7252,8 +7306,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -7311,6 +7367,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml
index 6acc7139209..d2515bc2585 100644
--- a/examples/render/contour-gateway-provisioner.yaml
+++ b/examples/render/contour-gateway-provisioner.yaml
@@ -741,8 +741,10 @@ spec:
headers. The Name field is required, only
one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect for
- Regex.
+ can be set. For negative matching rules
+ only (e.g. NotContains or NotExact) you
+ can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies a substring
@@ -797,6 +799,16 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies
+ if the header match rule specified
+ header does not exist, this header
+ value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy
+ implementation this is **only** supported
+ for negative matches (e.g. NotContains,
+ NotExact).
+ type: boolean
required:
- name
type: object
@@ -4135,8 +4147,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -4194,6 +4208,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -4992,6 +5017,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5154,6 +5187,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5684,8 +5725,10 @@ spec:
is required, only one of Present,
NotPresent, Contains, NotContains,
Exact, NotExact and Regex can be
- set. IgnoreCase has no effect for
- Regex.
+ set. For negative matching rules
+ only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty.
+ IgnoreCase has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -5746,6 +5789,17 @@ spec:
regular expression pattern that
must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is
+ **only** supported for negative
+ matches (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -7053,8 +7107,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -7112,6 +7168,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml
index c1730d9cc16..29d943daef8 100644
--- a/examples/render/contour-gateway.yaml
+++ b/examples/render/contour-gateway.yaml
@@ -946,8 +946,10 @@ spec:
headers. The Name field is required, only
one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect for
- Regex.
+ can be set. For negative matching rules
+ only (e.g. NotContains or NotExact) you
+ can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies a substring
@@ -1002,6 +1004,16 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies
+ if the header match rule specified
+ header does not exist, this header
+ value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy
+ implementation this is **only** supported
+ for negative matches (e.g. NotContains,
+ NotExact).
+ type: boolean
required:
- name
type: object
@@ -4340,8 +4352,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -4399,6 +4413,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5197,6 +5222,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5359,6 +5392,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5889,8 +5930,10 @@ spec:
is required, only one of Present,
NotPresent, Contains, NotContains,
Exact, NotExact and Regex can be
- set. IgnoreCase has no effect for
- Regex.
+ set. For negative matching rules
+ only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty.
+ IgnoreCase has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -5951,6 +5994,17 @@ spec:
regular expression pattern that
must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is
+ **only** supported for negative
+ matches (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -7258,8 +7312,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -7317,6 +7373,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml
index 907bb6a57f8..5745b5e9c95 100644
--- a/examples/render/contour.yaml
+++ b/examples/render/contour.yaml
@@ -940,8 +940,10 @@ spec:
headers. The Name field is required, only
one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect for
- Regex.
+ can be set. For negative matching rules
+ only (e.g. NotContains or NotExact) you
+ can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies a substring
@@ -996,6 +998,16 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies
+ if the header match rule specified
+ header does not exist, this header
+ value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy
+ implementation this is **only** supported
+ for negative matches (e.g. NotContains,
+ NotExact).
+ type: boolean
required:
- name
type: object
@@ -4334,8 +4346,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -4393,6 +4407,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5191,6 +5216,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5353,6 +5386,14 @@ spec:
description: Regex specifies a regular expression
pattern that must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty specifies if the
+ header match rule specified header does not exist,
+ this header value will be treated as empty. Defaults
+ to false. Unlike the underlying Envoy implementation
+ this is **only** supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -5883,8 +5924,10 @@ spec:
is required, only one of Present,
NotPresent, Contains, NotContains,
Exact, NotExact and Regex can be
- set. IgnoreCase has no effect for
- Regex.
+ set. For negative matching rules
+ only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty.
+ IgnoreCase has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -5945,6 +5988,17 @@ spec:
regular expression pattern that
must match the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is
+ **only** supported for negative
+ matches (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
@@ -7252,8 +7306,10 @@ spec:
HTTP headers. The Name field is required,
only one of Present, NotPresent, Contains,
NotContains, Exact, NotExact and Regex
- can be set. IgnoreCase has no effect
- for Regex.
+ can be set. For negative matching
+ rules only (e.g. NotContains or NotExact)
+ you can set TreatMissingAsEmpty. IgnoreCase
+ has no effect for Regex.
properties:
contains:
description: Contains specifies
@@ -7311,6 +7367,17 @@ spec:
expression pattern that must match
the header value.
type: string
+ treatMissingAsEmpty:
+ description: TreatMissingAsEmpty
+ specifies if the header match
+ rule specified header does not
+ exist, this header value will
+ be treated as empty. Defaults
+ to false. Unlike the underlying
+ Envoy implementation this is **only**
+ supported for negative matches
+ (e.g. NotContains, NotExact).
+ type: boolean
required:
- name
type: object
diff --git a/internal/dag/conditions.go b/internal/dag/conditions.go
index f51aa7ef029..19f5c62d718 100644
--- a/internal/dag/conditions.go
+++ b/internal/dag/conditions.go
@@ -181,11 +181,12 @@ func headerMatchConditions(conditions []contour_api_v1.HeaderMatchCondition) []H
})
case cond.NotContains != "":
hc = append(hc, HeaderMatchCondition{
- Name: cond.Name,
- Value: cond.NotContains,
- MatchType: HeaderMatchTypeContains,
- Invert: true,
- IgnoreCase: cond.IgnoreCase,
+ Name: cond.Name,
+ Value: cond.NotContains,
+ MatchType: HeaderMatchTypeContains,
+ Invert: true,
+ IgnoreCase: cond.IgnoreCase,
+ TreatMissingAsEmpty: cond.TreatMissingAsEmpty,
})
case cond.Exact != "":
hc = append(hc, HeaderMatchCondition{
@@ -196,11 +197,12 @@ func headerMatchConditions(conditions []contour_api_v1.HeaderMatchCondition) []H
})
case cond.NotExact != "":
hc = append(hc, HeaderMatchCondition{
- Name: cond.Name,
- Value: cond.NotExact,
- MatchType: HeaderMatchTypeExact,
- Invert: true,
- IgnoreCase: cond.IgnoreCase,
+ Name: cond.Name,
+ Value: cond.NotExact,
+ MatchType: HeaderMatchTypeExact,
+ Invert: true,
+ IgnoreCase: cond.IgnoreCase,
+ TreatMissingAsEmpty: cond.TreatMissingAsEmpty,
})
case cond.Regex != "":
hc = append(hc, HeaderMatchCondition{
diff --git a/internal/dag/dag.go b/internal/dag/dag.go
index 30edfbc5bca..de934223818 100644
--- a/internal/dag/dag.go
+++ b/internal/dag/dag.go
@@ -140,11 +140,12 @@ const (
// HeaderMatchCondition matches request headers by MatchType
type HeaderMatchCondition struct {
- Name string
- Value string
- MatchType string
- Invert bool
- IgnoreCase bool
+ Name string
+ Value string
+ MatchType string
+ Invert bool
+ IgnoreCase bool
+ TreatMissingAsEmpty bool
}
func (hc *HeaderMatchCondition) String() string {
@@ -152,6 +153,7 @@ func (hc *HeaderMatchCondition) String() string {
"name=" + hc.Name,
"value=" + hc.Value,
"matchtype=", hc.MatchType,
+ "TreatMissingAsEmpty=", strconv.FormatBool(hc.TreatMissingAsEmpty),
"invert=", strconv.FormatBool(hc.Invert),
"ignorecase=", strconv.FormatBool(hc.IgnoreCase),
}, "&")
diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go
index fd7124df3e9..6b5caea4807 100644
--- a/internal/dag/httpproxy_processor.go
+++ b/internal/dag/httpproxy_processor.go
@@ -1722,6 +1722,9 @@ func includeMatchConditionsIdentical(includeConds []contour_api_v1.MatchConditio
if includeHeaderConds[i].Invert != includeHeaderConds[j].Invert {
return includeHeaderConds[i].Invert
}
+ if includeHeaderConds[i].TreatMissingAsEmpty != includeHeaderConds[j].TreatMissingAsEmpty {
+ return includeHeaderConds[i].TreatMissingAsEmpty
+ }
if includeHeaderConds[i].Name != includeHeaderConds[j].Name {
return includeHeaderConds[i].Name < includeHeaderConds[j].Name
}
diff --git a/internal/envoy/v3/route.go b/internal/envoy/v3/route.go
index 3d9f06f59ce..205d7c54ee5 100644
--- a/internal/envoy/v3/route.go
+++ b/internal/envoy/v3/route.go
@@ -736,6 +736,8 @@ func headerMatcher(headers []dag.HeaderMatchCondition) []*envoy_route_v3.HeaderM
header := &envoy_route_v3.HeaderMatcher{
Name: h.Name,
InvertMatch: h.Invert,
+ // We only want to turn on TreatMissingHeaderAsEmpty on invert matches
+ TreatMissingHeaderAsEmpty: h.Invert && h.TreatMissingAsEmpty,
}
switch h.MatchType {
diff --git a/internal/envoy/v3/route_test.go b/internal/envoy/v3/route_test.go
index 212456ccb9e..102924d7654 100644
--- a/internal/envoy/v3/route_test.go
+++ b/internal/envoy/v3/route_test.go
@@ -1926,6 +1926,31 @@ func TestRouteMatch(t *testing.T) {
}},
},
},
+ "notcontains match -- treat missing as empty": {
+ route: &dag.Route{
+ HeaderMatchConditions: []dag.HeaderMatchCondition{{
+ Name: "x-header",
+ Value: "foo",
+ MatchType: "contains",
+ Invert: true,
+ TreatMissingAsEmpty: true,
+ }},
+ },
+ want: &envoy_route_v3.RouteMatch{
+ Headers: []*envoy_route_v3.HeaderMatcher{{
+ Name: "x-header",
+ InvertMatch: true,
+ TreatMissingHeaderAsEmpty: true,
+ HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{
+ StringMatch: &matcher.StringMatcher{
+ MatchPattern: &matcher.StringMatcher_Contains{
+ Contains: "foo",
+ },
+ },
+ },
+ }},
+ },
+ },
"path prefix string prefix": {
route: &dag.Route{
PathMatchCondition: &dag.PrefixMatchCondition{
@@ -2127,6 +2152,29 @@ func TestRouteMatch(t *testing.T) {
}},
},
},
+ "header not exact -- treat missing as empty": {
+ route: &dag.Route{
+ HeaderMatchConditions: []dag.HeaderMatchCondition{{
+ Name: "x-header-foo",
+ MatchType: dag.HeaderMatchTypeExact,
+ Value: "bar",
+ Invert: true,
+ TreatMissingAsEmpty: true,
+ }},
+ },
+ want: &envoy_route_v3.RouteMatch{
+ Headers: []*envoy_route_v3.HeaderMatcher{{
+ Name: "x-header-foo",
+ InvertMatch: true,
+ TreatMissingHeaderAsEmpty: true,
+ HeaderMatchSpecifier: &envoy_route_v3.HeaderMatcher_StringMatch{
+ StringMatch: &matcher.StringMatcher{
+ MatchPattern: &matcher.StringMatcher_Exact{Exact: "bar"},
+ },
+ },
+ }},
+ },
+ },
"header contains": {
route: &dag.Route{
HeaderMatchConditions: []dag.HeaderMatchCondition{{
diff --git a/internal/featuretests/v3/headercondition_test.go b/internal/featuretests/v3/headercondition_test.go
index dc35f691ed9..13ec7289152 100644
--- a/internal/featuretests/v3/headercondition_test.go
+++ b/internal/featuretests/v3/headercondition_test.go
@@ -55,25 +55,48 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
Name: "svc1",
Port: 80,
}},
- }, {
- Conditions: matchconditions(
- prefixMatchCondition("/"),
- headerContainsMatchCondition("x-header", "abc", false),
- ),
- Services: []contour_api_v1.Service{{
- Name: "svc2",
- Port: 80,
- }},
- }, {
- Conditions: matchconditions(
- prefixMatchCondition("/blog"),
- headerContainsMatchCondition("x-header", "abc", false),
- ),
- Services: []contour_api_v1.Service{{
- Name: "svc3",
- Port: 80,
- }},
- }},
+ },
+ {
+ Conditions: matchconditions(
+ prefixMatchCondition("/"),
+ headerContainsMatchCondition("x-header", "abc", false),
+ ),
+ Services: []contour_api_v1.Service{{
+ Name: "svc2",
+ Port: 80,
+ }},
+ },
+ {
+ Conditions: matchconditions(
+ prefixMatchCondition("/blog"),
+ headerContainsMatchCondition("x-header", "abc", false),
+ ),
+ Services: []contour_api_v1.Service{{
+ Name: "svc3",
+ Port: 80,
+ }},
+ },
+ {
+ Conditions: matchconditions(
+ prefixMatchCondition("/blog"),
+ headerNotExactMatchCondition("x-beta-release", "true", false, true),
+ ),
+ Services: []contour_api_v1.Service{{
+ Name: "svc2",
+ Port: 80,
+ }},
+ },
+ {
+ Conditions: matchconditions(
+ prefixMatchCondition("/blog"),
+ headerNotContainsMatchCondition("x-beta-release", "t", false, true),
+ ),
+ Services: []contour_api_v1.Service{{
+ Name: "svc2",
+ Port: 80,
+ }},
+ },
+ },
},
}
rh.OnAdd(proxy1)
@@ -82,6 +105,26 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
Resources: resources(t,
envoy_v3.RouteConfiguration("ingress_http",
envoy_v3.VirtualHost("hello.world",
+ &envoy_route_v3.Route{
+ Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{
+ Name: "x-beta-release",
+ Value: "true",
+ MatchType: "exact",
+ Invert: true,
+ TreatMissingAsEmpty: true,
+ }),
+ Action: routeCluster("default/svc2/80/da39a3ee5e"),
+ },
+ &envoy_route_v3.Route{
+ Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{
+ Name: "x-beta-release",
+ Value: "t",
+ MatchType: "contains",
+ Invert: true,
+ TreatMissingAsEmpty: true,
+ }),
+ Action: routeCluster("default/svc2/80/da39a3ee5e"),
+ },
&envoy_route_v3.Route{
Match: routePrefixWithHeaderConditions("/blog", dag.HeaderMatchCondition{
Name: "x-header",
@@ -121,7 +164,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
}, {
Conditions: matchconditions(
prefixMatchCondition("/"),
- headerNotContainsMatchCondition("x-header", "123", false),
+ headerNotContainsMatchCondition("x-header", "123", false, false),
),
Services: []contour_api_v1.Service{{
Name: "svc2",
@@ -130,7 +173,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
}, {
Conditions: matchconditions(
prefixMatchCondition("/blog"),
- headerNotContainsMatchCondition("x-header", "abc", false),
+ headerNotContainsMatchCondition("x-header", "abc", false, false),
),
Services: []contour_api_v1.Service{{
Name: "svc3",
@@ -247,7 +290,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
}, {
Conditions: matchconditions(
prefixMatchCondition("/"),
- headerNotExactMatchCondition("x-header", "abc", false),
+ headerNotExactMatchCondition("x-header", "abc", false, false),
),
Services: []contour_api_v1.Service{{
Name: "svc2",
@@ -256,7 +299,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
}, {
Conditions: matchconditions(
prefixMatchCondition("/blog"),
- headerNotExactMatchCondition("x-header", "123", false),
+ headerNotExactMatchCondition("x-header", "123", false, false),
),
Services: []contour_api_v1.Service{{
Name: "svc3",
@@ -438,7 +481,7 @@ func TestConditions_ContainsHeader_HTTProxy(t *testing.T) {
{
Conditions: matchconditions(
prefixMatchCondition("/"),
- headerNotContainsMatchCondition("x-header", "abc", false),
+ headerNotContainsMatchCondition("x-header", "abc", false, false),
),
Services: []contour_api_v1.Service{{
Name: "svc2",
diff --git a/internal/featuretests/v3/httpproxy.go b/internal/featuretests/v3/httpproxy.go
index 0796262ec39..d46ed1a1ba9 100644
--- a/internal/featuretests/v3/httpproxy.go
+++ b/internal/featuretests/v3/httpproxy.go
@@ -39,12 +39,13 @@ func headerContainsMatchCondition(name, value string, ignoreCase bool) contour_a
}
}
-func headerNotContainsMatchCondition(name, value string, ignoreCase bool) contour_api_v1.MatchCondition {
+func headerNotContainsMatchCondition(name, value string, ignoreCase, treatMissingAsEmpty bool) contour_api_v1.MatchCondition {
return contour_api_v1.MatchCondition{
Header: &contour_api_v1.HeaderMatchCondition{
- Name: name,
- NotContains: value,
- IgnoreCase: ignoreCase,
+ Name: name,
+ NotContains: value,
+ IgnoreCase: ignoreCase,
+ TreatMissingAsEmpty: treatMissingAsEmpty,
},
}
}
@@ -59,12 +60,13 @@ func headerExactMatchCondition(name, value string, ignoreCase bool) contour_api_
}
}
-func headerNotExactMatchCondition(name, value string, ignoreCase bool) contour_api_v1.MatchCondition {
+func headerNotExactMatchCondition(name, value string, ignoreCase bool, treatMissingAsEmpty bool) contour_api_v1.MatchCondition {
return contour_api_v1.MatchCondition{
Header: &contour_api_v1.HeaderMatchCondition{
- Name: name,
- NotExact: value,
- IgnoreCase: ignoreCase,
+ Name: name,
+ NotExact: value,
+ IgnoreCase: ignoreCase,
+ TreatMissingAsEmpty: treatMissingAsEmpty,
},
}
}
diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html
index b7b58ff6866..fed7ba6810e 100644
--- a/site/content/docs/main/config/api-reference.html
+++ b/site/content/docs/main/config/api-reference.html
@@ -1887,6 +1887,8 @@ HeaderMatchCondition specifies how to conditionally match against HTTP
headers. The Name field is required, only one of Present, NotPresent,
Contains, NotContains, Exact, NotExact and Regex can be set.
+For negative matching rules only (e.g. NotContains or NotExact) you can set
+TreatMissingAsEmpty.
IgnoreCase has no effect for Regex.