Skip to content
Merged
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
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# AgentIdentity Okta compatibility checks (examples/tests)

# Core Okta app settings used by OBO compatibility demo/test.
OKTA_ISSUER=https://<your-okta-domain>/oauth2/default
OKTA_CLIENT_ID=<your-okta-client-id>
OKTA_CLIENT_SECRET=<your-okta-client-secret>
OKTA_AUDIENCE=api://predicate-authority
OKTA_SCOPE=authority:check

# Enable live compatibility check test (disabled by default).
OKTA_OBO_COMPAT_CHECK_ENABLED=0

# Set to 1/true only if your Okta tenant supports token exchange/OBO.
OKTA_SUPPORTS_TOKEN_EXCHANGE=0
20 changes: 17 additions & 3 deletions .github/workflows/phase1-ci-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
push:
branches: ["main", "phase1"]
tags: ["v*"]
workflow_dispatch:
inputs:
publish:
Expand All @@ -27,7 +28,7 @@ jobs:

- name: Install dependencies
run: |
python -m pip install --upgrade pip pre-commit pytest
python -m pip install --upgrade pip pre-commit pytest bandit
python -m pip install -e predicate_contracts -e predicate_authority

- name: Verify package release order
Expand All @@ -36,13 +37,18 @@ jobs:
- name: Run tests
run: python -m pytest -q

- name: Run auth module security checks
run: |
python -m bandit -q -r predicate_authority/bridge.py predicate_authority/daemon.py predicate_authority/control_plane.py
python scripts/check_no_plaintext_okta_secrets.py

- name: Run pre-commit checks
run: pre-commit run --all-files

publish-predicate-contracts:
runs-on: ubuntu-latest
needs: [quality]
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'true'
if: (github.event_name == 'workflow_dispatch' && inputs.publish == 'true') || startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -58,6 +64,10 @@ jobs:
- name: Verify release order
run: python scripts/verify_release_order.py

- name: Validate release tag version
if: startsWith(github.ref, 'refs/tags/v')
run: python scripts/validate_release_tag.py --tag "${GITHUB_REF_NAME}"

- name: Build predicate-contracts
run: python -m build predicate_contracts

Expand All @@ -73,7 +83,7 @@ jobs:
publish-predicate-authority:
runs-on: ubuntu-latest
needs: [publish-predicate-contracts]
if: github.event_name == 'workflow_dispatch' && inputs.publish == 'true'
if: (github.event_name == 'workflow_dispatch' && inputs.publish == 'true') || startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -89,6 +99,10 @@ jobs:
- name: Verify release order
run: python scripts/verify_release_order.py

- name: Validate release tag version
if: startsWith(github.ref, 'refs/tags/v')
run: python scripts/validate_release_tag.py --tag "${GITHUB_REF_NAME}"

- name: Build predicate-authority
run: python -m build predicate_authority

Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ jobs:

- name: Install test dependencies
run: |
python -m pip install --upgrade pip pytest
python -m pip install --upgrade pip pytest bandit
python -m pip install -e predicate_contracts -e predicate_authority

- name: Run tests
run: python -m pytest -q

- name: Run auth module security checks
run: |
python -m bandit -q -r predicate_authority/bridge.py predicate_authority/daemon.py predicate_authority/control_plane.py
python scripts/check_no_plaintext_okta_secrets.py
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
.PHONY: hooks lint test examples verify-release-order build-packages format format-python format-docs lint-docs
.PHONY: hooks lint test examples verify-release-order build-packages format format-python format-docs lint-docs dev-install tag-release

dev-install:
python -m pip install -e predicate_contracts -e predicate_authority

tag-release:
@test -n "$(VERSION)" || (echo "Usage: make tag-release VERSION=X.Y.Z" && exit 1)
python scripts/validate_release_tag.py --tag "v$(VERSION)"
git tag -a "v$(VERSION)" -m "Release v$(VERSION)"
@echo "Created tag v$(VERSION). Push with: git push origin v$(VERSION)"

