Helpful Badger in the wild

Helpful Badger

Imagining the possibilities

JWS Token Validation with OPA

Learn how to validate JWS tokens with Open Policy Agent

Helpful Badger

9 minute read

Photo by SkillScouter on Unsplash

Getting Started with Envoy & Open Policy Agent — 05 —

JWS Token Validation with OPA

This is the 5th Envoy & Open Policy Agent Getting Started Guide. Each guide is intended to explore a single feature and walk through a simple implementation. Each guide builds on the concepts explored in the previous guide with the end goal of building a very powerful authorization service by the end of the series.

The source code for this getting started example is located on Github. ——> Envoy & OPA GS # 5

Here is a list of the Getting Started Guides that are currently available.

Getting Started Guides

  1. Using Envoy as a Front Proxy
  2. Adding Observability Tools
  3. Plugging Open Policy Agent into Envoy
  4. Using the Open Policy Agent CLI
  5. JWS Token Validation with OPA
  6. JWS Token Validation with Envoy
  7. Putting It All Together with Composite Authorization
  8. Configuring Envoy Logs Taps and Traces
  9. Sign / Verify HTTP Requests

Introduction

This getting started guide will validate 3 different identity tokens that are expressed as Signed JSON web tokens (JWS) for short. This guide assumes knowledge of several IETF RFCs:

  • The format for a JWS and standard claims included in it is specified by the IETF in RFC 7515
  • The signing algorithms for JSON Web tokens is specified by the IETF in RFC 7518
  • Encryption keys used for signing and validation are specified by the IETF in RFC 7517

There is another tool that was used to create this getting started guide, jose-util:

  • JSON Web Token Signing and Encryption is commonly abbreviated as JOSE
  • A command line utility called jose-util is available as part of a Golang JOSE library from Square
  • To install it execute:
    • go get -u github.com/square/go-jose/jose-util
    • go install github.com/square/go-jose/jose-util
    • and make sure your go install directory is in your path

Use of this tool is optional since the tool has alredy been used and the results are already stored in the project.

Simulated Scenario

The scenario being simulated is a customer calling into a company to inquire about an order that they have placed. Various JWS tokens are used as a tamper resistent mechanism to communicate information about these participants through the API request chain.

The JSON Web Tokens

The keys sub directory contains encryption keys that have been pre-created for you. If you would like to create new keys and have jose-util installed then run the script ./generate_keys.sh

The identities sub directory contains simulated identity tokens as JSON files. The script

./create_jws_tokens.sh
can be used to sign and verify these tokens.

Proof of Authentication for an External Customer User

A customer has called in to a company to enquire about an order that they have placed. The customer was authenticated by going through the Voice Response Unit (VRU) and evidence of that authentication is captured as a JWS token and is placed in the subject-token header. The customer identity provider performed the authentication. This token saves repeated database lookups and is used to authorize what the customer can do and has access to.

  • The iss field contains the customer identity provider as the issuer.
  • The profileRefID is a customer claim that can be used to get the customer’s preferences.
  • The customerRefID is a custom claim that can be used to get details about the customer.
  • The consentID is a custom claim that can be used to get details about what the customer has consented to and opted out from.
  • The remaining claims are standard claims that contain information about the token issuance, use and authentication methods performed.

The full token is below:

 1{
 2    "iss": "customerIdentity.example.com",
 3    "sub": "customerIdentity:DBm4FBclSD261G2",
 4    "profileRefID": "34983598sfkh9w8798798d",
 5    "customerRefID": "DBm4FBclSD261G2",
 6    "consentID": "4378afd4f",
 7    "aud": [ "apigateway.example.com", "protected-stuff.example.com"],
 8    "azp": "app_123456",
 9    "exp": 2735689600,
10    "iat": 1597676718,
11    "auth_time": 1597676718,
12    "jti": "o8T2hxraeFzIQ6p",
13    "nonce": "n-0S6_WzA2Mj",
14    "acr": "urn:mace:incommon:iap:silver",
15    "amr": [
16      "face",
17      "fpt",
18      "geo",
19      "mfa"
20    ],
21    "vot": "P1.Cc.Ac",
22    "vtm": "https://example.org/vot-trust-framework"
23}

Proof of Authentication for a Workforce User

The call center agent was authenticated by the application she / he is using. Evidence of that authentication is captured as a JWS token is placed in the actor-token header. The workforce identity provider performed the authentication. This token saves repeated database lookups and is used to authorize what an agent can do on their own behalf as well as what an agent can do on the customer’s behalf.

  • The iss field contains the workforce identity provider as the issuer.
  • The remaining claims are standard claims that contain information about the token issuance, and use.

The full token is below:

 1{
 2    "iss": "workforceIdentity.example.com",
 3    "sub": "workforceIdentity:406319",
 4    "aud": [ "apigateway.example.com", "protected-stuff.example.com"],
 5    "azp": "app_123456",
 6    "exp": 2735689600,
 7    "iat": 1597676718,
 8    "auth_time": 1597676718,
 9    "jti": "mPPdwJ7Jrr2MQzS"
10}

Proof of Authentication for an Application

The application that the call center agent is using was authenticated by the API gateway. Proof of authentication and application details are placed in the app-token header. This stateless token saves repeated database lookups and is used for application level authorizations.

  • The iss field contains the API gateway as the issuer.
  • The client_id The remaining claims are standard claims that contain information about the token issuance, and use.

