-
Notifications
You must be signed in to change notification settings - Fork 81
Cedarling Hello World [WIP]
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.
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
{
"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": {}
}
- Docker
- Python 3.10 or higher
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
-
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.
-
Once initialization is done, create a policy store named
gatewayDemo
. This will be your cedar namespace. -
Open the policy store by clicking the arrow next to it
-
Click on "Policies"
-
Click on "Add Policy" and then "Text Editor"
-
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
-
This will copy the URL for your policy store. You will need this for the sidecar setup
-
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
togatewayDemo::Workload
- Set
CEDARLING_ID_TOKEN_TRUST_MODE
to"none"
- Set
-
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.
- 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
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}
...