Skip to content

Cedarling Hello World [WIP]

Safin Wasi edited this page Feb 7, 2025 · 28 revisions

Cedarling Hello World

This is a simple application demonstrating the use case of an API gateway, where the gateway calls the Flask sidecar to check if access to an endpoint is allowed or not. The gateway will only allow access if the sidecar returns a "True" decision.

Sequence diagram

View Here

title API Gateway
bottomparticipants 
actor User
participant Gateway
participant Sidecar
User->Gateway: GET /protected\nAuthorization: Bearer ...
Gateway->Sidecar: POST /cedarling/evaluation\n{"subject": {"type": "JWT",\n"id": "cedarling", "properties":\n{"access_token": "..."}}}
Sidecar->Sidecar: cedarling.authorize(access_token)
alt ALLOW
Sidecar->Gateway: {"decision": true}
Gateway->User:200 {"result": "protected resource"}
else DENY
Sidecar->Gateway: {"decision": false,\ncontext: {...}}
Gateway->User: 403 Forbidden
end

Sample Authzen request

{
	"subject": {
		"type": "JWT",
		"id": "cedarling",
		"properties": {
			"access_token": ""
		}
	},
	"resource": {
		"type": "Jans::HTTP_Request",
		"id": "some_id",
		"properties": {
			"header": {},
			"url": {
				"host": "protected.host",
				"path": "/protected",
				"protocol": "http"
			}
		}
	},
	"action": {
		"name": "Jans::Action::\"GET\""
	},
	"context": {}
}

Prerequisites

  • Docker
  • Python 3.10 or higher

1. Author Policies

To begin, we will need to set up a policy store that can be downloaded and loaded by the sidecar. This process is simplified by using Agama Lab.

  • Navigate to Agama Lab, Sign in with Github, and click on Policy Designer

    image

  • Select a repository you want to use to store cedarling policies and schemas. The repository must have at least one commit on the default branch.

    image

  • Once initialization is done, create a policy store named gatewayDemo. This will be your cedar namespace.

    image

  • Open the policy store by clicking the arrow next to it

  • Click on "Policies"

  • Click on "Add Policy" and then "Text Editor"

    image

  • Paste the following cedar policy code:

    @id("allow_one")
    permit(
      principal is gatewayDemo::Workload,
      action == gatewayDemo::Action::"GET",
      resource is gatewayDemo::HTTP_Request
    )
    when {
      ((principal["access_token"])["scope"]).contains("profile")
    };
    
  • Click Save. Agama Lab will validate your policy syntax and entities.

  • On the top navigation pane, click on "Trusted Issuers"

  • Click "Add Issuer"

  • On the next page, add in the following details:

    • Name: Gluu
    • Description: Gluu
    • OpenID Configuration Endpoint: https://account.gluu.org/.well-known/openid-configuration
    • Click Save
  • Near the top, click on Copy Link image

  • This will copy the URL for your policy store. You will need this for the sidecar setup

2. Deploy Cedarling Sidecar

Docker instructions

  • Create a file named bootstrap.json. You may use the sample file.

    • Set CEDARLING_POLICY_STORE_URI to the URL you copied from Agama Lab.
    • Set CEDARLING_USER_AUTHZ to "disabled"
    • Set CEDARLING_TOKEN_CONFIGS to the following value:
    {
      "access_token": {
        "entity_type_name": "gatewayDemo::Access_token",
        "iss": "disabled",
        "aud": "disabled",
        "sub": "disabled",
        "nbf": "disabled",
        "exp": "disabled",
        "jti": "disabled"
       },
      "id_token": {
        "entity_type_name": "gatewayDemo::id_token",
        "iss": "disabled",
        "aud": "disabled",
        "sub": "disabled",
        "nbf": "disabled",
        "exp": "disabled",
        "jti": "disabled"
      },
      "userinfo_token": {
        "entity_type_name": "gatewayDemo::Userinfo_token",
        "iss": "disabled",
        "aud": "disabled",
        "sub": "disabled",
        "nbf": "disabled",
        "exp": "disabled",
        "jti": "disabled"
      }
    }
    • Set CEDARLING_MAPPING_WORKLOAD to gatewayDemo::Workload
    • Set CEDARLING_ID_TOKEN_TRUST_MODE to "none"
  • Pull the docker image:

    docker pull ghcr.io/janssenproject/jans/cedarling-flask-sidecar:0.0.0-nightly
    
  • Run the docker image, replacing </absolute/path/to/bootstrap.json> with the absolute path to your bootstrap file:

    docker run -e APP_MODE='development' -e CEDARLING_BOOTSTRAP_CONFIG_FILE=/bootstrap.json -e SIDECAR_DEBUG_RESPONSE=True --mount type=bind,src=</absolute/path/to/bootstrap.json>,dst=/bootstrap.json -p 5000:5000 -d ghcr.io/janssenproject/jans/cedarling-flask-sidecar:0.0.0-nightly
    
  • The sidecar is now running on http://127.0.0.1:5000. Keep track of the output of the previous command, it is your docker container ID.

Python instructions (if docker is unavailable)

  • Ensure python3.10 or greater is installed
  • Install pipx and poetry
  • Clone the Janssen repository
  • Navigate to jans/jans-cedarling/flask-sidecar
  • Run poetry install to install the dependencies
  • Download the latest cedarling nightly wheel:
    wget https://github.com/JanssenProject/jans/releases/download/nightly/cedarling_python-0.0.0-cp310-cp310-manylinux_2_31_x86_64.whl
    
  • Install the nightly wheel: poetry run pip install cedarling_python-0.0.0-cp310-cp310-manylinux_2_31_x86_64.whl
  • Modify secrets/bootstrap.json to your specifications.
  • Navigate to jans/jans-cedarling/flask-sidecar/main
  • Create a file called .env and paste in the following content. Alternatively, set the following environment variables:
    APP_MODE=development
    CEDARLING_BOOTSTRAP_CONFIG_FILE=../secrets/bootstrap.json
    SIDECAR_DEBUG_RESPONSE=False
    
  • Run the sidecar: poetry run flask run
  • The sidecar is now running on http://127.0.0.1:5000

