Skip to content

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

Page as Markdown

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 matcher field.
  • 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.

MatcherBehavior
ExactString (default)The full claim value must exactly equal the specified string.
BooleanThe claim value must equal true or false.
ListContainsThe claim value is a JSON array. The array must contain the specified string.
SpaceDelimitedStringContainsThe 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 jwtPrincipal must match.
  • OR: List multiple jwtPrincipal entries. 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: value3

Before you begin

  1. Follow the Get started guide to install Solo Enterprise for kgateway.

  2. Follow the Sample app guide to create a gateway proxy with an HTTP listener and deploy the httpbin sample app.

  3. 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_ADDRESS  
    kubectl port-forward deployment/http -n kgateway-system 8080:8080

  1. 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.

    1. Save Alice’s token. Alice has team: dev and org: solo.io claims.

      export ALICE_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InNvbG8tcHVibGljLWtleS0wMDEifQ.eyJpc3MiOiJzb2xvLmlvIiwib3JnIjoic29sby5pbyIsInN1YiI6ImFsaWNlIiwidGVhbSI6ImRldiIsImV4cCI6MjA3NDI3NDg4NCwibGxtcyI6eyJvcGVuYWkiOlsiZ3B0LTMuNS10dXJibyJdfX0.il5Rjsad65jpQR_pyRzBdEKFSj-ERmBf4K2VksvGvswWVv4n79lYERslr4KCECuiz9y_T-xUiQ9IkhW3YHzl5zo1kajhhIg7Nhnl1AvAqODbnF6wYpLRk0Npna_2T6lK3Yj54qQGi6vXG3IMRpo1_o2DrbdlKx2k_WFegCoQyyYazb4z3ZXfWvTiWqQDJA5wWcM3-jKzAWfNM8zgZWa-1BeAHDvpLcfWtuXEGSjkdCW0FQJOTjgIEqACnnXb2Jio0tWgelh9hDPILI-tvanj3iKCjpf3uF6g8QWSBNoVFfu7F1jJgj5Aj1sX8AV-CQVu2aQx3EHRZ1mL_3w3qSRWPw
    2. Save Bob’s token. Bob has team: ops and org: solo.io claims.

      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.

  1. 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
    EOF
    FieldDescription
    entJWTConfigures JWT verification. This example runs verification in the afterExtAuth stage; entRBAC then 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 jwtPrincipal must match (AND semantics).
    principals[].jwtPrincipal.matcherHow to compare the claim value. Defaults to ExactString. See How claim matching works.
  2. Send a request with Alice’s token. Alice has the team: dev and org: solo.io claims that match the claims that you defined in the policy. Verify that you receive a 200 OK response.

    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"
        ]
      }
    }
  3. Send the same request with Bob’s token. Bob has the team: ops claim, which does not match the required dev value. Verify that you receive a 403 Forbidden response.

    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 httpbin

The 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
EOF

Match 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
EOF

Match 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
EOF

Match 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
EOF

Cleanup

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
Was this page helpful?