Skip to content
Open
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
12 changes: 6 additions & 6 deletions api/v1alpha1/securitypolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,19 @@ type SecurityPolicy struct {

// SecurityPolicySpec defines the desired state of SecurityPolicy.
//
// NOTE: SecurityPolicy can target Gateway, ListenerSet, HTTPRoute, GRPCRoute, and TCPRoute.
// When a SecurityPolicy targets a TCPRoute, only client-IP CIDR based authorization
// NOTE: SecurityPolicy can target Gateway, ListenerSet, HTTPRoute, GRPCRoute, and TCPRoute, and TLSRoute.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please add a release note as well for this new feautre?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

A release note is added.

// When a SecurityPolicy targets a TCPRoute or TLSRoute, only client-IP CIDR based authorization
// (Authorization rules that use Principal.ClientCIDRs) is applied. Other
// authentication/authorization features such as JWT, API Key, Basic Auth,
// OIDC, External Authorization, or GeoIP based authorization are not applicable
// to TCPRoute targets.
// to TCPRoute or TLSRoute targets.
//
// +kubebuilder:validation:XValidation:rule="(has(self.targetRef) && !has(self.targetRefs)) || (!has(self.targetRef) && has(self.targetRefs)) || (has(self.targetSelectors) && self.targetSelectors.size() > 0) ", message="either targetRef or targetRefs must be used"
// +kubebuilder:validation:XValidation:rule="has(self.targetRef) ? self.targetRef.group == 'gateway.networking.k8s.io' : true", message="this policy can only have a targetRef.group of gateway.networking.k8s.io"
// +kubebuilder:validation:XValidation:rule="has(self.targetRef) ? self.targetRef.kind in ['Gateway', 'ListenerSet', 'HTTPRoute', 'GRPCRoute', 'TCPRoute'] : true", message="this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute"
// +kubebuilder:validation:XValidation:rule="has(self.targetRef) ? self.targetRef.kind in ['Gateway', 'ListenerSet', 'HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute'] : true", message="this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute"
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, ref.group == 'gateway.networking.k8s.io') : true ", message="this policy can only have a targetRefs[*].group of gateway.networking.k8s.io"
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in ['Gateway', 'ListenerSet', 'HTTPRoute', 'GRPCRoute', 'TCPRoute']) : true ", message="this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute"
// +kubebuilder:validation:XValidation:rule="!has(self.mergeType) || ((!has(self.targetRef) || self.targetRef.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute']) && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute'])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute'])))", message="mergeType can only be used with xRoute targets"
// +kubebuilder:validation:XValidation:rule="has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in ['Gateway', 'ListenerSet', 'HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute']) : true ", message="this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute"
// +kubebuilder:validation:XValidation:rule="!has(self.mergeType) || ((!has(self.targetRef) || self.targetRef.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute']) && (!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute'])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel, sel.kind in ['HTTPRoute', 'GRPCRoute', 'TCPRoute', 'TLSRoute'])))", message="mergeType can only be used with xRoute targets"
Comment thread
asHasnain marked this conversation as resolved.
// +kubebuilder:validation:XValidation:rule="(has(self.authorization) && has(self.authorization.rules) && self.authorization.rules.exists(r, has(r.principal) ? has(r.principal.jwt) : false)) ? has(self.jwt) : true", message="if authorization.rules.principal.jwt is used, jwt must be defined"
type SecurityPolicySpec struct {
PolicyTargetReferences `json:",inline"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7482,21 +7482,23 @@ spec:
- message: this policy can only have a targetRef.group of gateway.networking.k8s.io
rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io''
: true'
- message: this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute
- message: this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute
rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''ListenerSet'',
''HTTPRoute'', ''GRPCRoute'', ''TCPRoute''] : true'
''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute''] : true'
- message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io
rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group ==
''gateway.networking.k8s.io'') : true '
- message: this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute
- message: this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute
rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'',
''ListenerSet'', ''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'']) : true '
''ListenerSet'', ''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute''])
: true '
- message: mergeType can only be used with xRoute targets
rule: '!has(self.mergeType) || ((!has(self.targetRef) || self.targetRef.kind
in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'']) && (!has(self.targetRefs)
|| self.targetRefs.all(ref, ref.kind in [''HTTPRoute'', ''GRPCRoute'',
''TCPRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel,
sel.kind in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute''])))'
in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute'']) &&
(!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind in [''HTTPRoute'',
''GRPCRoute'', ''TCPRoute'', ''TLSRoute''])) && (!has(self.targetSelectors)
|| self.targetSelectors.all(sel, sel.kind in [''HTTPRoute'', ''GRPCRoute'',
''TCPRoute'', ''TLSRoute''])))'
- message: if authorization.rules.principal.jwt is used, jwt must be defined
rule: '(has(self.authorization) && has(self.authorization.rules) &&
self.authorization.rules.exists(r, has(r.principal) ? has(r.principal.jwt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7481,21 +7481,23 @@ spec:
- message: this policy can only have a targetRef.group of gateway.networking.k8s.io
rule: 'has(self.targetRef) ? self.targetRef.group == ''gateway.networking.k8s.io''
: true'
- message: this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute
- message: this policy can only have a targetRef.kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute
rule: 'has(self.targetRef) ? self.targetRef.kind in [''Gateway'', ''ListenerSet'',
''HTTPRoute'', ''GRPCRoute'', ''TCPRoute''] : true'
''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute''] : true'
- message: this policy can only have a targetRefs[*].group of gateway.networking.k8s.io
rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.group ==
''gateway.networking.k8s.io'') : true '
- message: this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute
- message: this policy can only have a targetRefs[*].kind of Gateway/ListenerSet/HTTPRoute/GRPCRoute/TCPRoute/TLSRoute
rule: 'has(self.targetRefs) ? self.targetRefs.all(ref, ref.kind in [''Gateway'',
''ListenerSet'', ''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'']) : true '
''ListenerSet'', ''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute''])
: true '
- message: mergeType can only be used with xRoute targets
rule: '!has(self.mergeType) || ((!has(self.targetRef) || self.targetRef.kind
in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'']) && (!has(self.targetRefs)
|| self.targetRefs.all(ref, ref.kind in [''HTTPRoute'', ''GRPCRoute'',
''TCPRoute''])) && (!has(self.targetSelectors) || self.targetSelectors.all(sel,
sel.kind in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute''])))'
in [''HTTPRoute'', ''GRPCRoute'', ''TCPRoute'', ''TLSRoute'']) &&
(!has(self.targetRefs) || self.targetRefs.all(ref, ref.kind in [''HTTPRoute'',
''GRPCRoute'', ''TCPRoute'', ''TLSRoute''])) && (!has(self.targetSelectors)
|| self.targetSelectors.all(sel, sel.kind in [''HTTPRoute'', ''GRPCRoute'',
''TCPRoute'', ''TLSRoute''])))'
- message: if authorization.rules.principal.jwt is used, jwt must be defined
rule: '(has(self.authorization) && has(self.authorization.rules) &&
self.authorization.rules.exists(r, has(r.principal) ? has(r.principal.jwt)
Expand Down
43 changes: 27 additions & 16 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -506,9 +506,9 @@ func (t *Translator) processSecurityPolicyForRoute(
// then run it once to keep the flow linear and easier to read.
validator := validateSecurityPolicy
errMsg := "invalid SecurityPolicy"
if currTarget.Kind == resource.KindTCPRoute {
validator = validateSecurityPolicyForTCP
errMsg = "invalid SecurityPolicy for TCP route"
if currTarget.Kind == resource.KindTCPRoute || currTarget.Kind == resource.KindTLSRoute {
validator = validateSecurityPolicyForTCPAndTLS
errMsg = "invalid SecurityPolicy for TCP/TLS route"
}
if err := validator(policy); err != nil {
status.SetTranslationErrorForPolicyAncestors(&policy.Status,
Expand Down Expand Up @@ -938,36 +938,36 @@ func validateSecurityPolicy(p *egv1a1.SecurityPolicy) error {
return nil
}

// validateSecurityPolicyForTCP ensures SecurityPolicy usage on TCP is compatible.
// validateSecurityPolicyForTCPAndTLS ensures SecurityPolicy usage on TCP/TLS is compatible.
//
// TCP supports Authorization with ClientCIDRs ONLY.
// TCP and TLS support Authorization with ClientCIDRs ONLY.
// - Principals.JWT => invalid (HTTP-only)
// - Principals.Headers => invalid (HTTP-only)
// - Empty/no Authorization is allowed and results in no-op on TCP.
// - Empty/no Authorization is allowed and results in no-op on TCP/TLS.
// Returns an error when any HTTP-only field is present or CIDRs are invalid.
func validateSecurityPolicyForTCP(p *egv1a1.SecurityPolicy) error {
func validateSecurityPolicyForTCPAndTLS(p *egv1a1.SecurityPolicy) error {
if p.Spec.CORS != nil || p.Spec.JWT != nil || p.Spec.OIDC != nil || p.Spec.APIKeyAuth != nil || p.Spec.BasicAuth != nil || p.Spec.ExtAuth != nil {
return fmt.Errorf("only authorization is supported for TCP (routes/listeners)")
return fmt.Errorf("only authorization is supported for TCP/TLS (routes/listeners)")
}
if p.Spec.Authorization == nil || len(p.Spec.Authorization.Rules) == 0 {
return nil
}
for i := range p.Spec.Authorization.Rules {
rule := &p.Spec.Authorization.Rules[i]
if rule.CEL != nil {
return fmt.Errorf("rule %d: CEL not supported for TCP", i)
return fmt.Errorf("rule %d: CEL not supported for TCP/TLS", i)
}
if rule.Principal == nil {
continue
}
if rule.Principal.JWT != nil {
return fmt.Errorf("rule %d: JWT not supported for TCP", i)
return fmt.Errorf("rule %d: JWT not supported for TCP/TLS", i)
}
if len(rule.Principal.Headers) > 0 {
return fmt.Errorf("rule %d: headers not supported for TCP", i)
return fmt.Errorf("rule %d: headers not supported for TCP/TLS", i)
Comment thread
asHasnain marked this conversation as resolved.
}
if len(rule.Principal.ClientIPGeoLocations) > 0 {
return fmt.Errorf("rule %d: clientIPGeoLocations not supported for TCP", i)
return fmt.Errorf("rule %d: clientIPGeoLocations not supported for TCP/TLS", i)
}
if err := validateCIDRs(rule.Principal.ClientCIDRs); err != nil {
return fmt.Errorf("rule %d: %w", i, err)
Expand Down Expand Up @@ -1342,16 +1342,26 @@ func (t *Translator) translateSecurityPolicyForRoute(

irKey := t.getIRKey(gtwCtx.Gateway)
switch route.GetRouteType() {
case resource.KindTCPRoute:
case resource.KindTCPRoute, resource.KindTLSRoute:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve TLSRoute rule names before enabling sectionName targets

Adding TLSRoute to this branch makes targetRefs.sectionName policies reach the TCP/TLS route path, but TLSRoute IR is currently built with Destination.Metadata: buildResourceMetadata(tlsRoute, nil) (internal/gatewayapi/route.go:1482), so r.Destination.Metadata.SectionName is always empty. A SecurityPolicy targeting a named TLSRoute rule passes HasRuleNames validation, then this path skips every route and leaves the policy accepted but with no authorization applied.

Useful? React with 👍 / 👎.

for _, listener := range parentRefCtx.listeners {
// If targetListener is set, only apply to that exact listener.
if targetListener != nil && targetListenerName != irListenerName(listener) {
continue
}
tl := xdsIR[irKey].GetTCPListener(irListenerName(listener))

for _, r := range tl.Routes {
// For TLSRoute: filter by route metadata to handle multiple routes per listener
if target.Kind == resource.KindTLSRoute {
if r.Metadata == nil ||
r.Metadata.Namespace != string(target.Namespace) ||
r.Metadata.Name != string(target.Name) {
continue
}
}

// If target.SectionName is specified it must match the route-rule section name
// in the IR. For HTTP/GRPC routes this is r.Metadata.SectionName; for TCP
// in the IR. For HTTP/GRPC routes this is r.Metadata.SectionName; for TCP/TLS
// routes the section name is currently stored on r.Destination.Metadata.SectionName.
if target.SectionName != nil && string(*target.SectionName) != r.Destination.Metadata.SectionName {
continue
Expand All @@ -1360,7 +1370,7 @@ func (t *Translator) translateSecurityPolicyForRoute(
if r.Authorization != nil {
continue
}
// Only authorization for TCP
// Only authorization for TCP/TLS
if authorization != nil {
authCopy := *authorization
r.Authorization = &authCopy
Expand Down Expand Up @@ -1708,7 +1718,8 @@ func (t *Translator) translateSecurityPolicyForListeners(
tcpAuthorization = &authCopy
}

// Apply to TCP listeners (Authorization only).
// Apply to TCP/TLS listeners (Authorization only).
// Both TCPRoute and TLSRoute translate to TCP listeners in the IR.
if tcpAuthorization != nil {
for _, tl := range x.TCP {
if tl == nil || len(tl.Routes) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion internal/gatewayapi/securitypolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1313,7 +1313,7 @@ func Test_validateSecurityPolicyForTCP_Table(t *testing.T) {
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
p := &egv1a1.SecurityPolicy{Spec: tc.spec}
err := validateSecurityPolicyForTCP(p)
err := validateSecurityPolicyForTCPAndTLS(p)
if tc.wantErr {
require.Error(t, err)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ securityPolicies:
sectionName: tcp
conditions:
- lastTransitionTime: null
message: Only authorization is supported for TCP (routes/listeners).
message: Only authorization is supported for TCP/TLS (routes/listeners).
reason: Invalid
status: "False"
type: Accepted
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-tls
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: tls-passthrough
protocol: TLS
port: 8443
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
tlsRoutes:
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
namespace: default
name: tlsroute-a
spec:
parentRefs:
- namespace: envoy-gateway
name: gateway-tls
sectionName: tls-passthrough
hostnames:
- "a.example.com"
rules:
- backendRefs:
- name: service-1
port: 8443
- apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
namespace: default
name: tlsroute-b
spec:
parentRefs:
- namespace: envoy-gateway
name: gateway-tls
sectionName: tls-passthrough
hostnames:
- "b.example.com"
rules:
- backendRefs:
- name: service-2
port: 8443
securityPolicies:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
namespace: default
name: sp-tlsroute-a
spec:
targetRef:
group: gateway.networking.k8s.io
kind: TLSRoute
name: tlsroute-a
authorization:
defaultAction: Deny
rules:
- action: Allow
name: allow-route-a
principal:
clientCIDRs:
- 192.168.0.0/16
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: SecurityPolicy
metadata:
namespace: default
name: sp-tlsroute-b
spec:
targetRef:
group: gateway.networking.k8s.io
kind: TLSRoute
name: tlsroute-b
authorization:
defaultAction: Deny
rules:
- action: Allow
name: allow-route-b
principal:
clientCIDRs:
- 10.0.0.0/8
Loading
Loading