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
10 changes: 7 additions & 3 deletions api/v1alpha1/jwt_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ package v1alpha1
import corev1 "k8s.io/api/core/v1"

// JWTAuthentication defines the providers used to configure JWT authentication
// +kubebuilder:validation:ExactlyOneOf=extensionRef;disable
type JWTAuthentication struct {
// ExtensionRef references a GatewayExtension that provides the jwt providers
// +required
ExtensionRef NamespacedObjectReference `json:"extensionRef"`
// +optional
ExtensionRef *NamespacedObjectReference `json:"extensionRef,omitempty"`

// TODO: add support for ValidationMode here (REQUIRE_VALID,ALLOW_MISSING,ALLOW_MISSING_OR_FAILED)

// TODO(npolshak): Add option to disable all jwt filters.
// Disable all JWT filters.
// Can be used to disable JWT policies applied at a higher level in the config hierarchy.
// +optional
Disable *PolicyDisable `json:"disable,omitempty"`
}

// JWTProvider configures the JWT Provider
Expand Down
11 changes: 10 additions & 1 deletion 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 @@ -892,6 +892,11 @@ spec:
JWT specifies the JWT authentication configuration for the policy.
This defines the JWT providers and their configurations.
properties:
disable:
description: |-
Disable all JWT filters.
Can be used to disable JWT policies applied at a higher level in the config hierarchy.
type: object
extensionRef:
description: ExtensionRef references a GatewayExtension that provides
the jwt providers
Expand All @@ -912,9 +917,12 @@ spec:
required:
- name
type: object
required:
- extensionRef
type: object
x-kubernetes-validations:
- message: exactly one of the fields in [extensionRef disable] must
be set
rule: '[has(self.extensionRef),has(self.disable)].filter(x,x==true).size()
== 1'
rateLimit:
description: |-
RateLimit specifies the rate limiting configuration for the policy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type TrafficPolicyGatewayExtensionIR struct {
ExtAuth *envoy_ext_authz_v3.ExtAuthz
ExtProc *envoymatchingv3.ExtensionWithMatcher
RateLimit *ratev3.RateLimit
Jwt *envoyjwtauthnv3.JwtAuthentication
Jwt *envoymatchingv3.ExtensionWithMatcher
PrecedenceWeight int32
Err error
}
Expand Down Expand Up @@ -168,7 +168,7 @@ func TranslateGatewayExtensionBuilder(commoncol *collections.CommonCollections)
p.Err = fmt.Errorf("jwt: %w", err)
return p
}
p.Jwt = jwtConfig
p.Jwt = buildCompositeJwtFilter(jwtConfig)
}
return p
}
Expand Down Expand Up @@ -343,9 +343,43 @@ func buildCompositeExtProcFilter(in v1alpha1.ExtProcProvider, envoyGrpcService *
}
}
}
return buildCompositeFilter(
"composite_ext_proc",
extProcGlobalDisableFilterMetadataNamespace,
&envoycorev3.TypedExtensionConfig{
Name: "envoy.filters.http.ext_proc",
TypedConfig: utils.MustMessageToAny(filter),
},
)
}

func buildCompositeJwtFilter(jwtConfig *envoyjwtauthnv3.JwtAuthentication) *envoymatchingv3.ExtensionWithMatcher {
if jwtConfig == nil {
return nil
}

return buildCompositeFilter(
"composite_jwt",
jwtGlobalDisableFilterMetadataNamespace,
&envoycorev3.TypedExtensionConfig{
Name: "envoy.filters.http.jwt_authn",
TypedConfig: utils.MustMessageToAny(jwtConfig),
},
)
}

