Skip to content

Commit

Permalink
Adds logic to set and delete headers via Ingress Operator and Route.
Browse files Browse the repository at this point in the history
  • Loading branch information
miheer committed Feb 15, 2023
1 parent 5bfc6af commit 65b288d
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 9 deletions.
35 changes: 35 additions & 0 deletions images/router/haproxy/conf/haproxy-config.template
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ frontend public
capture cookie {{ $captureCookie.Name }}{{ if eq $captureCookie.MatchType "exact" }}={{ end }} len {{ $captureCookie.MaxLength }}
{{- end }}

{{- range $idx, $http_response_header := .HTTPResponseHeaders }}
{{- if eq $http_response_header.Action "Set" }}
http-response set-header {{ $http_response_header.Name }} {{ $http_response_header.Value }}
{{- else if eq $http_response_header.Action "Delete" }}
http-response del-header {{ $http_response_header.Name }}
{{- end }}
{{- end }}

# Strip off Proxy headers to prevent HTTpoxy (https://httpoxy.org/)
http-request del-header Proxy

Expand Down Expand Up @@ -305,6 +313,15 @@ frontend fe_sni
capture cookie {{ $captureCookie.Name }}{{ if eq $captureCookie.MatchType "exact" }}={{ end }} len {{ $captureCookie.MaxLength }}
{{- end }}


{{- range $idx, $http_response_header := .HTTPResponseHeaders }}
{{- if eq $http_response_header.Action "Set" }}
http-response set-header {{ $http_response_header.Name }} {{ $http_response_header.Value }}
{{- else if eq $http_response_header.Action "Delete" }}
http-response del-header {{ $http_response_header.Name }}
{{- end }}
{{- end }}

# Strip off Proxy headers to prevent HTTpoxy (https://httpoxy.org/)
http-request del-header Proxy

Expand Down Expand Up @@ -391,6 +408,14 @@ frontend fe_no_sni
capture cookie {{ $captureCookie.Name }}{{ if eq $captureCookie.MatchType "exact" }}={{ end }} len {{ $captureCookie.MaxLength }}
{{- end }}

{{- range $idx, $http_response_header := .HTTPResponseHeaders }}
{{- if eq $http_response_header.Action "Set" }}
http-response set-header {{ $http_response_header.Name }} {{ $http_response_header.Value }}
{{- else if eq $http_response_header.Action "Delete" }}
http-response del-header {{ $http_response_header.Name }}
{{- end }}
{{- end }}

# Strip off Proxy headers to prevent HTTpoxy (https://httpoxy.org/)
http-request del-header Proxy

Expand Down Expand Up @@ -612,6 +637,16 @@ backend {{ genBackendNamePrefix $cfg.TLSTermination }}:{{ $cfgIdx }}
{{- end }}{{/* hsts header */}}
{{- end }}{{/* is "edge" or "reencrypt" */}}

{{- if matchValues (print $cfg.TLSTermination) "edge" "reencrypt" }}
{{- range $idx, $http_response_header := $cfg.HTTPResponseHeaders }}
{{- if eq $http_response_header.Action "Set" }}
http-response set-header {{ $http_response_header.Name }} {{ $http_response_header.Value }}
{{- else if eq $http_response_header.Action "Delete" }}
http-response del-header {{ $http_response_header.Name }}
{{- end }}
{{- end }}
{{- end }}{{/* is "edge" or "reencrypt" */}}

