JWT claim- and scope-based auth

To access your destinations in the service mesh, you can require specific JWT claims or scopes for authorization. These rules can match values extracted from the JWT for allowed issuers, expiries, audiences, scopes, or other valid JSON claims from the JWT payload.

About claim-based JWT policies

You can restrict a request's access based on the claims and scopes in a JWT. For more information about JWTs, see the JWT overview and API docs.

Claims are key-value pairs that provide identity details, such as the subject's user ID, the entity that issued the token, and expiration time. RFC 7519 spec reserves seven claims, and the IANA JSON Web Token Claims outline many more registered claims to encourage interoperability across providers. Further, your OIDC provider might have custom claims, such as described in the Auth0 docs.

For Gloo Platform JWT policies, claims have the following features:

The claims field does not support the following features:

Scopes are a special type of claim that represents a specific permission that a client has. These scopes might be returned from an Identity and Access Management (IAM) provider, and are typically formatted similar to is:developer or read:profile.

For Gloo Platform JWT policies, scopes have the following features:

Scopes do not support the following features:

More considerations for JWT policies:

Before you begin

This guide assumes that you use the same names for components like clusters, workspaces, and namespaces as in the getting started. If you have different names, make sure to update the sample configuration files in this guide.
  1. Complete the multicluster getting started guide to set up the following testing environment.
    • Three clusters along with environment variables for the clusters and their Kubernetes contexts.
    • The Gloo Platform CLI, meshctl, along with other CLI tools such as kubectl and istioctl.
    • The Gloo management server in the management cluster, and the Gloo agents in the workload clusters.
    • Istio installed in the workload clusters.
    • A simple Gloo workspace setup.
  2. Install Bookinfo and other sample apps.

Configure JWT policies with claims

Review the following examples for claim- and scope-based JWT authorization. You can apply a JWT policy at the destination or route level, but you cannot apply multiple JWT policies to the same destination or to the same route in a route table. If you try to apply multiple policies, only the policy that you created first takes effect. In general, you apply policies to routes to protect ingress traffic through the gateway, and to destinations to protect traffic within the service mesh. For more information, see Applying policies and the API docs.

You can configure key-value pairs of claims. The following example:

  • Requires that a request meets the conditions for all of the name, email, and org claims.
  • Requires that the name claim equals the value glooy.
  • Requires that the email claim equals the value user2@solo.io.
  • Requires that the org claim equals either internal or partner.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
  ...
  config:
    claims:
    - key: "name"
      values:
      - "glooy"
    - key: "email"
      values:
      - "user2@solo.io"
    - key: "org"
      values:
      - "internal"
      - "partner"

You can configure a list of scopes. The following example:

  • Requires that a request has all of the listed scopes.
  • Requires the ec2:DescribeInstances scope.
  • Requires the s3:ListBuckets scope.

JWT payloads with the specific listed scopes, as well as broader scopes such as ec2:* or s3:*, would meet the scope requirement.

apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
  ...
  config:
    requiredScopes:
    - "ec2:DescribeInstances"
    - "s3:ListBuckets"

You can use notValues for claims to deny requests that have a certain value. The following example:

  • Requires that a request meets the conditions for the name and aud claims.
  • Denies a request that has the name claim.
  • Denies a request that has either product.solo.io or marketing.solo.io as a value in the aud claim. Claims with other aud values are allowed.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
  ...
  config:
    claims:
    - key: "name"
      notValues:
      - "*"
    - key: "aud"
      notValues:
      - "product.solo.io"
      - "marketing.solo.io"

You can use wildcards to allow or deny a wide range of possible claim values. The following example:

  • Requires that a request meets the conditions for all of the name, email, and aud claims.
  • Denies a request that has the name claim.
  • Requires that the email claim has a value of any @solo.io email address.
  • Requires that a request has a value for the aud claim.
