Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
93 changes: 93 additions & 0 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,20 @@ func (t *Translator) processSecurityPolicyForGateway(
policy.Generation,
)
}

// append the preflight routes to the listener
for _, listener := range targetedGateway.listeners {
// If the target is a gateway and the section name is defined, then the policy should only apply to the listener
if currTarget.SectionName != nil && string(*currTarget.SectionName) != string(listener.Name) {
continue
}

irListener := xdsIR[t.getIRKey(targetedGateway.Gateway)].GetHTTPListener(irListenerName(listener))
if irListener != nil && irListener.Routes != nil {
// Prefix is empty because invalid prefix means apply to all routes
t.processCORSPreflight(irListener, "", policy.Spec.CORS)
}
}
}

// validateSecurityPolicy validates the SecurityPolicy.
Expand Down Expand Up @@ -764,6 +778,7 @@ func (t *Translator) translateSecurityPolicyForRoute(
}
}
}
t.processCORSPreflight(irListener, prefix, policy.Spec.CORS)
}
}
}
Expand All @@ -775,6 +790,7 @@ func (t *Translator) translateSecurityPolicyForRoute(
"error", errs,
)
}

return errs
}

Expand Down Expand Up @@ -2019,3 +2035,80 @@ func defaultAuthorizationRuleName(policy *egv1a1.SecurityPolicy, index int) stri
irConfigName(policy),
strconv.Itoa(index))
}

func (t *Translator) processCORSPreflight(irListener *ir.HTTPListener, prefix string, cors *egv1a1.CORS) {
if cors == nil {
return
}

var preflightRoutes []*ir.HTTPRoute
for _, r := range irListener.Routes {
// If the prefix is empty, it means the policy applies to all routes (Gateway target)
// If the prefix is not empty, it means the policy applies to a specific route (Route target)
if prefix != "" && !strings.HasPrefix(r.Name, prefix) {
continue
}

// Check if the route needs preflight
needsPreflight := false
for _, m := range r.HeaderMatches {
if m.Name == ":method" && m.Exact != nil && *m.Exact != "OPTIONS" {
needsPreflight = true
break
}
}

if needsPreflight {
// Check if a preflight route already exists for this route
preflightName := r.Name + "-cors-preflight"
exists := false
for _, existing := range irListener.Routes {
if existing.Name == preflightName {
exists = true
break
}
}
if exists {
continue
}

preRoute := r.DeepCopy()
preRoute.Name = preflightName
preRoute.Security = &ir.SecurityFeatures{
CORS: t.buildCORS(cors),
}

// Create header matches:
// copy original headers (excluding :method) + add CORS headers (:method=OPTIONS, origin, access-control-request-method)
headerMatches := make([]*ir.StringMatch, 0, len(r.HeaderMatches)+2)
for _, headerMatch := range r.HeaderMatches {
// Skip the original method match for CORS preflight route to avoid conflicting method requirements.
if headerMatch.Name == ":method" {
continue
}
headerMatches = append(headerMatches, headerMatch)
}

corsHeaders := []*ir.StringMatch{
{
Name: ":method",
Exact: ptr.To("OPTIONS"),
},
{
Name: "origin",
SafeRegex: ptr.To(".*"),
},
{
Name: "access-control-request-method",
SafeRegex: ptr.To(".*"),
},
}
headerMatches = append(headerMatches, corsHeaders...)
preRoute.HeaderMatches = headerMatches

preflightRoutes = append(preflightRoutes, preRoute)
}
}

irListener.Routes = append(irListener.Routes, preflightRoutes...)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/"
method: GET
backendRefs:
- name: service-1
port: 8080
securityPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
namespace: default
name: policy-for-route-1
spec:
targetRef:
group: gateway.networking.k8s.io
kind: HTTPRoute
name: httproute-1
cors:
allowOrigins:
- "https://*.test.com:8080"
allowMethods:
- GET
Loading
Loading