{{- range $serviceUnitName, $weight := $cfg.ServiceUnitNames }}
{{- if ge $weight 0 }}{{/* weight=0 is reasonable to keep existing connections to backends with cookies as we can see the HTTP headers */}}
{{- with $serviceUnit := index $.ServiceUnits $serviceUnitName }}
Expand Down
84 changes: 84 additions & 0 deletions pkg/cmd/infra/router/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ type TemplateRouter struct {
CaptureHTTPCookie *templateplugin.CaptureHTTPCookie
HTTPHeaderNameCaseAdjustmentsString string
HTTPHeaderNameCaseAdjustments []templateplugin.HTTPHeaderNameCaseAdjustment
HTTPResponseHeadersString string
HTTPResponseHeaders []templateplugin.HTTPHeader
HTTPRequestHeadersString string
HTTPRequestHeaders []templateplugin.HTTPHeader

TemplateRouterConfigManager
}
Expand Down Expand Up @@ -182,6 +186,8 @@ func (o *TemplateRouter) Bind(flag *pflag.FlagSet) {
flag.StringVar(&o.CaptureHTTPResponseHeadersString, "capture-http-response-headers", env("ROUTER_CAPTURE_HTTP_RESPONSE_HEADERS", ""), "A comma-delimited list of HTTP response header names and maximum header value lengths that should be captured for logging. Each item must have the following form: name:maxLength")
flag.StringVar(&o.CaptureHTTPCookieString, "capture-http-cookie", env("ROUTER_CAPTURE_HTTP_COOKIE", ""), "Name and maximum length of HTTP cookie that should be captured for logging. The argument must have the following form: name:maxLength. Append '=' to the name to indicate that an exact match should be performed; otherwise a prefix match will be performed. The value of first cookie that matches the name is captured.")
flag.StringVar(&o.HTTPHeaderNameCaseAdjustmentsString, "http-header-name-case-adjustments", env("ROUTER_H1_CASE_ADJUST", ""), "A comma-delimited list of HTTP header names that should have their case adjusted. Each item must be a valid HTTP header name and should have the desired capitalization.")
flag.StringVar(&o.HTTPResponseHeadersString, "set-delete-http-response-header", env("ROUTER_HTTP_RESPONSE_HEADERS", ""), "A comma-delimited list of HTTP response header names and values that should be set/deleted.")
flag.StringVar(&o.HTTPRequestHeadersString, "set-delete-http-request-header", env("ROUTER_HTTP_REQUEST_HEADERS", ""), "A comma-delimited list of HTTP request header names and values that should be set/deleted.")
}

type RouterStats struct {
Expand Down Expand Up @@ -287,6 +293,71 @@ func parseCaptureHeaders(in string) ([]templateplugin.CaptureHTTPHeader, error)
return captureHeaders, nil
}

func parseHeadersToBeSetOrDeleted(in string) ([]templateplugin.HTTPHeader, error) {
var captureHeaders []templateplugin.HTTPHeader
var capture templateplugin.HTTPHeader
in, err := url.QueryUnescape(in)
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", in)
}
if len(in) > 0 {
for _, header := range strings.Split(in, ",") {
parts := strings.Split(header, ":")
if len(parts) < 2 {
return captureHeaders, fmt.Errorf("invalid HTTP header input specification: %v", header)
}
if len(parts) == 3 {
headerName, err := url.QueryUnescape(parts[0])
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", headerName)
}
// RFC 2616, section 4.2, states that the header name
// must be a valid token.
if !validTokenRE.MatchString(headerName) {
return captureHeaders, fmt.Errorf("invalid HTTP header name: %v", headerName)
}
headerValue, err := url.QueryUnescape(parts[1])
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", headerValue)
}
action, err := url.QueryUnescape(parts[2])
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", action)
}
capture = templateplugin.HTTPHeader{
Name: headerName,
Value: headerValue,
Action: action,
}
captureHeaders = append(captureHeaders, capture)
}
if len(parts) == 2 {
headerName, err := url.QueryUnescape(parts[0])
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", headerName)
}
// RFC 2616, section 4.2, states that the header name
// must be a valid token.
if !validTokenRE.MatchString(headerName) {
return captureHeaders, fmt.Errorf("invalid HTTP header name: %v", headerName)
}

action, err := url.QueryUnescape(parts[2])
if err != nil {
return captureHeaders, fmt.Errorf("failed to decode percent encoding: %v", action)
}
capture = templateplugin.HTTPHeader{
Name: headerName,
Action: action,
}
captureHeaders = append(captureHeaders, capture)
}
}
}

return captureHeaders, nil
}

