Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions api/applyconfiguration/api/v1alpha1/csrfpolicy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/applyconfiguration/api/v1alpha1/trafficpolicyspec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 18 additions & 0 deletions api/applyconfiguration/internal/internal.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions api/applyconfiguration/utils.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions api/v1alpha1/traffic_policy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ type TrafficPolicySpec struct {
// This controls the rate at which requests are allowed to be processed.
// +optional
RateLimit *RateLimit `json:"rateLimit,omitempty"`

// Csrf specifies the Cross-Site Request Forgery (CSRF) policy for this traffic policy.
// +optional
Csrf *CsrfPolicy `json:"csrf,omitempty"`
}

// TransformationPolicy config is used to modify envoy behavior at a route level.
Expand Down Expand Up @@ -333,3 +337,24 @@ type RateLimitDescriptorEntryGeneric struct {
// +required
Value string `json:"value"`
}

// CsrfPolicy can be used to set percent of requests for which the CSRF filter is enabled,
// enable shadow-only mode where policies will be evaluated and tracked, but not enforced and
// add additional source origins that will be allowed in addition to the destination origin.
type CsrfPolicy struct {
// Specifies the percentage of requests for which the CSRF filter is enabled.
// If both PercentageEnabled and PercentageShadowed are set, the PercentageEnabled flag will take precedence.
// +required
PercentageEnabled *uint32 `json:"percentageEnabled,omitempty"`

// Specifies that CSRF policies will be evaluated and tracked, but not enforced.
// This is intended to be used when PercentageEnabled is 0 and will be ignored otherwise.
// If both PercentageEnabled and PercentageShadowed are set, the PercentageEnabled flag will take precedence.
// +optional
PercentageShadowed *uint32 `json:"percentageShadowed,omitempty"`

// Specifies additional source origins that will be allowed in addition to the destination origin.
// Only exact matches are supported.
// +optional
AdditionalOrigins []string `json:"additionalOrigins,omitempty"`
}
35 changes: 35 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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
Expand Up @@ -273,6 +273,21 @@ spec:
- CHAT_STREAMING
type: string
type: object
csrf:
properties:
additionalOrigins:
items:
type: string
type: array
percentageEnabled:
format: int32
type: integer
percentageShadowed:
format: int32
type: integer
required:
- percentageEnabled
type: object
extAuth:
properties:
contextExtensions:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import (
ratelimitv3 "github.com/envoyproxy/go-control-plane/envoy/config/ratelimit/v3"
routev3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
exteniondynamicmodulev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/dynamic_modules/v3"
envoy_csrf_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/csrf/v3"
dynamicmodulesv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/dynamic_modules/v3"
envoy_ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_authz/v3"
envoy_ext_proc_v3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ext_proc/v3"
localratelimitv3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/local_ratelimit/v3"
ratev3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/ratelimit/v3"
envoyhttp "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
envoy_matcher_v3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
envoy_type_v3 "github.com/envoyproxy/go-control-plane/envoy/type/v3"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/durationpb"
Expand Down Expand Up @@ -63,6 +66,7 @@ const (
localRateLimitFilterNamePrefix = "ratelimit/local"
localRateLimitStatPrefix = "http_local_rate_limiter"
rateLimitFilterNamePrefix = "ratelimit"
csrfExtensionFilterName = "envoy.filters.http.csrf"
)

var (
Expand Down Expand Up @@ -137,6 +141,7 @@ type trafficPolicySpecIr struct {
extAuth *extAuthIR
localRateLimit *localratelimitv3.LocalRateLimit
rateLimit *RateLimitIR
csrf *envoy_csrf_v3.CsrfPolicy
}

func (d *TrafficPolicy) CreationTime() time.Time {
Expand Down Expand Up @@ -194,6 +199,10 @@ func (d *TrafficPolicy) Equals(in any) bool {
return false
}

if !proto.Equal(d.spec.csrf, d2.spec.csrf) {
return false
}

return true
}

Expand Down Expand Up @@ -290,6 +299,7 @@ type trafficPolicyPluginGwPass struct {
extAuthPerProvider ProviderNeededMap
extProcPerProvider ProviderNeededMap
rateLimitPerProvider ProviderNeededMap
csrfInChain map[string]*envoy_csrf_v3.CsrfPolicy
}

func (p *trafficPolicyPluginGwPass) ApplyHCM(ctx context.Context, pCtx *ir.HcmContext, out *envoyhttp.HttpConnectionManager) error {
Expand Down Expand Up @@ -638,6 +648,8 @@ func (p *trafficPolicyPluginGwPass) handlePolicies(fcn string, typedFilterConfig
// Apply rate limit configuration if present
p.handleRateLimit(fcn, typedFilterConfig, spec.rateLimit)
p.handleLocalRateLimit(fcn, typedFilterConfig, spec.localRateLimit)
// Apply CSRF configuration if present
p.handleCsrf(fcn, typedFilterConfig, spec.csrf)
}

// handleRateLimit adds rate limit configurations to routes
Expand Down Expand Up @@ -859,6 +871,14 @@ func (p *trafficPolicyPluginGwPass) HttpFilters(ctx context.Context, fcc ir.Filt
filters = append(filters, stagedRateLimitFilter)
}

// Add global CSRF http filter
if p.csrfInChain[fcc.FilterChainName] != nil {
filter := plugins.MustNewStagedFilter(csrfExtensionFilterName,
p.csrfInChain[fcc.FilterChainName],
plugins.BeforeStage(plugins.AuthZStage))
filters = append(filters, filter)
}

if len(filters) == 0 {
return nil, nil
}
Expand Down Expand Up @@ -992,6 +1012,11 @@ func MergeTrafficPolicies(
p1.spec.rateLimit = p2.spec.rateLimit
mergeOrigins["rateLimit"] = p2Ref
}
// Handle CSRF policy merging
if policy.IsMergeable(p1.spec.csrf, p2.spec.csrf, mergeOpts) {
p1.spec.csrf = p2.spec.csrf
mergeOrigins["csrf"] = p2Ref
}
return mergeOrigins
}

Expand Down Expand Up @@ -1062,6 +1087,12 @@ func (b *TrafficPolicyBuilder) Translate(
errs := b.rateLimitForSpec(krtctx, policyCR, &outSpec)
errors = append(errors, errs...)

// Apply CSRF specific translation
err = csrfForSpec(policyCR.Spec, &outSpec)
if err != nil {
errors = append(errors, err)
}

for _, err := range errors {
logger.Error("error translating gateway extension", "namespace", policyCR.GetNamespace(), "name", policyCR.GetName(), "error", err)
}
Expand Down Expand Up @@ -1212,3 +1243,74 @@ func (b *TrafficPolicyBuilder) rateLimitForSpec(
}
return nil
}

// handleCsrf adds CSRF configuration to routes
func (p *trafficPolicyPluginGwPass) handleCsrf(fcn string, typedFilterConfig *ir.TypedFilterConfigMap, csrf *envoy_csrf_v3.CsrfPolicy) {
if csrf == nil {
return
}
typedFilterConfig.AddTypedConfig(csrfExtensionFilterName, csrf)

// Add a filter to the chain. When having a csrf for a route we need to also have a
// globally disabled csrf filter in the chain otherwise it will be ignored.
// If there is also csrf for the listener, it will not override this one.
if p.csrfInChain == nil {
p.csrfInChain = make(map[string]*envoy_csrf_v3.CsrfPolicy)
}
if _, ok := p.csrfInChain[fcn]; !ok {
p.csrfInChain[fcn] = &envoy_csrf_v3.CsrfPolicy{
FilterEnabled: &envoy_core_v3.RuntimeFractionalPercent{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we started a discussion on how to handle runtime keys?
I think we may want a doc or at least a discussion in a community meeting or something to codify it.

Namely I state this as runtimefractionals WITHOUT a key have bitten previous implementations pretty hard and I think we need to decide early if we want to try to plumb the runtime key naming in interesting ways

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nfuden This is "empty" filter being added to filter chain just so that the inner policies will be applied. The typed filter config do have runtime keys (here and here). (I will push a commit that changes the keys to envoy.csrf.filter_enabled and envoy.csrf.shadow_enabled though).

As for the generic approach, I can open an issue for this to track and discuss this over it.

DefaultValue: &envoy_type_v3.FractionalPercent{
Numerator: 0,
Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
},
},
}
}
}

// csrfForSpec translates the CSRF spec into and onto the IR policy
func csrfForSpec(spec v1alpha1.TrafficPolicySpec, out *trafficPolicySpecIr) error {
if spec.Csrf == nil {
return nil
}

csrfPolicy := &envoy_csrf_v3.CsrfPolicy{}

// Set filter enabled percentage
if spec.Csrf.PercentageEnabled != nil {
csrfPolicy.FilterEnabled = &envoy_core_v3.RuntimeFractionalPercent{
DefaultValue: &envoy_type_v3.FractionalPercent{
Numerator: *spec.Csrf.PercentageEnabled,
Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
},
RuntimeKey: "csrf.percentage_enabled",
}
}

// Set shadow enabled percentage if specified
if spec.Csrf.PercentageShadowed != nil {
csrfPolicy.ShadowEnabled = &envoy_core_v3.RuntimeFractionalPercent{
DefaultValue: &envoy_type_v3.FractionalPercent{
Numerator: *spec.Csrf.PercentageShadowed,
Denominator: envoy_type_v3.FractionalPercent_HUNDRED,
},
RuntimeKey: "csrf.percentage_shadowed",
}
}

// Add additional origins if specified
if len(spec.Csrf.AdditionalOrigins) > 0 {
csrfPolicy.AdditionalOrigins = make([]*envoy_matcher_v3.StringMatcher, len(spec.Csrf.AdditionalOrigins))
for i, origin := range spec.Csrf.AdditionalOrigins {
csrfPolicy.GetAdditionalOrigins()[i] = &envoy_matcher_v3.StringMatcher{
MatchPattern: &envoy_matcher_v3.StringMatcher_Exact{
Exact: origin,
},
}
}
}

out.csrf = csrfPolicy
return nil
}
Loading
Loading