3. Setup Test Gateway

For this example we will be using Flask. For ease of use we recommend setting up a virtual environment.

  • Create a folder named demo and navigate to it
  • Create a virtual environment: python -m venv venv and activate it: source venv/bin/activate
  • Install flask and requests: pip install flask requests
  • Create a file called gateway.py and paste in the following content:
from flask import Flask, abort, request
import requests

app = Flask(__name__)

@app.route("/protected")
def protected():
	token = request.headers.get("Authorization")
	if token is None:
		abort(403)
	token_jwt = token.split(" ")[1]
	payload = {
		"subject": {
			"type": "JWT",
			"id": "cedarling",
			"properties": {
				"access_token": token_jwt
			}
		},
		"resource": {
			"type": "gatewayDemo::HTTP_Request",
			"id": "some_id",
			"properties": {
				"header": {},
				"url": {
					"host": "protected.host",
					"path": "/protected",
					"protocol": "http"
				}
			}
		},
		"action": {
			"name": "gatewayDemo::Action::\"GET\""
		},
		"context": {}
	}
	response = requests.post("http://127.0.0.1:5000/cedarling/evaluation", json=payload)
	if response.ok and response.json()["decision"] == True:
		return {"protected_content": "secret"}
	abort(403)
  • Run the gateway: flask --app gateway run --port 5001
  • Access the protected endpoint on your browser: http://127.0.0.1:5001/protected. You should get a 403
  • Access the protected endpoint via curl with the provided JWT:
curl http://127.0.0.1:5001/protected -H "Authorization: Bearer eyJraWQiOiJjb25uZWN0X2Y5YTAwN2EyLTZkMGItNDkyYS05MGNkLWYwYzliMWMyYjVkYl9zaWdfcnMyNTYiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJxenhuMVNjcmI5bFd0R3hWZWRNQ2t5LVFsX0lMc3BaYVFBNmZ5dVlrdHcwIiwiY29kZSI6IjNlMmEyMDEyLTA5OWMtNDY0Zi04OTBiLTQ0ODE2MGMyYWIyNSIsImlzcyI6Imh0dHBzOi8vYWNjb3VudC5nbHV1Lm9yZyIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJjbGllbnRfaWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhdWQiOiJkN2Y3MWJlYS1jMzhkLTRjYWYtYTFiYS1lNDNjNzRhMTFhNjIiLCJhY3IiOiJzaW1wbGVfcGFzc3dvcmRfYXV0aCIsIng1dCNTMjU2IjoiIiwibmJmIjoxNzMxOTUzMDMwLCJzY29wZSI6WyJyb2xlIiwib3BlbmlkIiwicHJvZmlsZSIsImVtYWlsIl0sImF1dGhfdGltZSI6MTczMTk1MzAyNywiZXhwIjoxNzMyMTIxNDYwLCJpYXQiOjE3MzE5NTMwMzAsImp0aSI6InVaVWgxaERVUW82UEZrQlBud3BHemciLCJ1c2VybmFtZSI6IkRlZmF1bHQgQWRtaW4gVXNlciIsInN0YXR1cyI6eyJzdGF0dXNfbGlzdCI6eyJpZHgiOjMwNiwidXJpIjoiaHR0cHM6Ly9qYW5zLnRlc3QvamFucy1hdXRoL3Jlc3R2MS9zdGF0dXNfbGlzdCJ9fX0.Pt-Y7F-hfde_WP7ZYwyvvSS11rKYQWGZXTzjH_aJKC5VPxzOjAXqI3Igr6gJLsP1aOd9WJvOPchflZYArctopXMWClbX_TxpmADqyCMsz78r4P450TaMKj-WKEa9cL5KtgnFa0fmhZ1ZWolkDTQ_M00Xr4EIvv4zf-92Wu5fOrdjmsIGFot0jt-12WxQlJFfs5qVZ9P-cDjxvQSrO1wbyKfHQ_txkl1GDATXsw5SIpC5wct92vjAVm5CJNuv_PE8dHAY-KfPTxOuDYBuWI5uA2Yjd1WUFyicbJgcmYzUSVt03xZ0kQX9dxKExwU2YnpDorfwebaAPO7G114Bkw208g"

This should return a successful response:

{"protected_content":"secret"}

The cedarling decision log will be outputted by the docker container or directly by the API to stdout. In case of docker, this can be retrieved like so:

$ docker logs <container ID>
...
{"request_id":"0194cdbc-b8c7-798d-8cc8-fb483448e6fa","timestamp":"2025-02-03T21:34:44.935Z","log_kind":"Decision","pdp_id":"e122cc37-14f7-4033-a547-6791f339218b","policystore_id":"c92b24bb010b772e7702811ae0725986bf7d3b39656e","policystore_version":"undefined","principal":"Workload","Workload":{},"diagnostics":{"reason":[],"errors":[]},"action":"gatewayDemo::Action::\"GET\"","resource":"gatewayDemo::HTTP_Request::\"some_id\"","decision":"ALLOW","tokens":{"access_token":{"jti":"uZUh1hDUQo6PFkBPnwpGzg"}},"decision_time_ms":0}
...
Clone this wiki locally