Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 37 additions & 64 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,92 +93,65 @@ jobs:
with:
name: pdp-image

# Load the PR-built PDP image into the runner's Docker daemon. The tester's
# Docker runtime launches it directly by name:tag (no pull — see LOCAL_IMAGE).
- name: Load Docker image
run: docker load -i pdp-image.tar

# Checkout the pdp-tester repository
# Checkout the pdp-tester repository (installed and run as a plain Python
# process — the Docker runtime backend needs no k3d/Helm/Tilt).
- name: Checkout pdp-tester repository
uses: actions/checkout@v4
with:
repository: "permitio/pdp-tester"
token: ${{ secrets.CLONE_REPO_TOKEN }}
path: './pdp-tester'

# Start k3d cluster for Kubernetes-based pdp-tester
- name: Start k3d cluster
uses: AbsaOSS/k3d-action@v2.4.0
- name: Set up Python
uses: actions/setup-python@v5
with:
cluster-name: pdp-tester
args: --k3s-arg "--disable=traefik@server:0"

# Import PDP image into k3d with 'next' tag (locally built)
- name: Import PDP image into k3d
run: k3d image import permitio/pdp-v2:next -c pdp-tester
python-version: "3.12"

# Build pdp-tester image and import into k3d
- name: Build and import pdp-tester image
- name: Install pdp-tester (docker extra)
working-directory: ./pdp-tester
run: |
docker build -t pdp-tester:ci .
k3d image import pdp-tester:ci -c pdp-tester

# Create namespace and secrets
- name: Create secrets
env:
PERMIT_TOKEN: ${{ secrets.PDP_TESTER_API_KEY }}
run: |
kubectl create namespace pdp-tester || true
kubectl create secret generic pdp-tester-credentials \
-n pdp-tester \
--from-literal=token="${PERMIT_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -

# Deploy pdp-tester via Helm with the "next" PDP image
- name: Deploy pdp-tester via Helm
run: pip install -e ".[docker]"

# Run the tester with the Docker runtime against the locally-built PDP image.
# LOCAL_IMAGE + LOCAL_TAGS make the runtime launch permitio/pdp-v2:next from
# the local daemon directly, skipping registry tag discovery. AUTO_REMOVE=false
# keeps crashed PDP containers around so the capture step below can read their
# logs. In run-once mode the tester does not exit non-zero on test failures,
# so the grep for "test cases failed" is the failure signal (mirrors the
# pdp-tester repo's own CI).
- name: Run PDP tester against the locally-built PDP image (Docker runtime)
working-directory: ./pdp-tester
env:
TOKEN: ${{ secrets.PDP_TESTER_API_KEY }}
API_URL: https://permitio.api.stg.permit.io
LOCAL_IMAGE: permitio/pdp-v2
LOCAL_TAGS: '["next"]'
AUTO_REMOVE: "false"
run: |
helm install pdp-tester ./deploy/helm/pdp-tester \
--set mode=job \
--set permit.existingSecret=pdp-tester-credentials \
--set permit.apiUrl=https://permitio.api.stg.permit.io \
--set image.repository=pdp-tester \
--set image.tag=ci \
--set image.pullPolicy=Never \
--set pdp.image=permitio/pdp-v2 \
--set 'pdp.localTags[0]=next' \
--set 'pdp.includeTags=' \
--set tests.skipGenerate=true \
--set tests.startTimeout=180 \
--set namespace.create=false \
--set logJson=false

- name: Wait for Job completion
run: |
kubectl wait --for=condition=complete job/pdp-tester \
-n pdp-tester --timeout=600s

- name: Check test results
run: |
LOGS=$(kubectl logs job/pdp-tester -n pdp-tester)
echo "$LOGS" | tail -30
if echo "$LOGS" | grep -q "test cases failed"; then
set -o pipefail
python -m pdp_tester --docker --skip-generate 2>&1 | tee /tmp/tester.log
if grep -q "test cases failed" /tmp/tester.log; then
echo "::error::Some test cases failed!"
exit 1
fi

- name: Print tester logs
- name: Capture PDP container state and logs
if: always()
run: |
echo "=== PDP Tester logs ==="
kubectl logs job/pdp-tester -n pdp-tester --tail=200 || true
echo "=== PDP containers (all states) ==="
docker ps -a --filter "label=pdp-tester.permit.io/managed-by=pdp-tester" \
--format '{{.Names}}\t{{.Status}}' || true
echo ""
echo "=== PDP Pod logs ==="
kubectl logs -l pdp-tester.permit.io/managed-by=pdp-tester \
-n pdp-tester --tail=50 || true

- name: Teardown k3d cluster
if: always()
run: k3d cluster delete pdp-tester || true
echo "=== PDP container logs ==="
for c in $(docker ps -aq --filter "label=pdp-tester.permit.io/managed-by=pdp-tester"); do
name=$(docker inspect --format '{{.Name}}' "$c" | sed 's#^/##')
echo "--- ${name} ---"
docker logs "$c" 2>&1 | tail -200 || true
done

docker-scout:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion horizon/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from horizon.startup.api_keys import get_env_api_key


