Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jans-cedarling): implement environment variable loading for sidecar #10751

Merged
merged 11 commits into from
Jan 28, 2025
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
```
```
moabu marked this conversation as resolved.
Show resolved Hide resolved
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
Loading