diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b373bb1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.venv +.env +.env.* +.auth +var +staticfiles +db.sqlite3 +**/__pycache__ +**/*.pyc +.pytest_cache +.ruff_cache +scripts/node_modules +tests +docs +history +workplans diff --git a/Dockerfile b/Dockerfile index 5885faa..20fb42f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,8 @@ COPY manage.py pyproject.toml ./ COPY coulomb_social ./coulomb_social COPY scripts/docker-entrypoint.sh /app/docker-entrypoint.sh -RUN chmod +x /app/docker-entrypoint.sh \ +RUN python manage.py collectstatic --noinput \ + && chmod +x /app/docker-entrypoint.sh \ && chown -R appuser:appuser /app USER appuser diff --git a/docs/identity/user-engine-binding.md b/docs/identity/user-engine-binding.md index a534b58..19ccf1c 100644 --- a/docs/identity/user-engine-binding.md +++ b/docs/identity/user-engine-binding.md @@ -29,19 +29,13 @@ X-Verified-Oidc-Claims: {"iss","sub","tenant","principal_type","aud",...} plus `coulomb-social`. Trust is the proxy secret; claims were verified by KeyCape OIDC at the app edge. -### Operator: enable HTTP mode locally +### Governed credential configuration -```bash -# secret never printed to shell history if you use process substitution carefully -export USER_ENGINE_BASE_URL=https://users.92-205-62-239.nip.io -export USER_ENGINE_PROXY_SECRET="$(kubectl -n user-engine get secret user-engine-runtime \ - -o jsonpath='{.data.proxy-secret}' | base64 -d)" -export USER_ENGINE_EXPECTED_AUDIENCE=user-engine-portal -# then OIDC or dev-login -``` - -OpenBao lane for the proxy secret (future): document under railiance-platform -workload KV; until then kubectl-sourced secret is operator-only on the workstation. +Live HTTP mode already uses mounted runtime credential references. Do not +extract the proxy secret with kubectl/jsonpath or print it into shell/chat. +For a new credential need, run `warden route find "user-engine trusted proxy credential" --json` +and follow the owning OpenBao/platform lane. Interactive personal login belongs +to KeyCape/NetKingdom; it does not require retrieving this server credential. ## Profile attributes (shell only) diff --git a/scripts/keycape-client-repair.py b/scripts/keycape-client-repair.py new file mode 100644 index 0000000..ee87086 --- /dev/null +++ b/scripts/keycape-client-repair.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Restore the historical public client through NetKingdom's guarded lane. + +Inspection is read-only. Apply requires exact founder approval and a fresh +UID/resource-version receipt. Secret values stay inside the owner helper. +""" +import importlib.util +import os +from pathlib import Path +from types import SimpleNamespace + + +def load(name, path): + spec = importlib.util.spec_from_file_location(name, path) + result = importlib.util.module_from_spec(spec) + spec.loader.exec_module(result) + return result + + +def configure(owner_root): + rollout = load("keycape_guarded_rollout", owner_root / "portal-client-rollout.py") + historical = load("coulomb_registration", owner_root / "register-coulomb-social.py") + original = rollout.replacement + rollout.portal = SimpleNamespace(CLIENT_ID=historical.CLIENT_ID, CLIENT=historical.CLIENT) + + def replacement(secret): + _, config, _ = rollout.pin.issuer_document(secret) + for client in config.get("clients", []): + if client.get("clientId") == historical.CLIENT_ID: + rollout.require(client == historical.CLIENT, "existing_registration_differs") + return original(secret) + + rollout.replacement = replacement + return rollout + + +if __name__ == "__main__": + try: + owner_root = Path(os.environ.get( + "NET_KINGDOM_KEYCAPE_DIR", + str(Path.home() / "net-kingdom/sso-mfa/k8s/keycape"), + )) + helper = configure(owner_root) + except Exception: + print('{"status":"failed","reason":"owner_helper_unavailable"}') + raise SystemExit(1) + raise SystemExit(helper.main()) diff --git a/scripts/register-keycape-client.sh b/scripts/register-keycape-client.sh index 751e557..c7c500d 100755 --- a/scripts/register-keycape-client.sh +++ b/scripts/register-keycape-client.sh @@ -1,26 +1,8 @@ #!/usr/bin/env bash -# Register this app's OIDC client on live KeyCape (kc.coulomb.social). -# Delegates to net-kingdom/sso-mfa/k8s/keycape/register-coulomb-social.sh +# Inspect by default; explicit dry-run/apply use guarded owner receipts. set -euo pipefail - ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -NK_SCRIPT="${NET_KINGDOM_KEYCAPE_DIR:-$HOME/net-kingdom/sso-mfa/k8s/keycape}/register-coulomb-social.sh" - -if [[ ! -x "$NK_SCRIPT" && ! -f "$NK_SCRIPT" ]]; then - echo "ERROR: KeyCape register script not found: $NK_SCRIPT" >&2 - echo "Clone net-kingdom or set NET_KINGDOM_KEYCAPE_DIR." >&2 - exit 1 +if [[ $# -eq 0 ]]; then + set -- inspect fi - -chmod +x "$NK_SCRIPT" 2>/dev/null || true -bash "$NK_SCRIPT" - -echo "" -echo "Next: export OIDC settings and restart the app:" -echo " export OIDC_ENABLED=true" -echo " export OIDC_ISSUER=https://kc.coulomb.social" -echo " export OIDC_CLIENT_ID=coulomb-social" -echo " export OIDC_REDIRECT_URI=http://127.0.0.1:8008/auth/callback/" -echo " # no client secret — public + PKCE" -echo " make run" -echo "Docs: $ROOT/docs/identity/oidc-client.md" +exec python3 "$ROOT/scripts/keycape-client-repair.py" "$@" diff --git a/scripts/test_keycape_client_repair.py b/scripts/test_keycape_client_repair.py new file mode 100644 index 0000000..5ce5eb8 --- /dev/null +++ b/scripts/test_keycape_client_repair.py @@ -0,0 +1,49 @@ +"""Synthetic owner-lane checks; run with python3 scripts/test_keycape_client_repair.py.""" +import base64 +import json +from pathlib import Path +import unittest + +import yaml + +from importlib.machinery import SourceFileLoader + +adapter = SourceFileLoader("csoc_repair", str(Path(__file__).with_name("keycape-client-repair.py"))).load_module() + + +class RepairTests(unittest.TestCase): + def setUp(self): + self.rollout = adapter.configure(Path.home() / "net-kingdom/sso-mfa/k8s/keycape") + + def fixture(self, clients): + raw = ("# keep comment\nauthelia:\n issuer: https://auth.example.invalid\n" + " clientSecret: synthetic-marker\nclients:\n" + + yaml.safe_dump(json.loads(json.dumps(clients)), sort_keys=False) + "tokenLifetime: 15m\n") + return {"data": {"config.yaml": base64.b64encode(raw.encode()).decode(), + "key.pem": "synthetic-key"}}, raw + + def test_insert_preserves_bytes_and_is_idempotent(self): + secret, raw = self.fixture([{"clientId": "existing", "clientType": "public"}]) + encoded, changed = self.rollout.replacement(secret) + updated = base64.b64decode(encoded).decode() + self.assertTrue(changed) + self.assertTrue(updated.startswith(raw[:raw.index("tokenLifetime:")])) + self.assertTrue(updated.endswith("tokenLifetime: 15m\n")) + expected = yaml.safe_load(raw) + expected["clients"].append(self.rollout.portal.CLIENT) + self.assertEqual(yaml.safe_load(updated), expected) + self.assertEqual(secret["data"]["key.pem"], "synthetic-key") + secret["data"]["config.yaml"] = encoded + self.assertEqual(self.rollout.replacement(secret), (encoded, False)) + + def test_refuses_different_or_duplicate_registration(self): + client = self.rollout.portal.CLIENT + for clients in [[dict(client, redirectUris=["https://other.invalid/callback"])], + [dict(client, allowedScopes=["openid", "admin"])], + [client, client]]: + with self.subTest(clients=clients), self.assertRaises(self.rollout.Refused): + self.rollout.replacement(self.fixture(clients)[0]) + + +if __name__ == "__main__": + unittest.main() diff --git a/workplans/CSOC-WP-0008-production-transfer-acceptance.md b/workplans/CSOC-WP-0008-production-transfer-acceptance.md index ff7fcf4..8433513 100644 --- a/workplans/CSOC-WP-0008-production-transfer-acceptance.md +++ b/workplans/CSOC-WP-0008-production-transfer-acceptance.md @@ -66,7 +66,7 @@ an instruction to bulk-move every space before the first user review. ```task id: CSOC-WP-0008-T01 -status: wait +status: progress priority: high state_hub_task_id: "6adce69f-9011-5ada-a920-b2228aa15218" ``` @@ -266,3 +266,41 @@ clipboard and blocked/manual fallback pass Chromium checks with synthetic data. Full application suite: 87 passed; new test Ruff clean. Real latest protocol reconciliation and actual mail-client/user review remain T03/T09, not completed by these tests. No live deploy, private-data import or mail sending performed. + +## Live orientation and personal account — 2026-10-06 + +Fresh checks confirm a healthy 1/1 coulomb-social Helm workload on railiance01 +(92.205.62.239), image 7fcd0cf, revision 9. app.coulomb.social landing and health +return 200; KeyCape discovery is live. No content PVC/CONTENT_ROOT exists in +that old release. One live Member links the user-selected tegwick identity to +KeyCape and tenant:coulomb; one demo space exists. No new password/account is +needed. The user tried sign-in and received invalid_profile_usage / unknown +client_id. Guarded issuer inspection confirms the coulomb-social client is absent; +the stored Member and healthy discovery did not prove interactive login worked. + +T01 now progresses by preparing the latest tested app image with collected +static assets and an owning-chart persistent CONTENT_ROOT upgrade. Single-writer +Recreate avoids concurrent filesystem writers and rolling CPU surge. Exact live +change approval follows the prepared diff/dry-run and immutable image receipt. +Node CPU requests observed 3455m/4000m; disk has 123G available. +No deployment or credential change was made by these read-only checks. + +## T11 — Restore personal OIDC sign-in + +```task +id: CSOC-WP-0008-T11 +status: progress +priority: high +``` + +Restore the historical NetKingdom public coulomb-social authorization-code/PKCE +client through the owning guarded helper; refuse differing existing registrations, +preserve unrelated configuration bytes and Secret data, pin cluster identity and +Secret UID/resourceVersion. Inspection and server dry-run passed on 2026-10-06; +no live change made. Existing historical ordinary AAL1 behavior (mfaRequired: +false) is explicit in the review packet; shared issuer restart requires exact +founder approval. User tegwick must then confirm interactive login. Do not mark +this task done on an HTTP redirect or discovery-only check. + +Knowledge receipt: emergent-security 0.2.6 identity, verified-files, +orientation-only; digest 8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02.