def enforce_pdp_token(authorization: Annotated[str | None, Header()]):
def enforce_pdp_token(authorization: Annotated[str | None, Header()] = None):
if authorization is None:
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Missing Authorization header")
schema, token = authorization.split(" ")
Expand Down
40 changes: 24 additions & 16 deletions horizon/enforcer/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from pydantic import parse_obj_as
from starlette.responses import JSONResponse

from horizon.authentication import enforce_pdp_token
from horizon.config import sidecar_config
from horizon.enforcer.schemas import (
AllTenantsAuthorizationResult,
Expand Down Expand Up @@ -254,14 +253,13 @@ async def _is_allowed(query: BaseSchema, request: Request, policy_package: str):
return await post_to_opa(request, path, opa_input)


def init_enforcer_api_router(policy_store: BasePolicyStoreClient = None): # noqa: C901
policy_store = policy_store or DEFAULT_POLICY_STORE_GETTER()
def init_enforcer_health_router():
"""
The health check route lives on its own router, mounted without authentication:
k8s/LB liveness probes cannot attach the PDP token, so it must not be part of the
enforcer router, which requires the PDP token at include time.
"""
router = APIRouter()
if sidecar_config.KONG_INTEGRATION:
with Path(KONG_ROUTES_TABLE_FILE).open() as f:
kong_routes_table_raw = json.load(f)
kong_routes_table = [(re.compile(regex), resource) for regex, resource in kong_routes_table_raw]
logger.info(f"Kong integration: Loaded {len(kong_routes_table)} translation rules.")

@router.get("/health", status_code=status.HTTP_200_OK, include_in_schema=False)
async def health():
Expand All @@ -273,12 +271,23 @@ async def health():

return JSONResponse(status_code=status.HTTP_200_OK, content={"status": "ok"})

return router


def init_enforcer_api_router(policy_store: BasePolicyStoreClient = None): # noqa: C901
policy_store = policy_store or DEFAULT_POLICY_STORE_GETTER()
router = APIRouter()
if sidecar_config.KONG_INTEGRATION:
with Path(KONG_ROUTES_TABLE_FILE).open() as f:
kong_routes_table_raw = json.load(f)
kong_routes_table = [(re.compile(regex), resource) for regex, resource in kong_routes_table_raw]
logger.info(f"Kong integration: Loaded {len(kong_routes_table)} translation rules.")

@router.post(
"/authorized_users",
response_model=AuthorizedUsersResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token)],
)
async def authorized_users(request: Request, query: AuthorizedUsersAuthorizationQuery):
response = await _is_allowed(query, request, AUTHORIZED_USERS_POLICY_PACKAGE)
Expand All @@ -302,7 +311,7 @@ async def authorized_users(request: Request, query: AuthorizedUsersAuthorization
response_model=AuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def is_allowed_url(
request: Request,
Expand Down Expand Up @@ -363,7 +372,7 @@ async def is_allowed_url(
name="Get User Permissions",
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def user_permissions(
request: Request,
Expand All @@ -386,7 +395,7 @@ async def user_permissions(
name="Get User Tenants",
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def user_tenants(
request: Request,
Expand Down Expand Up @@ -415,7 +424,7 @@ async def user_tenants(
response_model=AllTenantsAuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def is_allowed_all_tenants(
request: Request,
Expand All @@ -439,7 +448,7 @@ async def is_allowed_all_tenants(
response_model=BulkAuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def is_allowed_bulk(
request: Request,
Expand All @@ -466,7 +475,7 @@ async def is_allowed_bulk(
response_model=AuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token), Depends(notify_seen_sdk)],
dependencies=[Depends(notify_seen_sdk)],
)
async def is_allowed(
request: Request,
Expand Down Expand Up @@ -503,7 +512,6 @@ async def is_allowed(
response_model=AuthorizationResult,
status_code=status.HTTP_200_OK,
response_model_exclude_none=True,
dependencies=[Depends(enforce_pdp_token)],
)
async def is_allowed_nginx(
request: Request,
Expand Down
6 changes: 5 additions & 1 deletion horizon/pdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from horizon.authentication import enforce_pdp_token
from horizon.config import MOCK_API_KEY, sidecar_config
from horizon.connectivity.api import init_connectivity_router
from horizon.enforcer.api import init_enforcer_api_router, stats_manager
from horizon.enforcer.api import init_enforcer_api_router, init_enforcer_health_router, stats_manager
from horizon.enforcer.opa.config_maker import (
get_opa_authz_policy_file_path,
get_opa_config_file_path,
Expand Down Expand Up @@ -384,15 +384,19 @@ def _configure_api_routes(self, app: FastAPI):
app.on_event("startup")(stats_manager.run)
app.on_event("shutdown")(stats_manager.stop_tasks)

enforcer_health_router = init_enforcer_health_router()
enforcer_router = init_enforcer_api_router(policy_store=self._opal.policy_store)
local_router = init_local_cache_api_router(policy_store=self._opal.policy_store)
# Init system router
system_router = init_system_api_router()

# include the api routes
# health stays public: k8s/LB liveness probes cannot attach the PDP token
app.include_router(enforcer_health_router)
app.include_router(
enforcer_router,
tags=["Authorization API"],
dependencies=[Depends(enforce_pdp_token)],
)

app.include_router(
Expand Down
Loading
Loading