-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(translator): early request header modifier #4004
Changes from 5 commits
c07506f
8e546e7
a9032ca
c09596d
592ab5b
846acce
a4d78d6
7d6ebbc
ee65414
bb9018d
d1da36e
a0db661
b894f4d
3370aea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/utils/ptr" | ||
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1" | ||
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2" | ||
|
||
egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" | ||
|
@@ -432,7 +433,10 @@ | |
translateClientIPDetection(policy.Spec.ClientIPDetection, httpIR) | ||
|
||
// Translate Header Settings | ||
translateListenerHeaderSettings(policy.Spec.Headers, httpIR) | ||
if err = translateListenerHeaderSettings(policy.Spec.Headers, httpIR); err != nil { | ||
err = perr.WithMessage(err, "Headers") | ||
errs = errors.Join(errs, err) | ||
} | ||
|
||
// Translate Path Settings | ||
translatePathSettings(policy.Spec.Path, httpIR) | ||
|
@@ -613,9 +617,9 @@ | |
httpIR.ClientIPDetection = (*ir.ClientIPDetectionSettings)(clientIPDetection) | ||
} | ||
|
||
func translateListenerHeaderSettings(headerSettings *egv1a1.HeaderSettings, httpIR *ir.HTTPListener) { | ||
func translateListenerHeaderSettings(headerSettings *egv1a1.HeaderSettings, httpIR *ir.HTTPListener) error { | ||
if headerSettings == nil { | ||
return | ||
return nil | ||
} | ||
httpIR.Headers = &ir.HeaderSettings{ | ||
EnableEnvoyHeaders: ptr.Deref(headerSettings.EnableEnvoyHeaders, false), | ||
|
@@ -634,6 +638,16 @@ | |
httpIR.Headers.XForwardedClientCert.CertDetailsToAdd = headerSettings.XForwardedClientCert.CertDetailsToAdd | ||
} | ||
} | ||
|
||
if headerSettings.EarlyRequestHeaderModifier != nil { | ||
headersToAdd, headersToRemove, err := translateEarlyRequestHeaderModifier(headerSettings.EarlyRequestHeaderModifier) | ||
if err != nil { | ||
return err | ||
} | ||
httpIR.Headers.EarlyAddRequestHeaders = headersToAdd | ||
httpIR.Headers.EarlyRemoveRequestHeaders = headersToRemove | ||
} | ||
return nil | ||
} | ||
|
||
func translateHTTP1Settings(http1Settings *egv1a1.HTTP1Settings, httpIR *ir.HTTPListener) error { | ||
|
@@ -869,3 +883,130 @@ | |
|
||
return irConnection, nil | ||
} | ||
|
||
func translateEarlyRequestHeaderModifier(headerModifier *gwapiv1.HTTPHeaderFilter) ([]ir.AddHeader, []string, error) { | ||
// Make sure the header modifier config actually exists | ||
if headerModifier == nil { | ||
return nil, nil, nil | ||
} | ||
var errs error | ||
emptyFilterConfig := true // keep track of whether the provided config is empty or not | ||
|
||
var AddRequestHeaders []ir.AddHeader | ||
var RemoveRequestHeaders []string | ||
|
||
// Add request headers | ||
if headersToAdd := headerModifier.Add; headersToAdd != nil { | ||
if len(headersToAdd) > 0 { | ||
emptyFilterConfig = false | ||
} | ||
for _, addHeader := range headersToAdd { | ||
emptyFilterConfig = false | ||
if addHeader.Name == "" { | ||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier cannot add a header with an empty name")) | ||
// try to process the rest of the headers and produce a valid config. | ||
continue | ||
} | ||
// Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names | ||
if strings.Contains(string(addHeader.Name), "/") || strings.Contains(string(addHeader.Name), ":") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe use strings.ContainsAny instead? |
||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier Filter cannot set headers with a '/' or ':' character in them. Header: %q", string(addHeader.Name))) | ||
continue | ||
} | ||
// Check if the header is a duplicate | ||
headerKey := string(addHeader.Name) | ||
canAddHeader := true | ||
for _, h := range AddRequestHeaders { | ||
if strings.EqualFold(h.Name, headerKey) { | ||
canAddHeader = false | ||
break | ||
} | ||
} | ||
|
||
if !canAddHeader { | ||
continue | ||
} | ||
|
||
newHeader := ir.AddHeader{ | ||
Name: headerKey, | ||
Append: true, | ||
Value: addHeader.Value, | ||
} | ||
|
||
AddRequestHeaders = append(AddRequestHeaders, newHeader) | ||
} | ||
} | ||
|
||
// Set headers | ||
if headersToSet := headerModifier.Set; headersToSet != nil { | ||
if len(headersToSet) > 0 { | ||
emptyFilterConfig = false | ||
} | ||
for _, setHeader := range headersToSet { | ||
|
||
if setHeader.Name == "" { | ||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier cannot set a header with an empty name")) | ||
continue | ||
} | ||
// Per Gateway API specification on HTTPHeaderName, : and / are invalid characters in header names | ||
if strings.Contains(string(setHeader.Name), "/") || strings.Contains(string(setHeader.Name), ":") { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier cannot set headers with a '/' or ':' character in them. Header: '%s'", string(setHeader.Name))) | ||
continue | ||
} | ||
|
||
// Check if the header to be set has already been configured | ||
headerKey := string(setHeader.Name) | ||
canAddHeader := true | ||
for _, h := range AddRequestHeaders { | ||
if strings.EqualFold(h.Name, headerKey) { | ||
canAddHeader = false | ||
break | ||
} | ||
} | ||
if !canAddHeader { | ||
continue | ||
} | ||
newHeader := ir.AddHeader{ | ||
Name: string(setHeader.Name), | ||
Append: false, | ||
Value: setHeader.Value, | ||
} | ||
|
||
AddRequestHeaders = append(AddRequestHeaders, newHeader) | ||
} | ||
} | ||
|
||
// Remove request headers | ||
// As far as Envoy is concerned, it is ok to configure a header to be added/set and also in the list of | ||
// headers to remove. It will remove the original header if present and then add/set the header after. | ||
if headersToRemove := headerModifier.Remove; headersToRemove != nil { | ||
if len(headersToRemove) > 0 { | ||
emptyFilterConfig = false | ||
} | ||
for _, removedHeader := range headersToRemove { | ||
if removedHeader == "" { | ||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier cannot remove a header with an empty name")) | ||
continue | ||
} | ||
|
||
canRemHeader := true | ||
for _, h := range RemoveRequestHeaders { | ||
if strings.EqualFold(h, removedHeader) { | ||
canRemHeader = false | ||
break | ||
} | ||
} | ||
if !canRemHeader { | ||
continue | ||
} | ||
|
||
RemoveRequestHeaders = append(RemoveRequestHeaders, removedHeader) | ||
} | ||
} | ||
|
||
// Update the status if the filter failed to configure any valid headers to add/remove | ||
if len(AddRequestHeaders) == 0 && len(RemoveRequestHeaders) == 0 && !emptyFilterConfig { | ||
errs = errors.Join(errs, fmt.Errorf("EarlyRequestHeaderModifier did not provide valid configuration to add/set/remove any headers")) | ||
} | ||
|
||
return AddRequestHeaders, RemoveRequestHeaders, errs | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thoughts on
earlyRequestHeaders
?