func parseCaptureCookie(in string) (*templateplugin.CaptureHTTPCookie, error) {
if len(in) == 0 {
return nil, nil
Expand Down Expand Up @@ -393,6 +464,18 @@ func (o *TemplateRouterOptions) Complete() error {
}
o.CaptureHTTPResponseHeaders = captureHTTPResponseHeaders

httpResponseHeaders, err := parseHeadersToBeSetOrDeleted(o.HTTPResponseHeadersString)
if err != nil {
return err
}
o.HTTPResponseHeaders = httpResponseHeaders

httpRequestHeaders, err := parseHeadersToBeSetOrDeleted(o.HTTPRequestHeadersString)
if err != nil {
return err
}
o.HTTPRequestHeaders = httpRequestHeaders

captureHTTPCookie, err := parseCaptureCookie(o.CaptureHTTPCookieString)
if err != nil {
return err
Expand Down Expand Up @@ -648,6 +731,7 @@ func (o *TemplateRouterOptions) Run(stopCh <-chan struct{}) error {
CaptureHTTPResponseHeaders: o.CaptureHTTPResponseHeaders,
CaptureHTTPCookie: o.CaptureHTTPCookie,
HTTPHeaderNameCaseAdjustments: o.HTTPHeaderNameCaseAdjustments,
HTTPResponseHeaders: o.HTTPResponseHeaders,
}

svcFetcher := templateplugin.NewListWatchServiceLookup(kc.CoreV1(), o.ResyncInterval, o.Namespace)
Expand Down
3 changes: 3 additions & 0 deletions pkg/router/template/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ type TemplatePluginConfig struct {
CaptureHTTPResponseHeaders []CaptureHTTPHeader
CaptureHTTPCookie *CaptureHTTPCookie
HTTPHeaderNameCaseAdjustments []HTTPHeaderNameCaseAdjustment
HTTPResponseHeaders []HTTPHeader
HTTPRequestHeaders []HTTPHeader
}

// RouterInterface controls the interaction of the plugin with the underlying router implementation
Expand Down Expand Up @@ -160,6 +162,7 @@ func NewTemplatePlugin(cfg TemplatePluginConfig, lookupSvc ServiceLookup) (*Temp
captureHTTPResponseHeaders: cfg.CaptureHTTPResponseHeaders,
captureHTTPCookie: cfg.CaptureHTTPCookie,
httpHeaderNameCaseAdjustments: cfg.HTTPHeaderNameCaseAdjustments,
httpResponseHeaders: cfg.HTTPResponseHeaders,
}
router, err := newTemplateRouter(templateRouterCfg)
return newDefaultTemplatePlugin(router, cfg.IncludeUDP, lookupSvc), err
Expand Down
71 changes: 63 additions & 8 deletions pkg/router/template/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ type templateRouter struct {
captureHTTPCookie *CaptureHTTPCookie
// httpHeaderNameCaseAdjustments specifies HTTP header name case adjustments.
httpHeaderNameCaseAdjustments []HTTPHeaderNameCaseAdjustment

httpResponseHeaders []HTTPHeader
httpRequestHeaders []HTTPHeader
}

// templateRouterCfg holds all configuration items required to initialize the template router
Expand All @@ -144,6 +147,8 @@ type templateRouterCfg struct {
captureHTTPResponseHeaders []CaptureHTTPHeader
captureHTTPCookie *CaptureHTTPCookie
httpHeaderNameCaseAdjustments []HTTPHeaderNameCaseAdjustment
httpResponseHeaders []HTTPHeader
httpRequestHeaders []HTTPHeader
}

// templateConfig is a subset of the templateRouter information that should be passed to the template for generating
Expand Down Expand Up @@ -183,6 +188,8 @@ type templateData struct {
// HTTPHeaderNameCaseAdjustments specifies HTTP header name adjustments
// performed on HTTP headers.
HTTPHeaderNameCaseAdjustments []HTTPHeaderNameCaseAdjustment
HTTPResponseHeaders []HTTPHeader
HTTPRequestHeaders []HTTPHeader
}

func newTemplateRouter(cfg templateRouterCfg) (*templateRouter, error) {
Expand Down Expand Up @@ -244,6 +251,7 @@ func newTemplateRouter(cfg templateRouterCfg) (*templateRouter, error) {
captureHTTPResponseHeaders: cfg.captureHTTPResponseHeaders,
captureHTTPCookie: cfg.captureHTTPCookie,
httpHeaderNameCaseAdjustments: cfg.httpHeaderNameCaseAdjustments,
httpResponseHeaders: cfg.httpResponseHeaders,

metricReload: metricsReload,
metricReloadFailure: metricReloadFailure,
Expand Down Expand Up @@ -582,6 +590,7 @@ func (r *templateRouter) writeConfig() error {
CaptureHTTPResponseHeaders: r.captureHTTPResponseHeaders,
CaptureHTTPCookie: r.captureHTTPCookie,
HTTPHeaderNameCaseAdjustments: r.httpHeaderNameCaseAdjustments,
HTTPResponseHeaders: r.httpResponseHeaders,
}
if err := template.Execute(file, data); err != nil {
file.Close()
Expand Down Expand Up @@ -922,16 +931,61 @@ func (r *templateRouter) createServiceAliasConfig(route *routev1.Route, backendK
activeServiceUnits++
}
}
var httpResponseHeadersList []HTTPHeader
var httpRequestHeadersList []HTTPHeader
if route.Spec.HTTPHeaders.Actions.Response != nil && len(route.Spec.HTTPHeaders.Actions.Response) != 0 {
for _, value := range route.Spec.HTTPHeaders.Actions.Response {
if value.Action.Type == routev1.Set && value.Action.Set != nil && len(value.Name) != 0 {

setValue := HTTPHeader{
Name: value.Name,
Value: value.Action.Set.Value,
Action: string(value.Action.Type),
}
httpResponseHeadersList = append(httpResponseHeadersList, setValue)
}
if value.Action.Type == routev1.Delete && len(value.Name) != 0 {
deleteValue := HTTPHeader{
Name: value.Name,
Action: string(value.Action.Type),
}
httpResponseHeadersList = append(httpResponseHeadersList, deleteValue)
}
}
}

if route.Spec.HTTPHeaders.Actions.Request != nil && len(route.Spec.HTTPHeaders.Actions.Request) != 0 {
for _, value := range route.Spec.HTTPHeaders.Actions.Request {
if value.Action.Type == routev1.Set && value.Action.Set != nil && len(value.Name) != 0 {

setValue := HTTPHeader{
Name: value.Name,
Value: value.Action.Set.Value,
Action: string(value.Action.Type),
}
httpRequestHeadersList = append(httpRequestHeadersList, setValue)
}
if value.Action.Type == routev1.Delete && len(value.Name) != 0 {
deleteValue := HTTPHeader{
Name: value.Name,
Action: string(value.Action.Type),
}
httpRequestHeadersList = append(httpRequestHeadersList, deleteValue)
}
}
}

config := ServiceAliasConfig{
Name: route.Name,
Namespace: route.Namespace,
Host: route.Spec.Host,
Path: route.Spec.Path,
IsWildcard: wildcard,
Annotations: route.Annotations,
ServiceUnits: serviceUnits,
ActiveServiceUnits: activeServiceUnits,
Name: route.Name,
Namespace: route.Namespace,
Host: route.Spec.Host,
Path: route.Spec.Path,
IsWildcard: wildcard,
Annotations: route.Annotations,
ServiceUnits: serviceUnits,
ActiveServiceUnits: activeServiceUnits,
HTTPResponseHeaders: httpResponseHeadersList,
HTTPRequestHeaders: httpRequestHeadersList,
}

if route.Spec.Port != nil {
Expand Down Expand Up @@ -1376,6 +1430,7 @@ func configsAreEqual(config1, config2 *ServiceAliasConfig) bool {
config1.RoutingKeyName == config2.RoutingKeyName &&
config1.IsWildcard == config2.IsWildcard &&
config1.VerifyServiceHostname == config2.VerifyServiceHostname &&
reflect.DeepEqual(config1.HTTPResponseHeaders, config2.HTTPResponseHeaders) &&
reflect.DeepEqual(config1.Annotations, config2.Annotations) &&
reflect.DeepEqual(config1.ServiceUnits, config2.ServiceUnits)
}
Expand Down

0 comments on commit 65b288d

Please sign in to comment.