diff --git a/api/v1alpha1/jwt_types.go b/api/v1alpha1/jwt_types.go index f35ea8e087b..1b9ca41d42d 100644 --- a/api/v1alpha1/jwt_types.go +++ b/api/v1alpha1/jwt_types.go @@ -54,7 +54,13 @@ type JWTProvider struct { // The claim must be of type; string, int, double, bool. Array type claims are not supported // ClaimToHeaders []ClaimToHeader `json:"claimToHeaders,omitempty"` - // TODO: Add TBD JWT fields based on defined use cases. + + // ExtractFrom defines different ways to extract the JWT token from HTTP request. + // If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema + // or access_token from query parameters. + // + // +optional + ExtractFrom *JWTExtractor `json:"extractFrom,omitempty"` } // RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote @@ -81,3 +87,12 @@ type ClaimToHeader struct { // to separate the JSON name path. Claim string `json:"claim"` } + +// JWTExtractor defines a custom JWT token extraction from HTTP request. +type JWTExtractor struct { + // Cookies represents a list of cookie names to extract the JWT token from. + // If specified, Envoy will extract the JWT token from the listed cookies and validate each of them. + // If any cookie is found to be an invalid JWT, a 401 error will be returned. + // + Cookies []string `json:"cookies,omitempty"` +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 92d0b64ba52..df57bd7512d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1276,6 +1276,26 @@ func (in *JWT) DeepCopy() *JWT { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JWTExtractor) DeepCopyInto(out *JWTExtractor) { + *out = *in + if in.Cookies != nil { + in, out := &in.Cookies, &out.Cookies + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTExtractor. +func (in *JWTExtractor) DeepCopy() *JWTExtractor { + if in == nil { + return nil + } + out := new(JWTExtractor) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { *out = *in @@ -1290,6 +1310,11 @@ func (in *JWTProvider) DeepCopyInto(out *JWTProvider) { *out = make([]ClaimToHeader, len(*in)) copy(*out, *in) } + if in.ExtractFrom != nil { + in, out := &in.ExtractFrom, &out.ExtractFrom + *out = new(JWTExtractor) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JWTProvider. diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml index 84c7b4e6822..2d32a0fa114 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_securitypolicies.yaml @@ -199,6 +199,23 @@ spec: - header type: object type: array + extractFrom: + description: ExtractFrom defines different ways to extract + the JWT token from HTTP request. If empty, it defaults + to extract JWT token from the Authorization HTTP request + header using Bearer schema or access_token from query + parameters. + properties: + cookies: + description: Cookies represents a list of cookie names + to extract the JWT token from. If specified, Envoy + will extract the JWT token from the listed cookies + and validate each of them. If any cookie is found + to be an invalid JWT, a 401 error will be returned. + items: + type: string + type: array + type: object issuer: description: Issuer is the principal that issued the JWT and takes the form of a URL or email address. For additional diff --git a/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.in.yaml b/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.in.yaml new file mode 100644 index 00000000000..96580c84971 --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.in.yaml @@ -0,0 +1,121 @@ +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 +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-2 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + namespace: default + name: grpcroute-1 + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 +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-2 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: envoy-gateway + name: policy-for-gateway + spec: + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + jwt: + providers: + - name: example1 + issuer: https://one.example.com + audiences: + - one.foo.com + remoteJWKS: + uri: https://one.example.com/jwt/public-key/jwks.json + claimToHeaders: + - header: one-route-example-key + claim: claim1 + - name: example2 + issuer: https://two.example.com + audiences: + - two.foo.com + remoteJWKS: + uri: https://two.example.com/jwt/public-key/jwks.json + claimToHeaders: + - header: two-route-example-key + claim: claim2 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + jwt: + providers: + - name: example3 + issuer: https://three.example.com + audiences: + - three.foo.com + remoteJWKS: + uri: https://three.example.com/jwt/public-key/jwks.json + claimToHeaders: + - header: three-route-example-key + claim: claim3 + extractFrom: + cookies: + - session_access_token diff --git a/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.out.yaml b/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.out.yaml new file mode 100755 index 00000000000..549feda35ab --- /dev/null +++ b/internal/gatewayapi/testdata/securitypolicy-with-jwt-with-custom-extractor.out.yaml @@ -0,0 +1,345 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-2 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +grpcRoutes: +- apiVersion: gateway.networking.k8s.io/v1alpha2 + kind: GRPCRoute + metadata: + creationTimestamp: null + name: grpcroute-1 + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-2 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-2 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: "" + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 + envoy-gateway/gateway-2: + proxy: + listeners: + - address: "" + ports: + - containerPort: 10080 + name: http + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-2 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-2 +securityPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-route + namespace: default + spec: + jwt: + providers: + - audiences: + - three.foo.com + claimToHeaders: + - claim: claim3 + header: three-route-example-key + extractFrom: + cookies: + - session_access_token + issuer: https://three.example.com + name: example3 + remoteJWKS: + uri: https://three.example.com/jwt/public-key/jwks.json + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + namespace: default + status: + conditions: + - lastTransitionTime: null + message: SecurityPolicy has been accepted. + reason: Accepted + status: "True" + type: Accepted +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: SecurityPolicy + metadata: + creationTimestamp: null + name: policy-for-gateway + namespace: envoy-gateway + spec: + jwt: + providers: + - audiences: + - one.foo.com + claimToHeaders: + - claim: claim1 + header: one-route-example-key + issuer: https://one.example.com + name: example1 + remoteJWKS: + uri: https://one.example.com/jwt/public-key/jwks.json + - audiences: + - two.foo.com + claimToHeaders: + - claim: claim2 + header: two-route-example-key + issuer: https://two.example.com + name: example2 + remoteJWKS: + uri: https://two.example.com/jwt/public-key/jwks.json + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + status: + conditions: + - lastTransitionTime: null + message: SecurityPolicy has been accepted. + reason: Accepted + status: "True" + type: Accepted +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: true + name: envoy-gateway/gateway-1/http + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: grpcroute/default/grpcroute-1/rule/0 + settings: + - endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: GRPC + weight: 1 + hostname: '*' + jwt: + providers: + - audiences: + - one.foo.com + claimToHeaders: + - claim: claim1 + header: one-route-example-key + issuer: https://one.example.com + name: example1 + remoteJWKS: + uri: https://one.example.com/jwt/public-key/jwks.json + - audiences: + - two.foo.com + claimToHeaders: + - claim: claim2 + header: two-route-example-key + issuer: https://two.example.com + name: example2 + remoteJWKS: + uri: https://two.example.com/jwt/public-key/jwks.json + name: grpcroute/default/grpcroute-1/rule/0/match/-1/* + envoy-gateway/gateway-2: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-2/http + port: 10080 + routes: + - backendWeights: + invalid: 0 + valid: 0 + destination: + name: httproute/default/httproute-1/rule/0 + settings: + - endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + jwt: + providers: + - audiences: + - three.foo.com + claimToHeaders: + - claim: claim3 + header: three-route-example-key + extractFrom: + cookies: + - session_access_token + issuer: https://three.example.com + name: example3 + remoteJWKS: + uri: https://three.example.com/jwt/public-key/jwks.json + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/xds/translator/jwt.go b/internal/xds/translator/jwt.go index 125fc7de16f..13d11e5bf91 100644 --- a/internal/xds/translator/jwt.go +++ b/internal/xds/translator/jwt.go @@ -144,6 +144,10 @@ func buildJWTAuthn(irListener *ir.HTTPListener) (*jwtauthnv3.JwtAuthentication, ClaimToHeaders: claimToHeaders, } + if irProvider.ExtractFrom != nil { + jwtProvider.FromCookies = irProvider.ExtractFrom.Cookies + } + providerKey := fmt.Sprintf("%s/%s", route.Name, irProvider.Name) jwtProviders[providerKey] = jwtProvider reqs = append(reqs, &jwtauthnv3.JwtRequirement{ diff --git a/internal/xds/translator/testdata/in/xds-ir/jwt-custom-extractor.yaml b/internal/xds/translator/testdata/in/xds-ir/jwt-custom-extractor.yaml new file mode 100644 index 00000000000..f8a56289c65 --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/jwt-custom-extractor.yaml @@ -0,0 +1,28 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + routes: + - name: "first-route" + hostname: "*" + pathMatch: + exact: "foo/bar" + jwt: + providers: + - name: example + issuer: https://www.example.com + audiences: + - foo.com + remoteJWKS: + uri: https://localhost/jwt/public-key/jwks.json + extractFrom: + cookies: + - session_access_token + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.clusters.yaml new file mode 100644 index 00000000000..7c8285ba0f6 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.clusters.yaml @@ -0,0 +1,46 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest + lbPolicy: LEAST_REQUEST + name: first-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + dnsRefreshRate: 30s + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: localhost_443 + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: localhost + portValue: 443 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: localhost_443/backend/0 + name: localhost_443 + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + validationContext: + trustedCa: + filename: /etc/ssl/certs/ca-certificates.crt + type: STRICT_DNS diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.endpoints.yaml new file mode 100644 index 00000000000..3b3f2d09076 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.endpoints.yaml @@ -0,0 +1,12 @@ +- clusterName: first-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: first-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml new file mode 100644 index 00000000000..2481c634ea7 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.listeners.yaml @@ -0,0 +1,55 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.jwt_authn + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication + providers: + first-route/example: + audiences: + - foo.com + fromCookies: + - session_access_token + issuer: https://www.example.com + payloadInMetadata: https://www.example.com + remoteJwks: + asyncFetch: {} + cacheDuration: 300s + httpUri: + cluster: localhost_443 + timeout: 5s + uri: https://localhost/jwt/public-key/jwks.json + retryPolicy: {} + requirementMap: + first-route: + providerName: first-route/example + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + statPrefix: http + upgradeConfigs: + - upgradeType: websocket + useRemoteAddress: true + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.routes.yaml new file mode 100644 index 00000000000..c73bec09093 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/jwt-custom-extractor.routes.yaml @@ -0,0 +1,16 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener/* + routes: + - match: + path: foo/bar + name: first-route + route: + cluster: first-route-dest + typedPerFilterConfig: + envoy.filters.http.jwt_authn: + '@type': type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.PerRouteConfig + requirementName: first-route diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index 6df2f5bcd9a..953932b7996 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -199,6 +199,9 @@ func TestTranslateXds(t *testing.T) { { name: "listener-proxy-protocol", }, + { + name: "jwt-custom-extractor", + }, } for _, tc := range testCases { diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 1698860d05a..09b5e0e62e1 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -896,6 +896,20 @@ _Appears in:_ When multiple JWT providers are specified, the JWT is considered valid if any of the providers successfully validate the JWT. For additional details, see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/jwt_authn_filter.html. | +#### JWTExtractor + + + +JWTExtractor defines a custom JWT token extraction from HTTP request. + +_Appears in:_ +- [JWTProvider](#jwtprovider) + +| Field | Description | +| --- | --- | +| `cookies` _string array_ | Cookies represents a list of cookie names to extract the JWT token from. If specified, Envoy will extract the JWT token from the listed cookies and validate each of them. If any cookie is found to be an invalid JWT, a 401 error will be returned. | + + #### JWTProvider @@ -912,6 +926,7 @@ _Appears in:_ | `audiences` _string array_ | Audiences is a list of JWT audiences allowed access. For additional details, see https://tools.ietf.org/html/rfc7519#section-4.1.3. If not provided, JWT audiences are not checked. | | `remoteJWKS` _[RemoteJWKS](#remotejwks)_ | RemoteJWKS defines how to fetch and cache JSON Web Key Sets (JWKS) from a remote HTTP/HTTPS endpoint. | | `claimToHeaders` _[ClaimToHeader](#claimtoheader) array_ | ClaimToHeaders is a list of JWT claims that must be extracted into HTTP request headers For examples, following config: The claim must be of type; string, int, double, bool. Array type claims are not supported | +| `extractFrom` _[JWTExtractor](#jwtextractor)_ | ExtractFrom defines different ways to extract the JWT token from HTTP request. If empty, it defaults to extract JWT token from the Authorization HTTP request header using Bearer schema or access_token from query parameters. | #### KubernetesContainerSpec