func buildCompositeFilter(
compositeName string,
metadataNamespace string,
filterTypedConfig *envoycorev3.TypedExtensionConfig,
) *envoymatchingv3.ExtensionWithMatcher {
if filterTypedConfig == nil {
return nil
}

return &envoymatchingv3.ExtensionWithMatcher{
ExtensionConfig: &envoycorev3.TypedExtensionConfig{
Name: "composite_ext_proc",
Name: compositeName,
TypedConfig: utils.MustMessageToAny(&envoycompositev3.Composite{}),
},
XdsMatcher: &xdsmatcherv3.Matcher{
Expand All @@ -359,7 +393,7 @@ func buildCompositeExtProcFilter(in v1alpha1.ExtProcProvider, envoyGrpcService *
Input: &xdscorev3.TypedExtensionConfig{
Name: globalFilterDisableMetadataKey,
TypedConfig: utils.MustMessageToAny(&envoynetworkv3.DynamicMetadataInput{
Filter: extProcGlobalDisableFilterMetadataNamespace,
Filter: metadataNamespace,
Path: []*envoynetworkv3.DynamicMetadataInput_PathSegment{
{
Segment: &envoynetworkv3.DynamicMetadataInput_PathSegment_Key{
Expand All @@ -370,7 +404,7 @@ func buildCompositeExtProcFilter(in v1alpha1.ExtProcProvider, envoyGrpcService *
}),
},
// This matcher succeeds when disable=true is not found in the dynamic metadata
// for the extProcGlobalDisableFilterMetadataNamespace
// for the metadataNamespace
Matcher: &xdsmatcherv3.Matcher_MatcherList_Predicate_SinglePredicate_CustomMatch{
CustomMatch: &xdscorev3.TypedExtensionConfig{
Name: "envoy.matching.matchers.metadata_matcher",
Expand All @@ -392,10 +426,7 @@ func buildCompositeExtProcFilter(in v1alpha1.ExtProcProvider, envoyGrpcService *
Action: &xdscorev3.TypedExtensionConfig{
Name: "composite-action",
TypedConfig: utils.MustMessageToAny(&envoycompositev3.ExecuteFilterAction{
TypedConfig: &envoycorev3.TypedExtensionConfig{
Name: "envoy.filters.http.ext_proc",
TypedConfig: utils.MustMessageToAny(filter),
},
TypedConfig: filterTypedConfig,
}),
},
},
Expand Down
42 changes: 30 additions & 12 deletions internal/kgateway/extensions2/plugins/trafficpolicy/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ import (
)

const (
PayloadInMetadata = "payload"
jwtFilterNamePrefix = "jwt"
jwtConfigMapKey = "jwks"
PayloadInMetadata = "payload"
jwtFilterNamePrefix = "jwt"
jwtConfigMapKey = "jwks"
jwtGlobalDisableFilterName = "global_disable/jwt"
jwtGlobalDisableFilterMetadataNamespace = "dev.kgateway.disable_jwt"
)

type jwtIr struct {
perProviderConfig []*perProviderJwtConfig
perProviderConfig []*perProviderJwtConfig
disableAllProviders bool
}

type perProviderJwtConfig struct {
Expand All @@ -53,6 +56,10 @@ func (j *jwtIr) Equals(other PolicySubIR) bool {
return j == nil && otherJwt == nil
}

if j.disableAllProviders != otherJwt.disableAllProviders {
return false
}

return slices.EqualFunc(j.perProviderConfig, otherJwt.perProviderConfig, func(a, b *perProviderJwtConfig) bool {
return proto.Equal(a.perRouteConfig, b.perRouteConfig) &&
cmputils.CompareWithNils(a.provider, b.provider, func(a, b *TrafficPolicyGatewayExtensionIR) bool {
Expand All @@ -67,15 +74,15 @@ func (p *trafficPolicyPluginGwPass) handleJwt(fcn string, pCtxTypedFilterConfig
return
}

if jwtIr.disableAllProviders {
pCtxTypedFilterConfig.AddTypedConfig(jwtGlobalDisableFilterName, EnableFilterPerRoute)
return
}

for _, cfg := range jwtIr.perProviderConfig {
if cfg == nil {
continue
}
providerName := providerName(cfg.provider)
if cfg.perRouteConfig != nil {
jwtName := jwtFilterName(providerName)
pCtxTypedFilterConfig.AddTypedConfig(jwtName, cfg.perRouteConfig)
}
jwtName := jwtFilterName(providerName)
pCtxTypedFilterConfig.AddTypedConfig(jwtName, cfg.perRouteConfig)
p.jwtPerProvider.Add(fcn, providerName, cfg.provider)
}
}
Expand All @@ -101,7 +108,18 @@ func constructJwt(
return nil
}

provider, err := fetchGatewayExtension(krtctx, spec.ExtensionRef, in.GetNamespace())
if spec.Disable != nil {
out.jwt = &jwtIr{
disableAllProviders: true,
}
return nil
}

if spec.ExtensionRef == nil {
// shouldn't happen due to CRD validation
return fmt.Errorf("jwt: extensionRef is required if disable is not set")
}
provider, err := fetchGatewayExtension(krtctx, *spec.ExtensionRef, in.GetNamespace())
if err != nil {
return fmt.Errorf("jwt: %w", err)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,9 @@ func (p *trafficPolicyPluginGwPass) HttpFilters(fcc ir.FilterChainCommon) ([]fil
stagedFilters = append(stagedFilters, stagedExtAuthFilter)
}

// TODO: Add support for global jwt disable filter
if len(p.jwtPerProvider.Providers[fcc.FilterChainName]) > 0 {
stagedFilters = AddDisableFilterIfNeeded(stagedFilters, jwtGlobalDisableFilterName, jwtGlobalDisableFilterMetadataNamespace)
}
for _, provider := range p.jwtPerProvider.Providers[fcc.FilterChainName] {
jwtFilter := provider.Extension.Jwt
if jwtFilter == nil {
Expand All @@ -435,7 +437,7 @@ func (p *trafficPolicyPluginGwPass) HttpFilters(fcc ir.FilterChainCommon) ([]fil
filters.DuringStage(filters.AuthNStage),
)

// stagedJwtFilter.Filter.Disabled = true
stagedJwtFilter.Filter.Disabled = true
stagedFilters = append(stagedFilters, stagedJwtFilter)
}

Expand Down
22 changes: 22 additions & 0 deletions internal/kgateway/translator/gateway/gateway_translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,28 @@ func TestBasic(t *testing.T) {
},
})
})

t.Run("JWT Policy and RBAC", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "jwt/rbac.yaml",
outputFile: "jwt/rbac.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "gw",
},
})
})

t.Run("JWT Policy at gateway level with disable on route", func(t *testing.T) {
test(t, translatorTestCase{
inputFile: "jwt/gateway-disable.yaml",
outputFile: "jwt/gateway-disable.yaml",
gwNN: types.NamespacedName{
Namespace: "default",
Name: "example-gateway",
},
})
})
}

func TestValidation(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: example-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
hostnames:
- "example.com"
rules:
- backendRefs:
- name: example-svc
port: 80
matches:
- path:
type: PathPrefix
value: /foo
- backendRefs:
- name: example-svc
port: 80
matches:
- path:
type: PathPrefix
value: /bar
---
apiVersion: v1
kind: Service
metadata:
name: example-svc
spec:
selector:
test: test
ports:
- protocol: TCP
port: 80
targetPort: test
---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: gateway-test
spec:
targetRefs:
- kind: Gateway
group: gateway.networking.k8s.io
name: example-gateway
jwt:
extensionRef:
name: jwt-ext-3
---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: TrafficPolicy
metadata:
name: route-test
spec:
targetRefs:
- kind: HTTPRoute
group: gateway.networking.k8s.io
name: example-route
jwt:
disable: {}
---
apiVersion: gateway.kgateway.dev/v1alpha1
kind: GatewayExtension
metadata:
name: jwt-ext-3
spec:
type: JWTProviders
jwtProviders:
- name: gateway-example
claimsToHeaders:
- name: gateway
header: x-gateway
issuer: https://gateway.example.com
jwks:
local:
inline: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruK9KacQjDePRyfG7oPI
aqAIyCeOCBIGB2nBbDLGp1Szdm7rsWcrzGf7Avpa/ijLV9huoNvpdflld4B+SaT7
m3EDDMDUyA4LayJC5JBI10Qfu3Qn8BEpcdN2uRiycXOzgsoIXneXp9hENlS5Vsr3
ur5BaBCc+BZZRRaXDTLy6KyD1Pyd6XRsxyZXt/SYOIww0NSt5u0CTyZUGJhQungJ
pI8Hhrzdf87mLZGZd16dOGObE5LqFwk2prN3D0+owLsA25WJOPZXizxpTB4tPvJu
YGATajDpzrHf+WXgOgvwyxaHJSN/fE+eFuRT3ooDaAuytsfYotsn4z/ajdEPSwXY
CwIDAQAB
-----END PUBLIC KEY-----
Loading