Header manip and validation added for ingress-gateway entries

This commit is contained in:
Paul Banks 2021-07-13 12:13:18 +01:00
parent 6cac30aa22
commit 46e4041283
4 changed files with 219 additions and 0 deletions

View File

@ -1461,3 +1461,36 @@ func IsProtocolHTTPLike(protocol string) bool {
return false
}
}
// HTTPHeaderModifiers is a set of rules for HTTP header modification that
// should be performed by proxies as the request passes through them. It can
// operate on either request or response headers depending on the context in
// which it is used.
type HTTPHeaderModifiers struct {
// Add is a set of name -> value pairs that should be appended to the request
// or response (i.e. allowing duplicates if the same header already exists).
Add map[string]string `json:",omitempty"`
// Set is a set of name -> value pairs that should be added to the request or
// response, overwriting any existing header values of the same name.
Set map[string]string `json:",omitempty"`
// Remove is the set of header names that should be stripped from the request
// or response.
Remove []string `json:",omitempty"`
}
func (m *HTTPHeaderModifiers) Validate(protocol string) error {
if m == nil {
// Empty is always valid
return nil
}
if len(m.Add) == 0 && len(m.Set) == 0 && len(m.Remove) == 0 {
return nil
}
if !IsProtocolHTTPLike(protocol) {
// Non nil but context is not an httpish protocol
return fmt.Errorf("only valid for http, http2 and grpc protocols")
}
return nil
}

View File

@ -75,6 +75,10 @@ type IngressService struct {
// using a "tcp" listener.
Hosts []string
// Allow HTTP header manipulation to be configured.
RequestHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"request_headers"`
ResponseHeaders *HTTPHeaderModifiers `json:",omitempty" alias:"response_headers"`
Meta map[string]string `json:",omitempty"`
EnterpriseMeta `hcl:",squash" mapstructure:",squash"`
}
@ -169,6 +173,13 @@ func (e *IngressGatewayConfigEntry) Validate() error {
return fmt.Errorf("Services[%d].%v", i, err)
}
if err := s.RequestHeaders.Validate(listener.Protocol); err != nil {
return fmt.Errorf("request headers %s (service %q on listener on port %d)", err, s.Name, listener.Port)
}
if err := s.ResponseHeaders.Validate(listener.Protocol); err != nil {
return fmt.Errorf("response headers %s (service %q on listener on port %d)", err, s.Name, listener.Port)
}
if listener.Protocol == "tcp" {
if s.Name == WildcardSpecifier {
return fmt.Errorf("Wildcard service name is only valid for protocol = 'http' (listener on port %d)", listener.Port)

View File

@ -394,6 +394,135 @@ func TestIngressGatewayConfigEntry(t *testing.T) {
},
validateErr: `Host '*' is not allowed when TLS is enabled, all hosts must be valid DNS records to add as a DNSSAN`,
},
"request header manip allowed for http(ish) protocol": {
entry: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "http",
Services: []IngressService{
{
Name: "web",
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{"x-foo": "bar"},
},
},
},
},
{
Port: 2222,
Protocol: "http2",
Services: []IngressService{
{
Name: "web2",
ResponseHeaders: &HTTPHeaderModifiers{
Set: map[string]string{"x-foo": "bar"},
},
},
},
},
{
Port: 3333,
Protocol: "grpc",
Services: []IngressService{
{
Name: "api",
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-grpc-internal"},
},
},
},
},
},
},
// Unchanged
expected: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "http",
Services: []IngressService{
{
Name: "web",
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{"x-foo": "bar"},
},
},
},
},
{
Port: 2222,
Protocol: "http2",
Services: []IngressService{
{
Name: "web2",
ResponseHeaders: &HTTPHeaderModifiers{
Set: map[string]string{"x-foo": "bar"},
},
},
},
},
{
Port: 3333,
Protocol: "grpc",
Services: []IngressService{
{
Name: "api",
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-grpc-internal"},
},
},
},
},
},
},
},
"request header manip not allowed for non-http protocol": {
entry: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "db",
RequestHeaders: &HTTPHeaderModifiers{
Set: map[string]string{"x-foo": "bar"},
},
},
},
},
},
},
validateErr: "request headers only valid for http",
},
"response header manip not allowed for non-http protocol": {
entry: &IngressGatewayConfigEntry{
Kind: "ingress-gateway",
Name: "ingress-web",
Listeners: []IngressListener{
{
Port: 1111,
Protocol: "tcp",
Services: []IngressService{
{
Name: "db",
ResponseHeaders: &HTTPHeaderModifiers{
Remove: []string{"x-foo"},
},
},
},
},
},
},
validateErr: "response headers only valid for http",
},
}
testConfigEntryNormalizeAndValidate(t, cases)

View File

@ -1037,6 +1037,24 @@ func TestDecodeConfigEntry(t *testing.T) {
},
{
name = "db"
request_headers {
add {
foo = "bar"
}
set {
bar = "baz"
}
remove = ["qux"]
}
response_headers {
add {
foo = "bar"
}
set {
bar = "baz"
}
remove = ["qux"]
}
}
]
},
@ -1081,6 +1099,24 @@ func TestDecodeConfigEntry(t *testing.T) {
},
{
Name = "db"
RequestHeaders {
Add {
foo = "bar"
}
Set {
bar = "baz"
}
Remove = ["qux"]
}
ResponseHeaders {
Add {
foo = "bar"
}
Set {
bar = "baz"
}
Remove = ["qux"]
}
}
]
},
@ -1125,6 +1161,16 @@ func TestDecodeConfigEntry(t *testing.T) {
},
{
Name: "db",
RequestHeaders: &HTTPHeaderModifiers{
Add: map[string]string{"foo": "bar"},
Set: map[string]string{"bar": "baz"},
Remove: []string{"qux"},
},
ResponseHeaders: &HTTPHeaderModifiers{
Add: map[string]string{"foo": "bar"},
Set: map[string]string{"bar": "baz"},
Remove: []string{"qux"},
},
},
},
},