Skip to content

Commit

Permalink
feat(jans-cedarling): implement environment variable loading for side…
Browse files Browse the repository at this point in the history
…car (#10751)

* feat(jans-cedarling): better sidecar logger

Signed-off-by: SafinWasi <[email protected]>

* chore: add bootstrap fix

Signed-off-by: SafinWasi <[email protected]>

* feat(jans-cedarling): update to use env variables

Signed-off-by: SafinWasi <[email protected]>

* feat(jans-cedarling): print out authz decision

Signed-off-by: SafinWasi <[email protected]>

* chore: remove prints

Signed-off-by: SafinWasi <[email protected]>

* feat(jans-cedarling): add dockerfile fixes

Signed-off-by: SafinWasi <[email protected]>

* docs(jans-cedarling): update readme

Signed-off-by: SafinWasi <[email protected]>

* chore: update sha

Signed-off-by: SafinWasi <[email protected]>

* style: move commands of one step in one code block

Signed-off-by: Mohammad Abudayyeh <[email protected]>

---------

Signed-off-by: SafinWasi <[email protected]>
Signed-off-by: Mohammad Abudayyeh <[email protected]>
Co-authored-by: Mohammad Abudayyeh <[email protected]>
  • Loading branch information
SafinWasi and moabu authored Jan 28, 2025
1 parent 2be259d commit 21d8ef5
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 92 deletions.
4 changes: 2 additions & 2 deletions jans-cedarling/flask-sidecar/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ RUN pip3 install "poetry==$POETRY_VERSION" gunicorn \
# ===============
# Project setup
# ===============
ENV JANS_SOURCE_VERSION=040ff17942019bc10433ce17d819b8d8474f13c8
ENV JANS_SOURCE_VERSION=81b061d8a2777afafe3ea9b36859561715da5839

COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
Expand Down Expand Up @@ -70,7 +70,7 @@ RUN poetry add /api/cedarling_python-$(cat /api/cedarling_version)-cp310-cp310-m

ENV FLASK_APP=main.core:app \
GUNICORN_LOG_LEVEL=${GUNICORN_LOG_LEVEL:-debug} \
CEDARLING_BOOTSTRAP_CONFIG_FILE=${CEDARLING_BOOTSTRAP_CONFIG_FILE:-/api/bootstrap.json} \
CEDARLING_BOOTSTRAP_CONFIG_FILE=${CEDARLING_BOOTSTRAP_CONFIG_FILE:-None} \
SIDECAR_DEBUG_RESPOSE=${SIDECAR_DEBUG_RESPONSE:-False}

EXPOSE 5000
Expand Down
94 changes: 76 additions & 18 deletions jans-cedarling/flask-sidecar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,11 @@ To run the API:
- Install [poetry](https://python-poetry.org/docs/#installation)
- Clone the [Janssen](https://github.com/JanssenProject/jans) repository:
```
git clone --filter blob:none --no-checkout https://github.com/JanssenProject/jans
```
```
cd jans
```
```
git sparse-checkout init --cone
```
```
git checkout main
```
```
git sparse-checkout set jans-cedarling
git clone --filter blob:none --no-checkout https://github.com/JanssenProject/jans \
&& cd jans \
&& git sparse-checkout init --cone \
&& git checkout main \
&& git sparse-checkout set jans-cedarling
```
- Navigate to `jans-cedarling/flask-sidecar`
- Run `poetry install` to install dependencies
Expand All @@ -47,14 +38,29 @@ CEDARLING_BOOTSTRAP_CONFIG_FILE=/path/to/bootstrap.json
SIDECAR_DEBUG_RESPONSE=False
```
Alternatively, you may add cedarling [bootstrap configuration](https://docs.jans.io/head/cedarling/cedarling-properties/) directly in your `.env` file.
```
APP_MODE=development
SIDECAR_DEBUG_RESPONSE=True
CEDARLING_APPLICATION_NAME=MyApp
CEDARLING_POLICY_STORE_ID=abcdef
CEDARLING_POLICY_STORE_URI=https://gluu.org
CEDARLING_USER_AUTHZ=disabled
CEDARLING_WORKLOAD_AUTHZ=enabled
CEDARLING_ID_TOKEN_TRUST_MODE=none
```
In this case, please be aware of case sensitivity. Environment variables are directly parsed as strings, hence `none` is not the same as `None`.
## Tests
Not yet implemented
# Docker Instructions
- Create a file called `bootstrap.json`. You may use this [sample](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/flask-sidecar/secrets/bootstrap.json) file.
- Modify the file to your specifications. Configuration values are described [here](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi).
- Modify the file to your specifications. Configuration values are described [here](https://docs.jans.io/head/cedarling/cedarling-properties/).
- Pull the docker image:
```
docker pull ghcr.io/janssenproject/jans/cedarling-flask-sidecar:0.0.0-nightly
Expand All @@ -70,23 +76,75 @@ Not yet implemented
-p 5000:5000\
ghcr.io/janssenproject/jans/cedarling-flask-sidecar:0.0.0-nightly
```
- Alternatively, you may provide environment variables directly via the `-e` flag:
```bash
docker run \
-e APP_MODE='development' \
-e SIDECAR_DEBUG_RESPONSE=True \
-e CEDARLING_APPLICATION_NAME=MyApp \
-e CEDARLING_POLICY_STORE_ID=abcdef \
-e CEDARLING_POLICY_STORE_URI=https://gluu.org \
-e CEDARLING_USER_AUTHZ=enabled \
-e CEDARLING_WORKLOAD_AUTHZ=enabled \
-e CEDARLING_ID_TOKEN_TRUST_MODE=none \
-p 5000:5000 \
ghcr.io/janssenproject/jans/cedarling-flask-sidecar:0.0.0-nightly
- The service is running on `http://0.0.0.0:5000`. OpenAPI documentation is available at `/swagger-ui`
## Docker Compose Instructions (for development)
- Clone the [Janssen](https://github.com/JanssenProject/jans) repository
- Clone the [Janssen](https://github.com/JanssenProject/jans) repository:
```
git clone --filter blob:none --no-checkout https://github.com/JanssenProject/jans
```
```
cd jans
```
```
git sparse-checkout init --cone
```
```
git checkout main
```
```
git sparse-checkout set jans-cedarling
```
- Navigate to `jans/jans-cedarling/flask-sidecar/`
- Modify the `secrets/bootstrap.json` file to your specifications. Configuration values are described [here](https://github.com/JanssenProject/jans/blob/main/jans-cedarling/bindings/cedarling_python/cedarling_python.pyi).
### Static bootstrap method
- Modify the `secrets/bootstrap.json` file to your specifications. Configuration values are described [here](https://docs.jans.io/head/cedarling/cedarling-properties/).
- The default configuration expects you to provide a URL to a policy store file via `CEDARLING_POLICY_STORE_URI`. If you want to use a local policy store via `CEDARLING_POLICY_STORE_FN`, you need to mount it inside the docker image. Place your policy store file in the `secrets` folder and edit the Dockerfile at line 46 to add this line:
```
...
COPY --chown=1000:1000 ./secrets/<policy store file>.json /api/
...
```
- Run `docker compose up`
- Run `docker compose -f docker-compose-file.yml up`
- The service is running on `http://0.0.0.0:5000`. OpenAPI documentation is available at `/swagger-ui`
### Environment variable method
- Set your environment variables. You may create an `.env` file and paste in the content for convenience:
```
APP_MODE=development
SIDECAR_DEBUG_RESPONSE=True
CEDARLING_APPLICATION_NAME=MyApp
CEDARLING_POLICY_STORE_ID=abcdef
CEDARLING_POLICY_STORE_URI=https://gluu.org
CEDARLING_USER_AUTHZ=disabled
CEDARLING_WORKLOAD_AUTHZ=enabled
CEDARLING_ID_TOKEN_TRUST_MODE=none
...
```
- Run `docker compose -f docker-compose-env.yml up`
- The service is running on `http://0.0.0.0:5000`. OpenAPI documentation is available at `/swagger-ui`
## OpenAPI
OpenAPI documentation is found in `flask-sidecar.yml`
20 changes: 20 additions & 0 deletions jans-cedarling/flask-sidecar/docker-compose-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
services:
sidecar:
image: flask-sidecar
build: ./
restart: always
ports:
- "5000:5000"
environment:
- FLASK_APP=main.core:app
- APP_MODE=${APP_MODE:-development}
- CEDARLING_BOOTSTRAP_CONFIG_FILE=None
- CEDARLING_APPLICATION_NAME=${CEDARLING_APPLICATION_NAME:-None}
- CEDARLING_POLICY_STORE_URI=${CEDARLING_POLICY_STORE_URI:-None}
- CEDARLING_POLICY_STORE_ID=${CEDARLING_POLICY_STORE_ID:-None}
- CEDARLING_USER_AUTHZ=${CEDARLING_USER_AUTHZ:-enabled}
- CEDARLING_WORKLOAD_AUTHZ=${CEDARLING_WORKLOAD_AUTHZ:-enabled}
- CEDARLING_USER_WORKLOAD_BOOLEAN_OPERATION=${CEDARLING_USER_WORKLOAD_BOOLEAN_OPERATION:-AND}
- CEDARLING_LOG_TYPE=${CEDARLING_LOG_TYPE:-memory}
- CEDARLING_LOG_LEVEL=${CEDARLING_LOG_LEVEL:-WARN}
- CEDARLING_ID_TOKEN_TRUST_MODE=${CEDARLING_ID_TOKEN_TRUST_MODE:-strict}
16 changes: 16 additions & 0 deletions jans-cedarling/flask-sidecar/docker-compose-file.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
sidecar:
image: flask-sidecar
build: ./
restart: always
ports:
- "5000:5000"
volumes:
- type: bind
source: ./secrets/bootstrap.json
target: /bootstrap.json
environment:
- FLASK_APP=main.core:app
- APP_MODE=${APP_MODE:-development}
- SIDECAR_DEBUG_RESPOSE=${SIDECAR_DEBUG_RESPOSE:-False}
- CEDARLING_BOOTSTRAP_CONFIG_FILE=/bootstrap.json
17 changes: 0 additions & 17 deletions jans-cedarling/flask-sidecar/docker-compose.yml

This file was deleted.

55 changes: 27 additions & 28 deletions jans-cedarling/flask-sidecar/main/base/cedarling/cedarling.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
)
from main.logger import logger
from flask import Flask
import json
import typing as _t

DictType = _t.Dict[str, _t.Any]
Expand All @@ -34,24 +33,25 @@
class CedarlingInstance:

def __init__(self, app=None):
self._bootstrap_config: str
self._bootstrap_config: str | None
self._cedarling: Cedarling
if app is not None:
self.init_app(app)

def init_app(self, app: Flask):
self._bootstrap_config = app.config.get(
"CEDARLING_BOOTSTRAP_CONFIG", "{}")
self.debug_response: bool = app.config.get(
"SIDECAR_DEBUG_RESPONSE", False)
self._bootstrap_config = app.config.get("CEDARLING_BOOTSTRAP_CONFIG", None)
self.debug_response: bool = app.config.get("SIDECAR_DEBUG_RESPONSE", False)
app.extensions = getattr(app, "extensions", {})
app.extensions["cedarling_client"] = self
self.initialize_cedarling()

def initialize_cedarling(self):
bootstrap_dict = json.loads(self._bootstrap_config)
bootstrap_instance = BootstrapConfig(bootstrap_dict)
self._cedarling = Cedarling(bootstrap_instance)
if self._bootstrap_config is None:
logger.info("Loading bootstrap from environment")
bootstrap_config = BootstrapConfig.from_env()
else:
bootstrap_config = BootstrapConfig.load_from_json(self._bootstrap_config)
self._cedarling = Cedarling(bootstrap_config)

def get_cedarling_instance(self) -> Cedarling:
return self._cedarling
Expand Down Expand Up @@ -146,24 +146,23 @@ def authorize(self,
person_value = person_result.decision.value
if workload_result is not None:
workload_value = workload_result.decision.value
person_diagnostic = self.generate_report(
person_result, "reason")
person_error = self.generate_report(person_result, "error")
person_reason = self.get_reason(person_result)
workload_diagnostic = self.generate_report(
workload_result, "reason")
workload_error = self.generate_report(workload_result, "error")
workload_reason = self.get_reason(workload_result)
result_dict["context"] = {
"reason_admin": {
"person evaluation": person_value,
"person diagnostics": person_diagnostic,
"person error": person_error,
"person reason": person_reason,
"workload evaluation": workload_value,
"workload diagnostics": workload_diagnostic,
"workload error": workload_error,
"workload reason": workload_reason
}
person_diagnostic = self.generate_report(person_result, "reason")
person_error = self.generate_report(person_result, "error")
person_reason = self.get_reason(person_result)
workload_diagnostic = self.generate_report(workload_result, "reason")
workload_error = self.generate_report(workload_result, "error")
workload_reason = self.get_reason(workload_result)
result_dict["context"] = {
"reason_admin": {
"person evaluation": person_value,
"person diagnostics": person_diagnostic,
"person error": person_error,
"person reason": person_reason,
"workload evaluation": workload_value,
"workload diagnostics": workload_diagnostic,
"workload error": workload_error,
"workload reason": workload_reason
}
}
logger.info(f"Cedarling evaluation result: {result_dict}")
return result_dict
21 changes: 7 additions & 14 deletions jans-cedarling/flask-sidecar/main/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@
from pathlib import Path
from main.logger import logger


def get_instance_path(parent_dir=""):
parent_dir = parent_dir or Path.home()
instance_path = Path(parent_dir).joinpath(".cloud")
instance_path.mkdir(parents=True, exist_ok=True)
return instance_path.resolve()


class BaseConfig:
API_TITLE = "Cedarling Sidecar"
API_VERSION = "v1"
Expand All @@ -37,42 +35,37 @@ class BaseConfig:
API_SPEC_OPTIONS = {
"x-internal-id": "1",
}
CEDARLING_BOOTSTRAP_CONFIG_FILE = os.getenv(
"CEDARLING_BOOTSTRAP_CONFIG_FILE", None)
if CEDARLING_BOOTSTRAP_CONFIG_FILE is None:
logger.warning("Cedarling bootstrap file not found")
exit()
with open(CEDARLING_BOOTSTRAP_CONFIG_FILE, "r") as f:
CEDARLING_BOOTSTRAP_CONFIG = f.read()
CEDARLING_BOOTSTRAP_CONFIG = None
CEDARLING_BOOTSTRAP_CONFIG_FILE = os.getenv("CEDARLING_BOOTSTRAP_CONFIG_FILE", "None")
if CEDARLING_BOOTSTRAP_CONFIG_FILE == "None":
logger.info("Cedarling bootstrap file not found, falling back to environment variables")
else:
with open(CEDARLING_BOOTSTRAP_CONFIG_FILE, "r") as f:
CEDARLING_BOOTSTRAP_CONFIG = f.read()
SIDECAR_DEBUG_RESPONSE = os.getenv("SIDECAR_DEBUG_RESPONSE", "False")
if SIDECAR_DEBUG_RESPONSE == "True":
SIDECAR_DEBUG_RESPONSE = True
else:
SIDECAR_DEBUG_RESPONSE = False


class TestingConfig(BaseConfig):
TESTING = True
DEBUG = True


class DevelopmentConfig(BaseConfig):
DEVELOPMENT = True
DEBUG = True


class ProductionConfig(BaseConfig):
DEVELOPMENT = False


config = {
"testing": TestingConfig,
"default": TestingConfig,
"development": DevelopmentConfig,
"production": ProductionConfig
}


class ConfigLoader:

@staticmethod
Expand Down
23 changes: 11 additions & 12 deletions jans-cedarling/flask-sidecar/main/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,24 @@
"""

from datetime import datetime
import logging
import orjson

from structlog import configure, stdlib, processors, get_logger
from structlog import configure, get_logger, stdlib, processors, BytesLoggerFactory, make_filtering_bound_logger, \
contextvars
from pythonjsonlogger import jsonlogger

configure(
cache_logger_on_first_use=True,
wrapper_class=make_filtering_bound_logger(logging.INFO),
processors=[
stdlib.filter_by_level,
stdlib.add_logger_name,
stdlib.add_log_level,
stdlib.PositionalArgumentsFormatter(),
processors.TimeStamper(fmt='iso'),
processors.StackInfoRenderer(),
contextvars.merge_contextvars,
processors.add_log_level,
processors.format_exc_info,
processors.JSONRenderer()
processors.TimeStamper(fmt="iso", utc=True),
processors.JSONRenderer(serializer=orjson.dumps),
],
context_class=dict,
logger_factory=stdlib.LoggerFactory(),
wrapper_class=stdlib.BoundLogger,
cache_logger_on_first_use=True,
logger_factory=BytesLoggerFactory(),
)

logger:stdlib.BoundLogger = get_logger()
Expand Down
Loading

0 comments on commit 21d8ef5

Please sign in to comment.