apiVersion: security.policy.gloo.solo.io/v2
kind: JWTPolicy
  ...
  config:
    claims:
    - key: "name"
      notValues:
      - "*"
    - key: "email"
      values:
      - "*@solo.io"
    - key: "aud"
      values:
      - "*"

Try out JWT policies with claims

To test claims-based JWT authorization, you can create a JWT policy that requires certain claims and scopes. Then, you can create two JWT tokens. One token has the required claims and scope, while the other token does not.

  1. Save the example JWT policy for the httpbin route. For more information about this policy, see the configuration example.

    cat << EOF > jwt-policy_authz.yaml
    apiVersion: security.policy.gloo.solo.io/v2
    kind: JWTPolicy
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: jwt-policy
      namespace: httpbin
    spec:
      applyToDestinations:
      - selector:
          labels:
            route: httpbin
      config:
        claims:
        - key: "email"
          values:
          - "*@solo.io"
        - key: "org"
          values:
          - "internal"
          - "partner"
        requiredScopes:
        - "is:developer"
        providers:
          provider1:
            issuer: https://localhost
            local:
              inline: |-
                -----BEGIN PUBLIC KEY-----
                MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnknfKiIDREaE/vxu8rtz
                oMaPop6rsiX7GANCRcqFks0j96Gb+UssKD8zJs2JBvEe4n0wNKVeLRbOctII+ZEO
                G8b+Dqig/1ubq3xiGbDBbZqHiFKjFQVUnII3Un9VRtDcJdgaaPGHnhlPs79sJNgQ
                e6AWJmfAasdT7i3MVEW7/dXcROiMRGapmxv+nQbKdoeiCJDULRdMSodhg/WJw2sH
                LLVxh4fPSF7cRxj36Y9FKWcGUH+YKe7n4gufAeEsHk+tPBndymYpmcMjb6W9HrJO
                39vvyMTjLAUyElCEfeMqCpFBCElhaGbF8ZncbV6vvDEkOxMX/m1TYhoJr1E2U8y/
                NwIDAQAB
                -----END PUBLIC KEY-----
            tokenSource:                    
              headers:  
              - name: X-Auth
                prefix: 'Bearer '
              queryParams:       
              - auth_token
    EOF
    
  2. Create a private-public key pair by using OpenSSL. You use this pair to create your own JWT token.

    1. Create a private key to sign the JWT.
      openssl genrsa 2048 > private-key.pem
      
    2. Extract the public key from the private key.
      openssl rsa -in private-key.pem -pubout
      
  3. Open the downloaded JWT policy.

    open jwt-policy_authz.yaml
    
  4. Run the following command to copy the public key value again.

    openssl rsa -in private-key.pem -pubout | pbcopy
    
  5. Paste the public key value in the inline section of the YAML configuration file. Make sure the indentation is kept.

  6. Create the JWT policy with your new public key value, which is used to verify the JWT token in subsequent requests.

    kubectl apply -f jwt-policy_authz.yaml
    
  7. Create a JWT that uses your private key. Refer to the following image to help understand the UI location of the steps. JWT token

    1. In your browser, go to https://jwt.io/. For the subsequent steps, you can refer to the following image.
    2. From the Algorithm dropdown, select RS256.
    3. From the Decoded section, scroll to the Payload: Data pane. Replace the content by pasting the following JWT payload information.
      {
        "iss": "https://localhost",
        "exp": 4804324736,
        "iat": 1648651136,
        "org": "internal",
        "email": "user2@solo.io",
        "scope": "is:developer"
      }
      
    4. From the Decoded section, scroll to the Verify Signature pane. In the first input box for -----BEGIN PUBLIC KEY-----, replace the content by pasting the value of your public key from the previous output of your terminal.
    5. In your terminal, copy the contents of your private key.
      cat private-key.pem | pbcopy
      
    6. Return to your browser and go to the Verify Signature pane in the Decoded section. In the second input box for -----BEGIN PRIVATE KEY-----, replace the content by pasting the value of your private key.
    7. Optional: Click Share JWT and paste the JWT values into a file to refer to later.
    8. From the Encoded section, copy the token value to your clipboard, such as eyJhbG.....
    9. Add the token value to an environment variable on your local machine.
      export TOKEN_DEV="Bearer <token_value>"
      
  8. Repeat the previous step to create another JWT that does not include the correct claim and scope data.

    1. For the Payload: Data section, use the following details. Note that the org, email, and scope details differ from the required claims in your JWT policy.
      {
        "iss": "https://localhost",
        "exp": 4804324736,
        "iat": 1648651136,
        "org": "external",
        "email": "user1@example.com",
        "scope": "is:user"
      }
      
    2. After filling out the rest of the details and getting the encoded token, set the token as a different environment variable.
      export TOKEN_USER="Bearer <token_value>"
      
  9. Try the request to the httpbin app without any authentication. Notice that your request is denied with a 401 error.

    Use the kubectl debug command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.

    kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -v http://httpbin.httpbin:8000/get
    

    If the output has an error about EphemeralContainers, see Ephemeral containers don’t work when testing Bookinfo.

    Create a temporary curl pod in the bookinfo namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.

    1. Create the curl pod.
      kubectl run -it -n httpbin --context $REMOTE_CONTEXT1 curl \
        --image=curlimages/curl:7.73.0 --rm  -- sh
      
    2. Send a request to the httpbin app.
      curl -v http://httpbin:8000/get
      
    Example output:

    HTTP/1.1 401 Unauthorized
    www-authenticate: Bearer realm="http://httpbin.httpbin:8000/get"
    ...
    Jwt is missing
    
  10. Try the request to the httpbin app again, this time with your developer token. Notice that your request is now accepted!

    Use the kubectl debug command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.

    kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -H "X-Auth: Bearer ${TOKEN_DEV}" -v http://httpbin.httpbin:8000/get
    

    Create a temporary curl pod in the bookinfo namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.

    1. Create the curl pod.
      kubectl run -it -n httpbin --context $REMOTE_CONTEXT1 curl \
        --image=curlimages/curl:7.73.0 --rm  -- sh
      
    2. Send a request to the httpbin app.
      curl -H "X-Auth: Bearer ${TOKEN_DEV}" -v http://httpbin:8000/get
      
    In the example output you get back a 200 response.

    HTTP/1.1 200 OK 
    ...
    
  11. Try the request to the httpbin app again, this time with your user token. Notice that your request is denied as expected, because this token does not have the required claim data.

    Use the kubectl debug command to create an ephemeral curl container in the deployment. This way, the curl container inherits any permissions from the app that you want to test. If you don't run Kubernetes 1.23 or later, you can deploy a separate curl pod or manually add the curl container as shown in the other tab.

    kubectl --context ${REMOTE_CONTEXT1} -n bookinfo debug -i pods/$(kubectl get pod --context ${REMOTE_CONTEXT1} -l app=reviews -A -o jsonpath='{.items[0].metadata.name}') --image=curlimages/curl -- curl -H "X-Auth: Bearer ${TOKEN_USER}" -v http://httpbin.httpbin:8000/get
    

    Create a temporary curl pod in the bookinfo namespace, so that you can test the app setup. You can also use this method in Kubernetes 1.23 or later, but an ephemeral container might be simpler, as shown in the other tab.

    1. Create the curl pod.
      kubectl run -it -n httpbin --context $REMOTE_CONTEXT1 curl \
        --image=curlimages/curl:7.73.0 --rm  -- sh
      
    2. Send a request to the httpbin app.
      curl -H "X-Auth: Bearer ${TOKEN_USER}" -v http://httpbin:8000/get
      

    In the example output you get back a 403 response.

    HTTP/1.1 403 Forbidden
    ...
    RBAC: access denied%
    
  12. Optional: Clean up the resources that you created.

    kubectl -n httpbin delete jwtpolicy jwt-policy