hooks:
pre-commit install
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[![License](https://img.shields.io/badge/License-MIT%2FApache--2.0-blue.svg)](LICENSE)
[![PyPI - predicate-authority](https://img.shields.io/pypi/v/predicate-authority.svg)](https://pypi.org/project/predicate-authority/)
[![PyPI - predicate-contracts](https://img.shields.io/pypi/v/predicate-contracts.svg)](https://pypi.org/project/predicate-contracts/)
[![Release Tag](https://img.shields.io/badge/release-vX.Y.Z-blue)](docs/pypi-release-guide.md)

`predicate-authority` is a production-grade pre-execution authority layer that binds AI agent identity to deterministic state. It bridges standard IdPs (Entra ID, Okta, OIDC) with runtime verification so every sensitive action is authorized, bounded, and provable.

Expand Down Expand Up @@ -61,6 +62,17 @@ Implemented in this repository:
pip install predicate-authority
```

For local editable development in this monorepo, install both package roots
(do not use `pip install -e .` at repo root):

```bash
make dev-install
# equivalent: python -m pip install -e predicate_contracts -e predicate_authority
```

Release note: publish is supported by pushing a synchronized git tag `vX.Y.Z`
(see `docs/pypi-release-guide.md`).

For shared contracts directly:

```bash
Expand Down
205 changes: 205 additions & 0 deletions docs/authorityd-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,217 @@ PYTHONPATH=. predicate-authorityd \
--mandate-signing-key-env PREDICATE_AUTHORITY_SIGNING_KEY
```

## 2b) Okta production hardening checklist + staging matrix

Use this section when validating enterprise IdP readiness for Phase 2.

### Checklist

- [ ] Configure dedicated Okta OIDC app integration per environment (staging/prod split).
- [ ] Verify configured `issuer` and `audience` are exact matches to the target environment.
- [ ] Verify required claims/scopes/groups mapping used by authority role/tenant checks.
- [ ] Enforce strict JWT checks (`iss`, `aud`, `exp`, `nbf`, `iat`, required claims, alg allowlist).
- [ ] Validate JWKS retrieval and cache behavior for normal operation.
- [ ] Validate key rotation behavior (`kid` rollover) without service restart.
- [ ] Validate fail-closed behavior for cold-start JWKS failure and stale key scenarios.
- [x] Validate redaction: no token/secret leakage in logs on failures/retries.
- [x] Validate startup diagnostics for missing/invalid auth configuration.
- [ ] Validate revocation path behavior under Okta-backed principals.

### Staging test matrix

| Test ID | Scenario | Expected Result |
| --- | --- | --- |
| OKTA-01 | Valid token (correct issuer/audience/scope) | Request authorized and audit emitted |
| OKTA-02 | Wrong issuer | Denied with issuer mismatch reason |
| OKTA-03 | Wrong audience | Denied with audience mismatch reason |
| OKTA-04 | Missing required scope | Denied fail-closed before action |
| OKTA-05 | Expired token | Denied with expiration reason |
| OKTA-06 | Future `nbf` beyond leeway | Denied with temporal validation reason |
| OKTA-07 | Unsupported signing algorithm | Denied before trust decision |
| OKTA-08 | JWKS rotation (`kid` changes) | Validation recovers without restart |
| OKTA-09 | JWKS outage with warm cache | Existing key path continues until cache boundary |
| OKTA-10 | JWKS outage with cold cache | Startup/auth fails closed with actionable diagnostics |
| OKTA-11 | Tenant outside allow-list | Denied with tenant policy reason |
| OKTA-12 | Principal/intent revocation during run | Subsequent action denied promptly |
| OKTA-13 | Log redaction check | No raw tokens/secrets in logs |

### Emergency JWKS key-rotation runbook (owner + on-call flow)

Owner model:

- Primary owner: Platform Identity On-call.
- Secondary owner: Security On-call (approver for forced key disable).
- Incident commander: Platform lead on duty.

Trigger conditions:

- compromised signing key suspected,
- unexpected `kid` churn causing authorization failures,
- emergency tenant request to invalidate active key material.

Runbook steps:

1. **Declare incident + freeze risky deploys**
- open incident channel and assign owner/approver,
- freeze policy/auth-related deploy pipelines until stabilized.
2. **Rotate signing key in Okta**
- publish new signing key and ensure new `kid` appears in JWKS,
- stop issuing tokens from compromised/old key.
3. **Force validation against refreshed JWKS**
- run targeted validation:
- `python3 -m pytest tests/test_identity_bridge_phase2.py -k "jwks_kid_rollover_refreshes_without_restart"`
- if runtime impact is active, temporarily reduce cache TTL and trigger sidecar restart waves.
4. **Confirm deny behavior for old/unknown `kid`**
- run:
- `python3 -m pytest tests/test_identity_bridge_phase2.py -k "jwks_stale_cache_and_outage_fails_closed_with_diagnostics"`
- verify fail-closed behavior remains active.
5. **Recovery validation**
- confirm healthy authorization path with new `kid`,
- confirm no broad deny regressions in tenant traffic.
6. **Closeout**
- document timeline, affected tenants, and remediation actions,
- restore deploy pipeline and publish post-incident notes.

### Signoff evidence commands (deterministic integration tests)

Run these from `AgentIdentity` repo root and attach output to signoff evidence.

1) Network partition fail-closed behavior:

```bash
python3 -m pytest tests/test_daemon_phase2.py -k "network_partition_fail_closed_raises_and_tracks_failure"
```

Checkpoints:

- pass result proves fail-closed error path is enforced when control-plane is partitioned and `fail_open=False`,
- `/status` payload includes incremented control-plane failure counters.

2) Restart recovery with persisted queue:

```bash
python3 -m pytest tests/test_daemon_phase2.py -k "restart_recovers_queue_after_partition"
```

Checkpoints:

- pre-restart flush queue has pending event(s),
- post-restart `POST /ledger/flush-now` reports `sent_count >= 1`,
- post-flush queue is empty (`GET /ledger/flush-queue` returns no items).

3) Redaction and failure-reason validation:

```bash
python3 -m pytest tests/test_identity_bridge_phase2.py -k "reasonful_and_redacted"
```

Checkpoints:

- validation error includes a reason category (e.g. issuer mismatch),
- error text does not include raw token string or sensitive claim values.

4) Okta token exchange/OBO compatibility (tenant capability-gated):

```bash
# If tenant supports token exchange:
export OKTA_OBO_COMPAT_CHECK_ENABLED=1
export OKTA_SUPPORTS_TOKEN_EXCHANGE=true
python3 -m pytest tests/test_okta_obo_compatibility.py -k "live_check_when_enabled"

# If tenant does NOT support token exchange:
export OKTA_OBO_COMPAT_CHECK_ENABLED=1
export OKTA_SUPPORTS_TOKEN_EXCHANGE=false
python3 -m pytest tests/test_okta_obo_compatibility.py -k "live_check_when_enabled"
```

Checkpoints:

- `client_credentials_ok` must pass in both modes,
- when `OKTA_SUPPORTS_TOKEN_EXCHANGE=true`, token exchange must succeed,
- when `OKTA_SUPPORTS_TOKEN_EXCHANGE=false`, token exchange path is explicitly gated as tenant-disabled (no false failure).

### Example demo script: Okta delegation compatibility

Run example from repo root:

```bash
python3 examples/delegation/okta_obo_compat_demo.py \
--issuer "$OKTA_ISSUER" \
--client-id "$OKTA_CLIENT_ID" \
--client-secret "$OKTA_CLIENT_SECRET" \
--audience "$OKTA_AUDIENCE" \
--scope "${OKTA_SCOPE:-authority:check}" \
--supports-token-exchange
```

Notes:

- omit `--supports-token-exchange` for tenants that do not support OBO/token exchange,
- script reports whether delegation path should use IdP token exchange or authority mandate delegation.

### Secret storage policy (Okta credentials)

- never commit Okta client secrets/API tokens/private keys to repo files,
- store Okta credentials in runtime secret manager and CI secret store only,
- CI enforcement:
- `scripts/check_no_plaintext_okta_secrets.py` scans for plaintext Okta secrets,
- auth module security checks run Bandit for `predicate_authority` auth paths.

When enabled, daemon bootstrap auto-attaches `ControlPlaneTraceEmitter` so each
authority decision pushes:

- audit events -> `/v1/audit/events:batch`
- usage credits -> `/v1/metering/usage:batch`

### Optional: use Okta identity mode

Provide Okta OIDC values via env vars:

```bash
export OKTA_ISSUER="https://<org>.okta.com/oauth2/default"
export OKTA_CLIENT_ID="<okta-client-id>"
export OKTA_AUDIENCE="api://predicate-authority"
```

Start daemon in Okta mode:

```bash
PYTHONPATH=. predicate-authorityd \
--host 127.0.0.1 \
--port 8787 \
--mode cloud_connected \
--identity-mode okta \
--okta-issuer "$OKTA_ISSUER" \
--okta-client-id "$OKTA_CLIENT_ID" \
--okta-audience "$OKTA_AUDIENCE" \
--okta-required-claims "sub,tenant_id" \
--okta-required-scopes "authority:check" \
--okta-required-roles "authority-operator" \
--okta-allowed-tenants "tenant-a" \
--idp-token-ttl-s 300 \
--mandate-ttl-s 300 \
--policy-file examples/authorityd/policy.json
```

Safety gate note:

- in `cloud_connected` mode, `identity-mode local` or `identity-mode local-idp` now requires explicit `--allow-local-fallback`,
- this prevents accidental implicit downgrade to local identity behavior.

TTL alignment note:

- startup enforces `idp-token-ttl-s >= mandate-ttl-s` to avoid mandates outliving identity session controls.

### Emergency rollback route (Okta integration)

If Okta integration causes broad auth failures, use this rollback sequence:

1. disable the affected Okta app integration for the impacted environment,
2. rotate signing keys and invalidate compromised sessions in Okta,
3. switch sidecar traffic to a known-good identity config (or controlled local fallback with explicit `--allow-local-fallback`),
4. verify deny behavior + recovery through signoff evidence commands before restoring normal traffic.

## 3b) Optional local identity registry (ephemeral task identities)

Enable local identity support:
Expand Down
Loading