Open Policy Agent

Open Policy Agent, "OPA" for short, is a lightweight and responsive framework that was developed to de-couple policy from your services' code, across your service mesh. The capabilities of OPA are far-reaching and we will run you through some of the most helpful functionalities that it offers.

Editing Policies Already Set in Place (OPA's Policy API)

Once OPA is added into your service mesh and policies are set in place, you are able to edit and delete these policies on the fly with OPA's REST API. It's as simple as creating or modifying a secret in your service mesh and shelling into the pod that contains your policy.

If you want to add a policy to a service pod, remove the current secret opa-policy you have. This can be done with:

kubectl delete secret opa-policy

This will allow our new policy to be available in the mesh. Now that we're in the same directory as our new policy, we can make that our new opa-policy secret with:

kubectl create secret generic opa-policy --from-file your-policy.rego

Now, we must shell into the Grey Matter sidecar of the pod we are working on. We will use this kubectl command to get a shell of the sidecar:

kubectl exec -it <pod-name> -c sidecar sh

It is at this point where we can send policy edits through OPA's REST API. There are three HTTP methods that OPA's REST API has dedicated to editing policies:

  • PUT - Upload a new policy

  • PATCH - Edit an existing policy

  • DELETE - Delete an existing policy

Here is an example of how to upload a new policy through the sidecar using curl and the Rego policy file that we made into a secret:

curl -X PUT --data-binary @your-policy.rego \
    localhost:8181/v1/policies/

Note Using this same curl syntax but with a different method i.e. PATCH, DELETE, you can also update and delete policies.

API Protection

Protecting your API and establishing authorized users is as simple as running OPA in your API pod and using the following methods to determine who gets what permissions.

JWT Verification / Signing

The Rego library includes built in functions for JSON Web Token verification and signing. This gives the ability for OPA to be able to authenticate JWTs, interpret the payload information, and sign JWTs with your certificate.

Verification

For instance, take this example HTTP request and Rego policy from the OPA Docs

"request": {
    "http": {
    "headers": {
        ":authority": "example-app",
        ":method": "POST",
        ":path": "/pets/dogs",
        "accept": "*/*",
        "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQWxpY2lhIFNtaXRoc29uaWFuIiwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInVzZXJuYW1lIjoiYWxpY2UifQ.md2KPJFH9OgBq-N0RonGdf5doGYRO_1miN8ugTSeTYc",
        "content-length": "0",
        "user-agent": "curl/7.68.0-DEV",
        "x-ext-auth-allow": "yes",
        "x-forwarded-proto": "http",
        "x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a"
package envoy.http.jwt   # Envoy JWT Package

default allow = false

allow {
    claims.username == "alice"  # Information stripped from ensured token
}

claims := payload {
    io.jwt.verify_hs256(bearer_token, "B41BD5F462719C6D6118E673A2389")  # Ensure token authorization with certificate

    [_, payload, _] := io.jwt.decode(bearer_token)  # Return token information
}

bearer_token := t {    # Sanitize token string
    v := input.attributes.request.http.headers.authorization   # Take raw string
    startswith(v, "Bearer ")  # Get start
    t := substring(v, count("Bearer "), -1)  # Retrieve full JWT Token
}

There are several built-in JWT functions that Rego has in its arsenal, in order to work with JSON Web Tokens. The first step being to split the request strings into the raw token, then comparing it to a certificate, and finally extracting a payload, if necessary. As per the example, one can also utilize this to determine Role Based Access.

Note More information on Rego's built in Token Verification.

Signing

Rego also supports JWT Signing in two methods:

  • Raw: io.jwt.encode_sign_raw(headers, payload, key)

  • With a Symmetric / RSA key: io.jwt.encode_sign(headers, payload, key)

Here are two examples of how these functions would look (from https://openpolicyagent.org):

Signing with a Symmetric Key (HMAC with SHA-256)

io.jwt.encode_sign({
    "typ": "JWT",
    "alg": "HS256"
}, {
    "iss": "joe",
    "exp": 1300819380,
    "aud": ["bob", "saul"],
    "http://example.com/is_root": true,
    "privateParams": {
        "private_one": "one",
        "private_two": "two"
    }
}, {
    "kty": "oct",
    "k": "AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"
})

Signing Raw Token

io.jwt.encode_sign_raw(
    `{"typ":"JWT","alg":"HS256"}`,
     `{"iss":"joe","exp":1300819380,"http://example.com/is_root":true}`,
    `{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}`
)

Note More information on Rego's built in Token Signing.

Access Control Lists (OPA's Data API)

OPA offers even more control over your security and permissions system. Using OPA's Data API, you can hardcode in Access Control Lists that determine who is authorized to use your API and what permissions they have. These ACL's take the form of json documents that map out users and their individual permissions. Custom Rego policies can extract information from your ACL to help resolve decisions. Here is what one ACL might look like:

{
  "john": [
    "read"
  ],
  "bob": [
    "read",
    "write"
  ]
}

We would save this as something along the lines of api-permissions-acl.json. We would go about uploading these data documents the same way we edit policies: first, upload the file as a secret to the mesh, and then use the same PUT/PATCH/DELETE request style in the sidecar. However, the curl syntax is slightly different. We would point the curl to a different endpoint, we would use /v1/data/ instead. Uploading a new ACL data document would look as such:

curl -X PUT --data-binary @api-permissions-acl.json \
    localhost:8181/v1/data/

And to invoke this data in our policy, we would use this include statement:

import data.api.permissions.acl

Note More information on OPA's Data API.

Last updated

Was this helpful?