For the complete documentation index, see llms.txt. Markdown versions of all docs pages are available by appending .md to any docs URL.
Restrict access with claim-based rules
Authorize requests based on JWT claims and OAuth 2.0 scopes by using the entRBAC field.
Use the entRBAC field alongside the entJWT field in the EnterpriseKgatewayTrafficPolicy resource to allow or deny requests based on the claims in a verified JWT.
How claim matching works
You define the claims that you want to match in the jwtPrincipal field of your EnterpriseKgatewayTrafficPolicy resource. For each jwtPrincipal, you specify the following:
- How you want to match each claim, by using the
matcherfield. - Which claims to match, and whether a request must satisfy all of them (AND) or any of them (OR).
matcher
The matcher field on a jwtPrincipal sets how each claim in the verified JWT is compared to the values that you define in the policy. The matcher applies to all claims that you defined in a jwtPrincipal.
| Matcher | Behavior |
|---|---|
ExactString (default) | The full claim value must exactly equal the specified string. |
Boolean | The claim value must equal true or false. |
ListContains | The claim value is a JSON array. The array must contain the specified string. |
SpaceDelimitedStringContains | The claim value is a space-delimited string, such as an OAuth 2.0 scope claim. Each space-delimited token in the value that you specify must appear in that string. |
AND or OR claim matching
Principals combine with the following logic:
- AND: All claims within a single
jwtPrincipalmust match. - OR: List multiple
jwtPrincipalentries. Access is granted if any one of them matches.
entRBAC:
policies:
example-policy:
# restrict to specific HTTP methods and paths; omit to apply to all
permissions:
methods:
- GET
pathPrefix: /anything
principals:
# AND: both claims must match
- jwtPrincipal:
claims:
key1: value1
key2: value2
# OR: a request that matches this separate principal is also allowed
- jwtPrincipal:
claims:
key3: value3Before you begin
Follow the Get started guide to install Solo Enterprise for kgateway.
Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESSkubectl port-forward deployment/http -n kgateway-system 8080:8080
Save example JWTs for Alice and Bob in environment variables. This example uses an inline JWKS and pre-minted tokens for Alice and Bob, which are signed with the private key that matches that JWKS. To generate tokens with different claims, use the JWT generator tool, or configure an IdP and swap the inline JWKS for a remote one as shown in Set up staged JWT auth.
Save Alice’s token. Alice has
team: devandorg: solo.ioclaims.export ALICE_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbG8tcHVibGljLWtleS0wMDEifQ.eyJpc3MiOiJzb2xvLmlvIiwib3JnIjoic29sby5pbyIsInN1YiI6ImFsaWNlIiwidGVhbSI6ImRldiIsImV4cCI6MjA3NDI3NDg4NCwibGxtcyI6eyJvcGVuYWkiOlsiZ3B0LTMuNS10dXJibyJdfX0.il5Rjsad65jpQR_pyRzBdEKFSj-ERmBf4K2VksvGvswWVv4n79lYERslr4KCECuiz9y_T-xUiQ9IkhW3YHzl5zo1kajhhIg7Nhnl1AvAqODbnF6wYpLRk0Npna_2T6lK3Yj54qQGi6vXG3IMRpo1_o2DrbdlKx2k_WFegCoQyyYazb4z3ZXfWvTiWqQDJA5wWcM3-jKzAWfNM8zgZWa-1BeAHDvpLcfWtuXEGSjkdCW0FQJOTjgIEqACnnXb2Jio0tWgelh9hDPILI-tvanj3iKCjpf3uF6g8QWSBNoVFfu7F1jJgj5Aj1sX8AV-CQVu2aQx3EHRZ1mL_3w3qSRWPwSave Bob’s token. Bob has
team: opsandorg: solo.ioclaims.export BOB_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbG8tcHVibGljLWtleS0wMDEifQ.eyJpc3MiOiJzb2xvLmlvIiwib3JnIjoic29sby5pbyIsInN1YiI6ImJvYiIsInRlYW0iOiJvcHMiLCJleHAiOjIwNzQyNzQ5NTQsImxsbXMiOnsibWlzdHJhbGFpIjpbIm1pc3RyYWwtbGFyZ2UtbGF0ZXN0Il19fQ.GF_uyLpZSTT1DIvJeO_eish1WDjMaS4BQSifGQhqPRLjzu3nXtPkaBRjceAmJi9gKZYAzkT25MIrT42ZIe3bHilrd1yqittTPWrrM4sWDDeldnGsfU07DWJHyboNapYR-KZGImSmOYshJlzm1tT_Bjt3-RK3OBzYi90_wl0dyAl9D7wwDCzOD4MRGFpoMrws_OgVrcZQKcadvIsH8figPwN4mK1U_1mxuL08RWTu92xBcezEO4CdBaFTUbkYN66Y2vKSTyPCxg3fLtg1mvlzU1-Wgm2xZIiPiarQHt6Uq7v9ftgzwdUBQM1AYLvUVhCN6XkkR9OU3p0OXiqEDjAxcg
Set up a JWT policy with claim-based access control
In this example, you create a JWT policy with an inline JWKS endpoint that you use to verify incoming JWTs. Only JWTs with both team: dev and org: solo.io (AND semantics) claims are allowed to send GET requests to httpbin’s /anything endpoint.
Apply the policy.
kubectl apply -f- <<EOF apiVersion: enterprisekgateway.solo.io/v1alpha1 kind: EnterpriseKgatewayTrafficPolicy metadata: name: jwt-claims-policy namespace: httpbin spec: targetRefs: - group: gateway.networking.k8s.io kind: HTTPRoute name: httpbin entJWT: afterExtAuth: providers: selfminted: issuer: solo.io jwks: local: key: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}' entRBAC: policies: require-dev-team: permissions: methods: - GET pathPrefix: /anything principals: - jwtPrincipal: claims: team: dev org: solo.io EOFField Description entJWTConfigures JWT verification. This example runs verification in the afterExtAuthstage;entRBACthen evaluates the verified claims.entRBAC.policiesA map of named RBAC policies. policies.*.permissionsRestricts the HTTP methods and path prefix the policy applies to. Omit to apply the policy to all methods and paths. principals[].jwtPrincipal.claimsA map of claim names and values. All entries in one jwtPrincipalmust match (AND semantics).principals[].jwtPrincipal.matcherHow to compare the claim value. Defaults to ExactString. See How claim matching works.Send a request with Alice’s token. Alice has the
team: devandorg: solo.ioclaims that match the claims that you defined in the policy. Verify that you receive a200 OKresponse.curl -vik http://$INGRESS_GW_ADDRESS:8080/anything \ -H "host: www.example.com:8080" \ --header "Authorization: Bearer $ALICE_TOKEN"curl -vik localhost:8080/anything \ -H "host: www.example.com:8080" \ --header "Authorization: Bearer $ALICE_TOKEN"Example output:
HTTP/1.1 200 OK ... { "args": {}, "headers": { "Authorization": [ "Bearer <ALICE_TOKEN>" ], "Host": [ "www.example.com:8080" ] } }Send the same request with Bob’s token. Bob has the
team: opsclaim, which does not match the requireddevvalue. Verify that you receive a403 Forbiddenresponse.curl -vik http://$INGRESS_GW_ADDRESS:8080/anything \ -H "host: www.example.com:8080" \ --header "Authorization: Bearer $BOB_TOKEN"curl -vik localhost:8080/anything \ -H "host: www.example.com:8080" \ --header "Authorization: Bearer $BOB_TOKEN"Example output:
HTTP/1.1 403 Forbidden RBAC: access denied
Other configurations
Each of the following examples is a complete policy that replaces the one from the previous section. The entRBAC rules of multiple policies that target the same HTTPRoute do not combine. Only one policy takes effect, based on merge priority, so delete the previous policy before you apply a new one to make sure that your new rules take effect.
kubectl delete EnterpriseKgatewayTrafficPolicy jwt-claims-policy -n httpbinThe boolean, list, and scope examples require claims that the Alice and Bob tokens do not include. Mint test tokens that carry those claims with the JWT generator tool and the same private key that matches the inline JWKS.
Allow any of several principals (OR)
List multiple jwtPrincipal entries to grant access when any one matches. This policy has no permissions block, so it applies to all methods and paths. It allows requests from either the dev or ops team, so both Alice and Bob receive a 200 OK.
kubectl apply -f- <<EOF
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
name: jwt-claims-policy
namespace: httpbin
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
entJWT:
afterExtAuth:
providers:
selfminted:
issuer: solo.io
jwks:
local:
key: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
entRBAC:
policies:
allow-dev-or-ops:
principals:
- jwtPrincipal:
claims:
team: dev
- jwtPrincipal:
claims:
team: ops
EOFMatch a boolean claim
Use the Boolean matcher when the claim value is true or false. The following policy requires the JWT to have the active: true claim. A token with active: true receives a 200 OK, and a token with active: false or no active claim receives a 403 Forbidden response.
kubectl apply -f- <<EOF
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
name: jwt-claims-policy
namespace: httpbin
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
entJWT:
afterExtAuth:
providers:
selfminted:
issuer: solo.io
jwks:
local:
key: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
entRBAC:
policies:
require-active:
principals:
- jwtPrincipal:
claims:
active: "true"
matcher: Boolean
EOFMatch a value in a list claim
Use the ListContains matcher when the claim value is a JSON array. The principal matches if the array contains the specified string. The following policy requires the JWT to have a roles claim that contains admin. A token with roles: ["admin", "developer"] receives a 200 OK; a token with roles: ["developer"] receives a 403 Forbidden response.
kubectl apply -f- <<EOF
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
name: jwt-claims-policy
namespace: httpbin
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
entJWT:
afterExtAuth:
providers:
selfminted:
issuer: solo.io
jwks:
local:
key: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
entRBAC:
policies:
require-admin-role:
principals:
- jwtPrincipal:
claims:
roles: admin
matcher: ListContains
EOFMatch OAuth 2.0 scopes
OAuth 2.0 scopes (RFC 6749 §3.3) are a space-delimited list of strings in a single scope claim, such as openid email profile. Use the SpaceDelimitedStringContains matcher to require one or more scopes within that claim. When you list several scopes in a single value, such as email profile, all of them must be present (AND); list separate principals to allow any one scope (OR).
Scope claims typically come from an IdP, so in production you verify the token with a remote JWKS as shown in Set up staged JWT auth. The following example reuses the inline JWKS for consistency with the other examples. Mint a token that carries a scope claim such as email profile. The policy requires both the email and profile scopes.
kubectl apply -f- <<EOF
apiVersion: enterprisekgateway.solo.io/v1alpha1
kind: EnterpriseKgatewayTrafficPolicy
metadata:
name: jwt-claims-policy
namespace: httpbin
spec:
targetRefs:
- group: gateway.networking.k8s.io
kind: HTTPRoute
name: httpbin
entJWT:
afterExtAuth:
providers:
selfminted:
issuer: solo.io
jwks:
local:
key: '{"keys":[{"kty":"RSA","kid":"solo-public-key-001","use":"sig","alg":"RS256","n":"AOfIaJMUm7564sWWNHaXt_hS8H0O1Ew59-nRqruMQosfQqa7tWne5lL3m9sMAkfa3Twx0LMN_7QqRDoztvV3Wa_JwbMzb9afWE-IfKIuDqkvog6s-xGIFNhtDGBTuL8YAQYtwCF7l49SMv-GqyLe-nO9yJW-6wIGoOqImZrCxjxXFzF6mTMOBpIODFj0LUZ54QQuDcD1Nue2LMLsUvGa7V1ZHsYuGvUqzvXFBXMmMS2OzGir9ckpUhrUeHDCGFpEM4IQnu-9U8TbAJxKE5Zp8Nikefr2ISIG2Hk1K2rBAc_HwoPeWAcAWUAR5tWHAxx-UXClSZQ9TMFK850gQGenUp8","e":"AQAB"}]}'
entRBAC:
policies:
require-both-scopes:
principals:
- jwtPrincipal:
claims:
scope: "email profile"
matcher: SpaceDelimitedStringContains
EOFCleanup
You can optionally remove the resources that you set up as part of this guide.kubectl delete EnterpriseKgatewayTrafficPolicy jwt-claims-policy -n httpbin 2>/dev/null || true