The full token is below:

 1{
 2    "iss": "apigateway.example.com",
 3    "sub": "apigateway:app_123456",
 4    "aud": [ "apigateway.example.com", "protected-stuff.example.com"],
 5    "azp": "app_123456",
 6    "client_id": "app_123456",
 7    "exp": 2735689600,
 8    "iat": 1597676718,
 9    "auth_time": 1597676718,
10    "jti": "XcOpQe2vqTq1kny",
11    "grant": "client_credentials",
12    "owningLegalEntity": "Example Co.",
13    "scopes": [
14      "rewards:read",
15      "rewards:redeem"
16    ],
17    "kid": "APIGW-ES256"
18}

Open Policy Agent Examples

In the policy examples directory, there are 2 different REGO policies:

  • jws_examples_v1.rego - which shows three diffent commands to process a JWS
  • policy.rego - which shows our final policy

jws_examples_v1.rego

1verify_example = { "isValid": isValid} {
2    isValid := io.jwt.verify_es256(es256_token, jwks)
3}

The io.jwt.verify_xxx function simply checks to see if the signature on the token is valid. It returns a boolean indicating signature validity. It does not check the token claims. The downside of this function is that you must already know the algorithm used and then call the correct validation function.

1decode_example = {"header": header, "payload": payload, "signature": signature} {
2    [header, payload, signature] := io.jwt.decode(es256_token)
3}

The io.jwt.decode() function does not validate the JWS. It base64 decodes the token and returns it’s constituent parts (header, payload and signature).

1decode_verify_example = { "isValid": isValid, "header": header, "payload": payload } {
2     [isValid, header, payload] := io.jwt.decode_verify(es256_token, { "cert": jwks, "iss": "xxx", })
3}

The io.jwt.decode_verify() function detects the correct algorithm from the JWS headers, validates the signature and validates the claims in the token. The second parameter for the decode and verify function is an object that contains the validation parameters. The cert property must be a string. A JSON web token keyset must be serialized to a string if you have it already read into memory as a parsed object. The other properties in the object are the expected token values. If an audience claim is specified in the token, then an audience claim must be specified in the parameter list. The audience claim is also assumed to be an array of strings. Audience claim validation checks for the presence of the supplied audience string anywhere in the audience claim array in the JWS.

The REGO policy in this file decodes the token using all 3 functions above and assigns them to the result.

1result = {
2    "verify_example": verify_example,
3    "decode_example": decode_example,
4    "decode_verify_example": decode_verify_example
5}

The output below shows the result of executing the policy.

 1{
 2  "result": [
 3    {
 4      "expressions": [
 5        {
 6          "value": {
 7            "decode_example": {
 8              "header": { "alg": "ES256",  "typ": "JWT" },
 9              "payload": { "iss": "xxx", "nbf": 1444478400 },
10              "signature": "940adccdf37ea482fca1453eecf53cdeefb37d7a2e8170598fa76b15e285b0f128561cbd580ca266546c858aa34d25dd6b0f32c362fea2fb78cd35196f63d632"
11            },
12            "decode_verify_example": {
13              "header": { "alg": "ES256",  "typ": "JWT" },
14              "isValid": true,
15              "payload": { "iss": "xxx", "nbf": 1444478400 }
16            },
17            "verify_example": {
18              "isValid": true
19            }
20          },
21          "text": "data.jws.examples.v1.result",
22          "location": {
23            "row": 1,
24            "col": 1
25          }
26        }
27      ]
28    }
29  ]
30}

The policy_examples directory contains several scripts and rego policies that demonstrates 5 different ways to invoke our policy:

  1. policy_examples/run_rego_tests_locally.sh - This script runs the test policies with a locally installed Open Policy Agent.
    As you can see from this example, the output looks much like outpu from test runners in most other languages.
  2. policy_examples/run_rego_tests_in_docker.sh - This script runs the test policies using the Open Policy Agent docker container and a locally mapped volume
    The nice part about using this approach is that it doesn’t require anything installed locally other than docker.
  3. policy_examples/run_opa_cli.sh - This script uses eval statements to test policies using the Open Policy Agent docker container. It shows the results of using all 3 JWS functions io.jwt.verify_es256(), io.jwt.decode() and io.jwt.decode_verify() REGO commands in jws_examples_v1.rego. Additionally it shows using eval statements with our final policy.rego. You should see an output similar to this for each request.
    As you can see from this example and the REST API example that follows, the output from the eval statement approach and the REST API approach are a bit different. The eval statement technique returns a result object and an array of expression evalution objects. Within each of those objects is a value object that finally contains our results.
  4. policy_examples/run_opa_rest_decision_api_tests.sh - uses Curl commands and OPA’s REST API running in docker.
    The REST API has both a decision ID and the results in a results object. The decision ID can be used to with Open Policy Agent’s decision logs to find out more information about how the result was arrived at.
  5. demonstrate_opa_jws_validation.sh - This script uses the final solution that shows our fully integrated solution with Envoy. This script uses Curl to make requests to our target service. Envoy asks OPA for an authorization decision prior to forwarding to HTTPBIN.
    In the final result you can see the details of the curl request going into Envoy and the decision is implied from being able to see the response from the requested API or a 403 Forbidden response is given.

Conclusion

The difficult part of getting this solution to work is tinkering required to get the Envoy configuration correct and understanding the idiosyncrasies of each of the Open Policy agent JWS functions. The purpose of this article is to shortcut that process for you by giving you a known good starting point with a working configuration and rego policy. Once you have a functioning example, it is easy to adapt the example to whatever your local use cases might be.

In the next getting started guide, we are going to show how you can also validate JWS tokens in Envoy without using OPA at. This gives you a choice to make about where you would like that authorization decision to be made.

Recent posts

Categories

About

Sharing perspectives.