Prepare restricted seven-source reuse reader and bounded recovery
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a137ca-5c77-7a71-b9ae-23ad9b0b5684
This commit is contained in:
parent
748fe6c7db
commit
a570bc2f5b
12 changed files with 1367 additions and 0 deletions
|
|
@ -0,0 +1,155 @@
|
|||
id: CCR-2026-0046
|
||||
kind: credential-change-request
|
||||
schema_version: 1
|
||||
request_type: workload-kv-read
|
||||
title: Reuse Surface exact seven private-source repository reader
|
||||
status: proposed
|
||||
created: '2026-10-15'
|
||||
updated: '2026-10-15'
|
||||
requester:
|
||||
agent: platform-reader-owner delegated preparation worker
|
||||
reason: CUST-WP-0081 hosted63/70; founder metadata consent excludes credential provisioning.
|
||||
Prepare separate seven-source reader.
|
||||
review:
|
||||
required: true
|
||||
required_approvers:
|
||||
- platform-operator
|
||||
- railiance-forge-owner
|
||||
- reuse-surface-owner
|
||||
- railiance-apps-owner
|
||||
comments: []
|
||||
target:
|
||||
domain: infotech
|
||||
tenant: reuse
|
||||
workload: reuse-surface
|
||||
environment: production
|
||||
purpose: Native acquisition of seven owner-consented pinned capability indexes.
|
||||
Entire private repository code becomes readable by this dedicated reader; publication
|
||||
remains only approved metadata.
|
||||
openbao:
|
||||
mount: platform
|
||||
kv_path: platform/workloads/reuse/reuse-surface/forgejo-source-read
|
||||
fields:
|
||||
- FORGEJO_SOURCE_TOKEN
|
||||
- API_USER
|
||||
- API_BASE_URL
|
||||
- TOKEN_SCOPES
|
||||
- GENERATED_AT
|
||||
- CCR_ID
|
||||
policy_name: workload-kv-read-reuse-surface-runtime-eso
|
||||
policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl
|
||||
auth:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
role: reuse-surface-runtime-eso
|
||||
bound_claims:
|
||||
service_account_names:
|
||||
- reuse-surface-eso
|
||||
service_account_namespaces:
|
||||
- reuse
|
||||
bound_claims_confirmed: false
|
||||
policies:
|
||||
- workload-kv-read-reuse-surface-runtime-eso
|
||||
ttl: 15m
|
||||
audience: openbao
|
||||
access_frontdoor:
|
||||
type: external-secrets
|
||||
catalog_id: reuse-surface-forgejo-source-read
|
||||
readiness: pending-review
|
||||
resolvable: false
|
||||
delivery:
|
||||
surface: external-secrets
|
||||
bootstrap_command: warden access openbao-platform-admin-login --exec -- python3
|
||||
scripts/provision-reuse-surface-forgejo-source.py --approval <approved-revision-receipt>
|
||||
--receipt <0600-durable-receipt>
|
||||
provisioning_script: scripts/provision-reuse-surface-forgejo-source.py
|
||||
target: Existing namespace-limited store openbao-reuse. Apps owner adds only proposed
|
||||
data item into ExternalSecret reuse/reuse-surface-runtime. Shared target reuse-surface-env
|
||||
remains intact.
|
||||
forgejo_identity: reuse-surface-source
|
||||
forgejo_team: reuse-surface-source-readers
|
||||
repositories:
|
||||
- coulomb/rapp-openbao
|
||||
- coulomb/rapp-policy-nexus
|
||||
- coulomb/rapp-postgres
|
||||
- coulomb/rapp-sbom-nexus
|
||||
- coulomb/rapp-telemetry
|
||||
- coulomb/rapp-tenant-engine
|
||||
- coulomb/rapp-user-engine
|
||||
token_scopes:
|
||||
- read:repository
|
||||
consumer_gate: Reader must not be delivered until reuse-surface owner supplies verified
|
||||
image digest containing BOTH authenticated redirect rejection/HTTPS enforcement
|
||||
AND runtime-controlled exact URL allowlist checked before credential lookup. Apps
|
||||
owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment
|
||||
to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated
|
||||
sources for migration. Current image and redirect-only fix are not approved. Native
|
||||
owner helper must verify all seven pinned indexes and entry hashes after rollout.
|
||||
risk:
|
||||
classification: high
|
||||
cost_estimate_eur: 0
|
||||
notes:
|
||||
- Repository grants disclose entire source of these seven repositories to hub; PAT
|
||||
cannot restrict access to index files.
|
||||
- No all-repositories membership, unrelated private grants, admin privileges or
|
||||
repository writes.
|
||||
- Existing ESO role expands by one exact data-read path; writer OIDC caller remains
|
||||
unchanged and denied new path.
|
||||
- Current consumer accepts arbitrary authenticated source URLs and redirects, allowing
|
||||
bearer disclosure. Delivery barred until full reviewed redirect/HTTPS and runtime-controlled
|
||||
exact URL allowlist patch image plus config is verified.
|
||||
- PAT has no server-side expiry; rotate quarterly and immediately upon incident.
|
||||
- Bootstrap writes only exact new identity/team/PAT/KV. Policy apply, ESO delivery,
|
||||
consumer rollout and registrations require their separate reviewed owner steps.
|
||||
- R1 pagination defect preserved; R2 pages until empty with duplicate/count/bound
|
||||
checks. Deployed-version live complete-grant inventory remains required; code
|
||||
review is not live scope proof.
|
||||
- Quarterly rotation is a proposed manual requirement, not deployed expiry enforcement;
|
||||
due-date owner task/receipt and separate exact-token recovery approval must be
|
||||
established before activation.
|
||||
verification:
|
||||
positive:
|
||||
- All seven repositories private; exact team/code-read flags, one dedicated team
|
||||
member, exact seven memberships, effective permissions read and exactly one read:repository
|
||||
PAT.
|
||||
- Each consented index digest matches from authenticated Forgejo read; final hosted
|
||||
native owner helper verifies pinned entry hashes as well.
|
||||
- OpenBao stdin CAS=0 field write readback matches without printing or persisting
|
||||
value.
|
||||
- ESO binding and namespace condition surveyed and confirmed; native fixed consumer
|
||||
image verified before delivery; final hosted70/70 exactly once.
|
||||
negative:
|
||||
- Known excluded private rapp-core-hub reader request denied (403/404); administrative
|
||||
endpoint and Actions secrets denied.
|
||||
- No additional private effective repositories or teams; no write probing that could
|
||||
itself cause a change.
|
||||
- Scoped writer and default OpenBao identities denied new data path; ESO role denied
|
||||
unrelated siblings and wrong SA/namespace/audience login refused.
|
||||
- Missing/anonymous native authentication refused with no privileged fallback.
|
||||
activation_conditions:
|
||||
- Independent platform/Forgejo/reuse/apps owner reviews of exact artifacts and founder
|
||||
CCR provisioning disposition recorded; no approval inferred from metadata consent.
|
||||
- Confirm existing ESO Kubernetes binding before any delegated metadata apply.
|
||||
- Consumer security patch image digest verified and apps rollout artifacts reviewed/approved
|
||||
before delivery.
|
||||
- Durable bounded bootstrap and positive/negative receipts pass; owner native fetch
|
||||
proof passes all seven pinned sources.
|
||||
- Separate scoped writer registration batch followed by fresh hosted70/70 completion
|
||||
audit.
|
||||
evidence: []
|
||||
lifecycle:
|
||||
deactivate: Separate explicitly approved silent scripts/revoke-reuse-surface-forgejo-source.py
|
||||
validates reviewed single PAT ID/name, admin PATCH only dedicated user to fresh
|
||||
process-memory password, Basic-auth DELETE exact token, then reconciles complete
|
||||
inventory absence. Admin PAT/Sudo alone cannot delete. KV/shared runtime Secret
|
||||
preserved. Apps/platform owners remove only new ESO item/policy stanza through
|
||||
their reviewed path; preserve writer/webhook/public registrations. Recovery helper
|
||||
approval is separate from mint/bootstrap approval.
|
||||
rotate: Attended owner revokes old identified PAT then governed bootstrap remints
|
||||
and verifies; never overwrite differing/orphan KV through bootstrap. Explicit
|
||||
scoped CAS update plan required for rotation.
|
||||
compromised: Revoke all dedicated service-user PATs, remove delivery data item,
|
||||
roll fixed consumer with owners, retain failed sanitized evidence; investigate
|
||||
all seven private source exposure. Do not rotate unrelated credentials without
|
||||
separate scope.
|
||||
revision: 2
|
||||
83
docs/proposals/2026-10-15-reuse-private-reader/README.md
Normal file
83
docs/proposals/2026-10-15-reuse-private-reader/README.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Seven private-source reader preparation
|
||||
|
||||
This bundle is prepared under platform's INTENT custody/Q1 remit. Nothing here
|
||||
records founder credential approval or another owner's review. The existing
|
||||
all-repository core-hub provisioner is precedent only and is never executed.
|
||||
|
||||
The exact bounded command, after approved CCR, independent reviews and binding
|
||||
confirmation, is:
|
||||
|
||||
```sh
|
||||
warden access openbao-platform-admin-login --exec -- python3 scripts/provision-reuse-surface-forgejo-source.py --approval /absolute/path/approved-artifact-receipt.json --receipt /absolute/path/durable-0600-receipt.jsonl
|
||||
```
|
||||
|
||||
An approval receipt must contain `ccr_id: CCR-2026-0046`, `decision: approved`,
|
||||
nonempty `supervisor` and `decision_ref`, and `approved_artifacts` mapping exactly
|
||||
these four relative paths to SHA256 digests of their approved bytes:
|
||||
|
||||
- credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml
|
||||
- scripts/provision-reuse-surface-forgejo-source.py
|
||||
- docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json
|
||||
- openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl
|
||||
|
||||
CCR itself must be approved; preparation hashes do not satisfy that gate. This
|
||||
local check binds revisions and relies on attended platform lane/supervisor
|
||||
custody for authenticity; it is not a signed approval authority or an autopilot
|
||||
grant. Metadata apply is separately governed by existing credential-change CLI.
|
||||
Bootstrap never applies policy or changes cluster delivery.
|
||||
|
||||
`apps-externalsecret-data-item.draft.yaml` is a proposed insertion for apps owner,
|
||||
not a deployable manifest or an apps review. Existing ESO role reads only one new
|
||||
exact data path; caller writer policy is unchanged. Claim confirmation remains
|
||||
false until live owner survey. Consumer delivery must wait for the FULL reviewed
|
||||
image fixing redirects/HTTPS and runtime-controlled exact source URL allowlist,
|
||||
with fourteen consented pinned URLs in REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST.
|
||||
|
||||
Failure recovery: inspect sanitized journal and authoritative identity/team/PAT/
|
||||
KV state quietly. A missing observation is not proof a request did not apply.
|
||||
The script refuses orphan PAT/KV state rather than reminting. Revoke only an
|
||||
identified dedicated PAT through Forgejo owner's attended recovery; if mint
|
||||
response lost, locate its exact recorded attempt-name prefix before revocation.
|
||||
Never delete unrelated token or shared runtime Secret. Rotation/recovery of an
|
||||
existing KV version requires a separately scoped CAS update plan; this create-only
|
||||
bootstrap does not overwrite or automatically rollback ambiguous writes.
|
||||
|
||||
Known execution gates: provisional IDs must be rechecked against current primary;
|
||||
Forgejo owner must confirm the API contracts (team organization username, complete
|
||||
sudo user inventory and token_last_eight metadata) on the deployed server; missing
|
||||
or differing metadata fails closed. Excluded-private rapp-core-hub must still be
|
||||
private. Native verification is a separate consumer-owned proof, not this API
|
||||
bootstrap's index-only custody check. New reader provisioning/production approval,
|
||||
apps config delta, fixed-image digest, live ESO survey and final70 audit all pending.
|
||||
|
||||
Revision2 refines the same CCR after a confirmed server-capped pagination defect.
|
||||
R1 patch and failed review evidence remain retained; no new scored trial is claimed.
|
||||
All list requests now continue until an observed empty page, reject duplicate IDs,
|
||||
and reconcile supplied total counts. Forgejo v11.0.3 source confirms user/repos
|
||||
includes effective direct/team grants. Stable ID sort is requested for that list.
|
||||
Version compatibility and live proof still precede apply.
|
||||
|
||||
Forgejo11.0.3 token DELETE requires Basic authentication, unlike token inventory GET.
|
||||
Admin PAT/Sudo alone is not a valid recovery implementation. The separate reviewed
|
||||
recovery must reset only this dedicated user password to fresh process-memory bytes,
|
||||
Basic-auth DELETE only the reviewed token ID, and verify absence. Recovery
|
||||
helper approval and manual rotation due-date task remain activation gates; no
|
||||
deployed quarterly expiry enforcement is claimed.
|
||||
|
||||
|
||||
The separately reviewed executable credential rollback is:
|
||||
|
||||
```sh
|
||||
warden access openbao-platform-admin-login --exec -- python3 scripts/revoke-reuse-surface-forgejo-source.py --approval /absolute/path/approved-exact-token-recovery.json --receipt /absolute/path/durable-0600-recovery.jsonl
|
||||
```
|
||||
|
||||
Its independent approval record names `action: revoke_exact_pat`, the CCR,
|
||||
`decision: approved`, supervisor and decision_ref, `target: {id: <reviewed id>,
|
||||
name: <exact recorded attempt name>}`, and SHA256s in `approved_artifacts` for
|
||||
exactly recovery helper, bootstrap helper, CCR and bootstrap contract. This
|
||||
receipt also relies on attended supervisor custody; it is not signed authority.
|
||||
Different inventory stops before password mutation. Password stays in memory;
|
||||
DELETE uses Basic authorization captured only by the quiet client. Uncertain
|
||||
DELETE is followed only by read-only absence reconciliation, never another
|
||||
DELETE or mint. KV is deliberately preserved, so rotation needs its separate CAS
|
||||
plan. Credential rollback and apps delivery cleanup have independent receipts.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
secretKey: REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN
|
||||
remoteRef:
|
||||
key: workloads/reuse/reuse-surface/forgejo-source-read
|
||||
property: FORGEJO_SOURCE_TOKEN
|
||||
|
|
@ -0,0 +1,211 @@
|
|||
{
|
||||
"id": "CCR-2026-0046-BOOTSTRAP-R2",
|
||||
"revision": 2,
|
||||
"sources": [
|
||||
{
|
||||
"repo": "rapp-openbao",
|
||||
"capability_ids": [
|
||||
"capability.railiance.openbao-package"
|
||||
],
|
||||
"consented_commit": "35672be138036c8433a5ac5de91e068c03c2b124",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124",
|
||||
"index_sha256": "044efa3d02b90e1872f2516c581159d388a8080a689f76fcd78bb4d048d6ee64",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-policy-nexus",
|
||||
"capability_ids": [
|
||||
"capability.railiance.policy-publication-package"
|
||||
],
|
||||
"consented_commit": "088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
|
||||
"index_sha256": "e68bdedf61eee9ca2026f46a2434ce384c26234616e74f7a5d08a06c75b3bb00",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-postgres",
|
||||
"capability_ids": [
|
||||
"capability.railiance.postgres-isolation-recovery"
|
||||
],
|
||||
"consented_commit": "a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
|
||||
"index_sha256": "d939333eed9d85172e006f526c445fbe2602a721d53f7c0d14bc68286ae08776",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-sbom-nexus",
|
||||
"capability_ids": [
|
||||
"capability.railiance.sbom-runtime-package"
|
||||
],
|
||||
"consented_commit": "adbedd349e8df97be43a1f57db7095af45ff29d1",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1",
|
||||
"index_sha256": "d2ee0f193d2198bf8f16ce64c90ae7664e59a2bc307ad711426af3fd47deb057",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-telemetry",
|
||||
"capability_ids": [
|
||||
"capability.railiance.telemetry-package"
|
||||
],
|
||||
"consented_commit": "f158307e24b86bab88a1af4830ff6b4f85afdeb5",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5",
|
||||
"index_sha256": "ee8801f143ed5cf064205710a61c2bb24efc6ea8585d5786d6b679426ec4c1d4",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-tenant-engine",
|
||||
"capability_ids": [
|
||||
"capability.railiance.tenant-engine-package"
|
||||
],
|
||||
"consented_commit": "9bdc86572728783a5ed94266f04f7b12d414c557",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557",
|
||||
"index_sha256": "d8e9c1282350b5d2fd4f4d69712aa7a5fbcd2367bb1a7fad1b6eb16a97e5d732",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
},
|
||||
{
|
||||
"repo": "rapp-user-engine",
|
||||
"capability_ids": [
|
||||
"capability.railiance.user-engine-package"
|
||||
],
|
||||
"consented_commit": "f35108f9e1b8702fb6c526fe42b2fec5ca032738",
|
||||
"native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738",
|
||||
"index_sha256": "95b4ed2d0b189f6847b1c0899f740d1da46933a8f40491e1a02f2d4e57c819fc",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN"
|
||||
}
|
||||
],
|
||||
"negative_private_probe": "rapp-core-hub",
|
||||
"identity": {
|
||||
"forgejo_user": "reuse-surface-source",
|
||||
"forgejo_org": "coulomb",
|
||||
"forgejo_team": "reuse-surface-source-readers",
|
||||
"restricted": true,
|
||||
"is_admin": false,
|
||||
"visibility": "private",
|
||||
"password": "random value retained only in attended process memory; never stored in evidence",
|
||||
"team_permission": "read",
|
||||
"includes_all_repositories": false,
|
||||
"can_create_org_repo": false,
|
||||
"units_map": {
|
||||
"repo.code": "read",
|
||||
"repo.actions": "none",
|
||||
"repo.packages": "none",
|
||||
"repo.issues": "none",
|
||||
"repo.ext_issues": "none",
|
||||
"repo.wiki": "none",
|
||||
"repo.pulls": "none",
|
||||
"repo.releases": "none",
|
||||
"repo.projects": "none",
|
||||
"repo.ext_wiki": "none"
|
||||
},
|
||||
"team_repositories": [
|
||||
"coulomb/rapp-openbao",
|
||||
"coulomb/rapp-policy-nexus",
|
||||
"coulomb/rapp-postgres",
|
||||
"coulomb/rapp-sbom-nexus",
|
||||
"coulomb/rapp-telemetry",
|
||||
"coulomb/rapp-tenant-engine",
|
||||
"coulomb/rapp-user-engine"
|
||||
],
|
||||
"token_scopes": [
|
||||
"read:repository"
|
||||
],
|
||||
"token_name_prefix": "reuse-surface-source-read-",
|
||||
"reuse_other_workload_token": false,
|
||||
"fail_on_differing_existing_identity_or_team": true,
|
||||
"enforcement": "Verify complete team membership and all effective user repository grants; no other team, collaborator or organisation grants may provide extra private read access."
|
||||
},
|
||||
"delivery": {
|
||||
"kv_mount": "platform",
|
||||
"kv_path": "platform/workloads/reuse/reuse-surface/forgejo-source-read",
|
||||
"field": "FORGEJO_SOURCE_TOKEN",
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN",
|
||||
"eso_role": "reuse-surface-runtime-eso",
|
||||
"eso_identity": "reuse/reuse-surface-eso",
|
||||
"audience": "openbao",
|
||||
"ttl": "15m",
|
||||
"cluster_secret_store": "openbao-reuse",
|
||||
"store_namespace_condition": [
|
||||
"reuse"
|
||||
],
|
||||
"external_secret": "reuse/reuse-surface-runtime",
|
||||
"target_secret": "reuse/reuse-surface-env",
|
||||
"existing_consumption": "Deployment reuse/reuse-surface envFrom secretRef reuse-surface-env; environment variable available only after controlled rollout. Existing image and resource requests retained.",
|
||||
"policy_delta": {
|
||||
"file": "railiance-platform/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl",
|
||||
"add_exact_stanza": "path \"platform/data/workloads/reuse/reuse-surface/forgejo-source-read\" { capabilities = [\"read\"] }",
|
||||
"retain_existing_paths": true,
|
||||
"do_not_change": "Caller-facing workload-kv-read-reuse-surface-runtime policy or writer OIDC role; those remain unable to read this new path."
|
||||
},
|
||||
"external_secret_data_delta": {
|
||||
"secretKey": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN",
|
||||
"remoteRef": {
|
||||
"key": "workloads/reuse/reuse-surface/forgejo-source-read",
|
||||
"property": "FORGEJO_SOURCE_TOKEN"
|
||||
}
|
||||
},
|
||||
"delivery_design_reason": "Uses the existing namespace-limited ESO store and identity plus the existing envFrom interface. Separate new KV path keeps scoped writer callers from also acquiring private-source access. No new static ESO token or new store is needed.",
|
||||
"native_source_auth": {
|
||||
"auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN",
|
||||
"auth_header": "Authorization",
|
||||
"header_behavior": "Native federation _auth_headers prefixes a bare token with Bearer; owner must verify accepted Forgejo raw API route with this actual native implementation."
|
||||
},
|
||||
"rollout_image": "Pending full consumer fix reviewed image digest; same-image rollout of current consumer is prohibited."
|
||||
},
|
||||
"consumer_gate": "Reader must not be delivered until reuse-surface owner supplies verified image digest containing BOTH authenticated redirect rejection/HTTPS enforcement AND runtime-controlled exact URL allowlist checked before credential lookup. Apps owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated sources for migration. Current image and redirect-only fix are not approved. Native owner helper must verify all seven pinned indexes and entry hashes after rollout.",
|
||||
"approval": "No approval recorded. External supervisor approval receipt must bind exactly CCR, script, contract and ESO policy bytes; CCR must be approved. Additional forge/apps/reuse owner reviews and rollout approval remain required.",
|
||||
"metadata_consent_sha256": "4a4602ad926e3af855d6ddba4731430d33e4a7432c507ba0a5193b9f43056b52",
|
||||
"preparation_sha256": "7819eb51d249f5c7aff4df9fb520afcfb76603b05a4104439499102b6c20af99",
|
||||
"recovery": "On ambiguous mutation stop. Reconcile complete Forgejo identity/team/repository/PAT metadata and exact KV field through suppressed helper before retry. Token without KV or reverse refuses automatically; revoke identified dedicated PAT through attended Forgejo owner recovery before retry. Never remint on observation timeout. KV CAS=0 prevents overwrites. Do not delete shared runtime Secret.",
|
||||
"consumer_preparation": {
|
||||
"owner": "reuse-surface",
|
||||
"allowlist_artifact": "/tmp/cust81-reuse-reader-owner/docs/assessments/cust81-private-reader/runtime-auth-source-allowlist-r1.json",
|
||||
"allowlist_sha256": "e1948c537e206b30fc72b9e4732a3f600d92e131813e6f7ecfd4bfbb18c27286",
|
||||
"federation_py_sha256": "dbaa40f25cc0f090143578cd1c7068cb2cb00960fafd17d5239bf8a5bac514dd",
|
||||
"integration_patch_sha256": "da9634481ce80658dfce815d3539ba023234938dc76163dfd93d7677be95ed51",
|
||||
"review_state": "changes_requested for current consumer; preparation patch alone is not deployed-image evidence"
|
||||
},
|
||||
"pagination_contract": "All list endpoints page until observed empty, max20pages; reject duplicate/missing IDs, reconcile X-Total-Count when supplied, reject changed/mismatched counts and bound exhaustion. Never infer completion from requestedlimit100 versus server-capped50.",
|
||||
"effective_grants_contract": "Forgejo11.0.3 /user/repos?order_by=id with Sudo reader uses OwnerID=reader and Collaborate=None; SearchRepositoryCondition defaults collaborations true and includes direct UserAccessRepoCond plus UserOrgTeamRepoCond. It is not owned-only despite Swagger summary. Independent deployed-version compatibility and live inventory proof pending.",
|
||||
"r1_review_failure": {
|
||||
"patch_sha256": "a94547b419b87e38dc178b02d1d31af61cc11a71e3734052a190167668db8e30",
|
||||
"confirmed_defect": "R1 len(batch)<100 stopped after server-capped50, omitting later grants",
|
||||
"corrected_preliminary_claim": "Owned-only endpoint claim disproved by Forgejo11.0.3 model defaults; no endpoint replacement based on false premise",
|
||||
"same_proposal": "CCR-2026-0046; revision2 is refinement, not a new successful trial"
|
||||
},
|
||||
"api_source_evidence": [
|
||||
{
|
||||
"upstream_path": "models/repo/repo/list.go",
|
||||
"url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/models/repo/repo/list.go",
|
||||
"sha256": "bb7833badf0fe7f6a2c800c2f301cb846b5968ce3f9e9bd29c3f1677db42acd9",
|
||||
"local_cache": "/tmp/cust81-forge-contract-source/source-models_repo_repo_list.go"
|
||||
},
|
||||
{
|
||||
"upstream_path": "routers/api/v1/utils/page.go",
|
||||
"url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/routers/api/v1/utils/page.go",
|
||||
"sha256": "1d286f60b5571513fb5a26a9243c22eae65f05d7be32dfe5d0f0b15d20550954",
|
||||
"local_cache": "/tmp/cust81-forge-contract-source/source-routers_api_v1_utils_page.go"
|
||||
},
|
||||
{
|
||||
"upstream_path": "services/convert/utils.go",
|
||||
"url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/services/convert/utils.go",
|
||||
"sha256": "2689ffc579ea1df69457fbe84410fef101ae4a9580f0e56dcc3113a2988626b3",
|
||||
"local_cache": "/tmp/cust81-forge-contract-source/source-services_convert_utils.go"
|
||||
},
|
||||
{
|
||||
"upstream_path": "modules/setting/api.go",
|
||||
"url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/modules/setting/api.go",
|
||||
"sha256": "1f640ed884afaa6454c4bf427eccd2d6bb9a092fe37ac1aedfdc5eb85de4f7ba",
|
||||
"local_cache": "/tmp/cust81-forge-contract-source/source-modules_setting_api.go"
|
||||
},
|
||||
{
|
||||
"upstream_path": "routers/api/v1/user/repo.go",
|
||||
"url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/routers/api/v1/user/repo.go",
|
||||
"sha256": "adee62e6671aadd95b5013a64dbb690f2d81a0b9b4de0350c70b4153074ca6ec",
|
||||
"local_cache": "/tmp/cust81-forge-contract-source/source-routers_api_v1_user_repo.go"
|
||||
}
|
||||
],
|
||||
"rollback_helper": {
|
||||
"path": "scripts/revoke-reuse-surface-forgejo-source.py",
|
||||
"scope": "Separate approval binds exact PAT id/name and artifact bytes; Basic-auth revocation after dedicated-user memory-only password reset. Preserve KV/sharedruntime; no remint. Reconcile uncertain DELETE by inventory absence."
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"id": "RPF-WP-0058-PLATFORM-OWNER-REVIEW-R1",
|
||||
"owner_repo": "railiance-platform",
|
||||
"reviewer": "platform-reader-owner delegated worker",
|
||||
"basis": [
|
||||
"INTENT.md shared stateful service custody, Q1 approval/delegated apply/revocation, stable delivery interface",
|
||||
"AGENTS.md credential-change owner review and founder-gated attended provisioning",
|
||||
"No own supervision record exists; supervised preparation only"
|
||||
],
|
||||
"disposition": "prepared_with_conditions_not_execution_approval",
|
||||
"scope": "Only platform custody/CCR/policy/bootstrap; other repo reviews not authored",
|
||||
"findings": [
|
||||
"Separate exact KV path and exact data-read ESO delta preserve writer-only caller boundary.",
|
||||
"No all-repo Forgejo team; preflight rejects different existing user/team, unexpected team repos/members, other teams/orgs/private grants, orphan PAT/KV, and unverified PAT/KV binding.",
|
||||
"Quiet create-only bootstrap with per-user lane lock, durable fsynced intents/outcomes, CAS=0 KV; uncertainty is reconciled before retry, never blindly reminted.",
|
||||
"Full consumer redirect/HTTPS plus authenticated URL allowlist image and exact config mandatory before credential delivery.",
|
||||
"Approval receipt is locally bound revision evidence, not authenticated authority; attended founder/platform lane remains authorization source."
|
||||
],
|
||||
"validation": {
|
||||
"targeted_tests": 17,
|
||||
"result": "passed",
|
||||
"ccr_validator": "passed with expected unconfirmed ESO binding/apply blocked warning",
|
||||
"diff_check": "passed"
|
||||
},
|
||||
"pending": [
|
||||
"Allocation recheck against owner primary before integration",
|
||||
"Independent Forgejo/apps/reuse owner reviews and actual founder credential provisioning decision",
|
||||
"Live ESO claims and namespace condition confirmation; no token read or live mutations performed",
|
||||
"Forgejo deployed API metadata contract support proof; sudo inventory and token_last_eight missing metadata fail closed",
|
||||
"Apps fixedimage digest/config/migration/dryrun/headroom rollout approval and receipt",
|
||||
"Native seven index+entry verification and final hosted70/70 audit"
|
||||
],
|
||||
"execution_approved": false,
|
||||
"live_changes_performed": false
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"id": "RPF-WP-0058-PLATFORM-OWNER-REVIEW-R2",
|
||||
"owner_repo": "railiance-platform",
|
||||
"reviewer": "platform-reader-owner delegated worker",
|
||||
"basis": [
|
||||
"INTENT.md shared stateful service custody, Q1 approval/delegated apply/revocation, stable delivery interface",
|
||||
"AGENTS.md credential-change owner review and founder-gated attended provisioning",
|
||||
"No own supervision record exists; supervised preparation only"
|
||||
],
|
||||
"disposition": "approved",
|
||||
"scope": "Platform custody PREPARATION artifact revision2 only; not founder credential/production authority or another repo review",
|
||||
"findings": [
|
||||
"Separate exact KV path and exact data-read ESO delta preserve writer-only caller boundary.",
|
||||
"No all-repo Forgejo team; preflight rejects different existing user/team, unexpected team repos/members, other teams/orgs/private grants, orphan PAT/KV, and unverified PAT/KV binding.",
|
||||
"Quiet create-only bootstrap with per-user lane lock, durable fsynced intents/outcomes, CAS=0 KV; uncertainty is reconciled before retry, never blindly reminted.",
|
||||
"Full consumer redirect/HTTPS plus authenticated URL allowlist image and exact config mandatory before credential delivery.",
|
||||
"Approval receipt is locally bound revision evidence, not authenticated authority; attended founder/platform lane remains authorization source.",
|
||||
"Forgejo version source disproves preliminary owned-only /user/repos claim; unchanged endpoint with id sort includes direct and team grants. R2 pages until observed empty, refuses missing/duplicate IDs and inconsistent total counts."
|
||||
],
|
||||
"validation": {
|
||||
"targeted_tests": 28,
|
||||
"result": "passed",
|
||||
"ccr_validator": "passed with expected unconfirmed ESO binding/apply blocked warning",
|
||||
"diff_check": "passed"
|
||||
},
|
||||
"pending": [
|
||||
"Allocation recheck against owner primary before integration",
|
||||
"Independent Forgejo/apps/reuse owner reviews and actual founder credential provisioning decision",
|
||||
"Live ESO claims and namespace condition confirmation; no token read or live mutations performed",
|
||||
"Forgejo deployed API metadata contract support proof; sudo inventory and token_last_eight missing metadata fail closed",
|
||||
"Apps fixedimage digest/config/migration/dryrun/headroom rollout approval and receipt",
|
||||
"Native seven index+entry verification and final hosted70/70 audit",
|
||||
"Separate explicit exact-token recovery approval and manual rotation due-date task/receipt before activation."
|
||||
],
|
||||
"execution_approved": false,
|
||||
"live_changes_performed": false,
|
||||
"revision": 2,
|
||||
"decision": "approved",
|
||||
"work_item": "RPF-WP-0058-T01",
|
||||
"reviewed_artifacts": {
|
||||
"credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml": "90522912fb78fed72e625382fb7114a0e9aafe76d59e36bffecab83c5bb7d643",
|
||||
"scripts/provision-reuse-surface-forgejo-source.py": "abf60a754596baa1610d23b1fd367385b0222ac8aeaebba0f2cfb7ec4c62055b",
|
||||
"scripts/revoke-reuse-surface-forgejo-source.py": "f78d1b26fbff93c6cc481a896d9fb9b9ce93049b8b3267fa41a64bc57dadb162",
|
||||
"tests/test_reuse_surface_forgejo_source_provision.py": "0d06fd22596a0f17ecd8dd5ff6a12bb1b1782258d00126ad468661099d7d298b",
|
||||
"tests/test_reuse_surface_forgejo_source_recovery.py": "08247129b220c198d8f7d1322ce63956203b9ffcaa0342e52990bb6520fd138d",
|
||||
"docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json": "2bb72671e0a5f230497ab8ef2967a20ee68d1f2cf081f07c84f6db3442e18d8b",
|
||||
"openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl": "6320f6c2d262d924ea16291d301d5f9eeb7efdb6ce7ea141c1a41d277d86b01b",
|
||||
"docs/proposals/2026-10-15-reuse-private-reader/README.md": "dcc8879d96b85b21b52c6cac9d7a58db78169ed903490ef11561bf8d6aa507e5",
|
||||
"workplans/RPF-WP-0058-reuse-private-source-reader.md": "9aeb0fbbb5359b7ba9afb4f5f7f2c55f84dade0d6d95fe98b15901e656aed5b5",
|
||||
"docs/proposals/2026-10-15-reuse-private-reader/apps-externalsecret-data-item.draft.yaml": "e582a7d2eeb68668858ec19a6a32b8b816a4850b5ad1757f7686330b141578b2"
|
||||
},
|
||||
"supersedes_review_artifact_sha256": "0a9746f65bb79982632accd9e20a2a632eafc5ee5410f4eddeadd625e73ca0bd",
|
||||
"refinement_of_same_proposal": "CCR-2026-0046 R1 confirmed capped-page bug retained; R2 fixes pagination, not a new trial"
|
||||
}
|
||||
|
|
@ -9,3 +9,8 @@ path "auth/token/lookup-self" {
|
|||
path "auth/token/revoke-self" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
|
||||
# CCR-2026-0046 proposed; exact extra data path, no metadata/list grants.
|
||||
path "platform/data/workloads/reuse/reuse-surface/forgejo-source-read" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
|
|
|||
383
scripts/provision-reuse-surface-forgejo-source.py
Executable file
383
scripts/provision-reuse-surface-forgejo-source.py
Executable file
|
|
@ -0,0 +1,383 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent attended CCR-2026-0046 bootstrap; never runs without exact approval.
|
||||
|
||||
Only this seven-repository identity/PAT/KV path is mutable. No policy, ESO,
|
||||
consumer rollout, or registration mutations occur here. A receipt is evidence,
|
||||
not authorization. Uncertain requests are recorded and reconciled, never retried
|
||||
in the same execution; orphan PAT drift requires attended recovery.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import argparse
|
||||
import base64
|
||||
import datetime as dt
|
||||
import fcntl
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import yaml
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CCR = 'CCR-2026-0046'
|
||||
CCR_FILE = 'credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml'
|
||||
PLAN_FILE = 'docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json'
|
||||
SCRIPT_FILE = 'scripts/provision-reuse-surface-forgejo-source.py'
|
||||
API = 'https://forgejo.coulomb.social/api/v1'
|
||||
USER = 'reuse-surface-source'
|
||||
TEAM = 'reuse-surface-source-readers'
|
||||
ORG = 'coulomb'
|
||||
KV = 'platform/workloads/reuse/reuse-surface/forgejo-source-read'
|
||||
FIELD = 'FORGEJO_SOURCE_TOKEN'
|
||||
PREFIX = 'reuse-surface-source-read-'
|
||||
REPOS = frozenset('rapp-openbao rapp-policy-nexus rapp-postgres rapp-sbom-nexus rapp-telemetry rapp-tenant-engine rapp-user-engine'.split())
|
||||
SCOPES = ['read:repository']
|
||||
UNITS = {'repo.code': 'read', **{x: 'none' for x in ('repo.actions', 'repo.packages', 'repo.issues', 'repo.ext_issues', 'repo.wiki', 'repo.pulls', 'repo.releases', 'repo.projects', 'repo.ext_wiki')}}
|
||||
|
||||
class Refused(Exception):
|
||||
"""Messages must be fixed non-secret codes, never provider text."""
|
||||
|
||||
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, *args, **kwargs):
|
||||
raise Refused('redirect_refused')
|
||||
|
||||
class Journal:
|
||||
def __init__(self, path):
|
||||
flags = os.O_RDWR | os.O_CREAT | os.O_APPEND | os.O_NOFOLLOW
|
||||
self.fd = os.open(path, flags, 0o600)
|
||||
if os.fstat(self.fd).st_mode & 0o077:
|
||||
os.close(self.fd)
|
||||
raise Refused('receipt_permissions')
|
||||
fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
self.attempt = secrets.token_hex(12)
|
||||
def put(self, event, **fields):
|
||||
# Only locally authored operation tags, scalar status/IDs are accepted.
|
||||
allowed = {'operation', 'status', 'http_status', 'token_id', 'team_id', 'outcome', 'error_code'}
|
||||
if set(fields) - allowed:
|
||||
raise Refused('receipt_fields')
|
||||
record = {'at': dt.datetime.now(dt.timezone.utc).isoformat(), 'ccr': CCR,
|
||||
'attempt': self.attempt, 'event': event, **fields}
|
||||
data = (json.dumps(record, sort_keys=True) + '\n').encode()
|
||||
while data:
|
||||
written = os.write(self.fd, data)
|
||||
if written <= 0:
|
||||
raise Refused('receipt_write_failed')
|
||||
data = data[written:]
|
||||
os.fsync(self.fd)
|
||||
def close(self):
|
||||
os.close(self.fd)
|
||||
|
||||
class Client:
|
||||
def __init__(self, admin, journal):
|
||||
self.admin, self.journal = admin, journal
|
||||
self.opener = urllib.request.build_opener(NoRedirect())
|
||||
def request(self, method, path, payload=None, expected=(200,), *, token=None, sudo=False, basic=None, operation=None):
|
||||
if not path.startswith('/') or '://' in path:
|
||||
raise Refused('invalid_api_path')
|
||||
headers = {'Authorization': basic or 'token ' + (self.admin if token is None else token), 'Accept': 'application/json'}
|
||||
if sudo:
|
||||
headers['Sudo'] = USER
|
||||
if payload is not None:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
if method != 'GET':
|
||||
if not operation:
|
||||
raise Refused('mutation_without_intent')
|
||||
self.journal.put('intent', operation=operation)
|
||||
req = urllib.request.Request(API + path, data=None if payload is None else json.dumps(payload).encode(), headers=headers, method=method)
|
||||
self.last_total_count = None
|
||||
try:
|
||||
with self.opener.open(req, timeout=30) as response:
|
||||
status, body = response.status, response.read(2_000_001)
|
||||
raw_total = response.headers.get('X-Total-Count')
|
||||
if raw_total is not None:
|
||||
if not re.fullmatch(r'[0-9]+', raw_total):
|
||||
raise Refused('invalid_total_count')
|
||||
self.last_total_count = int(raw_total)
|
||||
except urllib.error.HTTPError as error:
|
||||
status, body = error.code, b''
|
||||
except Exception:
|
||||
if operation:
|
||||
self.journal.put('outcome', operation=operation, status='ambiguous')
|
||||
raise Refused('request_observation_failed') from None
|
||||
if operation:
|
||||
self.journal.put('outcome', operation=operation, status='observed', http_status=status)
|
||||
if status not in expected:
|
||||
raise Refused('unexpected_http_status')
|
||||
if len(body) > 2_000_000:
|
||||
raise Refused('response_bound_exceeded')
|
||||
if not body:
|
||||
return status, None
|
||||
try:
|
||||
return status, json.loads(body)
|
||||
except Exception:
|
||||
raise Refused('invalid_json') from None
|
||||
def pages(self, path, *, sudo=False):
|
||||
"""Page until observed empty; requested size may be server-clamped.
|
||||
|
||||
Forgejo11.0.3 default MaxResponseItems=50, so a response shorter than
|
||||
our requested100 proves nothing. Detect unstable/duplicate IDs and
|
||||
reconcile X-Total-Count whenever available; exceeding bound refuses.
|
||||
"""
|
||||
items, seen, expected_total = [], set(), None
|
||||
for page in range(1, 21):
|
||||
separator = '&' if '?' in path else '?'
|
||||
_, batch = self.request('GET', path + separator + f'limit=100&page={page}', sudo=sudo)
|
||||
if not isinstance(batch, list) or any(not isinstance(x, dict) or type(x.get('id')) is not int or x['id'] <= 0 for x in batch):
|
||||
raise Refused('invalid_list')
|
||||
observed_total = getattr(self, 'last_total_count', None)
|
||||
if observed_total is not None:
|
||||
if expected_total is not None and observed_total != expected_total:
|
||||
raise Refused('unstable_total_count')
|
||||
expected_total = observed_total
|
||||
if not batch:
|
||||
if expected_total is not None and len(items) != expected_total:
|
||||
raise Refused('total_count_mismatch')
|
||||
return items
|
||||
for item in batch:
|
||||
if item['id'] in seen:
|
||||
raise Refused('duplicate_page_id')
|
||||
seen.add(item['id'])
|
||||
items.extend(batch)
|
||||
if expected_total is not None and len(items) > expected_total:
|
||||
raise Refused('total_count_mismatch')
|
||||
raise Refused('pagination_bound_exceeded')
|
||||
|
||||
class Bao:
|
||||
@staticmethod
|
||||
def run(args, stdin=None):
|
||||
try:
|
||||
return subprocess.run(['bao', *args], input=stdin, capture_output=True, timeout=60, check=False, cwd=ROOT)
|
||||
except Exception:
|
||||
raise Refused('bao_observation_failed') from None
|
||||
def get(self):
|
||||
result = self.run(['kv', 'get', '-format=json', KV])
|
||||
if result.returncode != 0:
|
||||
# Distinguish an actual not-found response from denied/unavailable.
|
||||
# Never include captured raw errors in any evidence.
|
||||
if re.search(rb'Code:\s*404\b', result.stderr or b'') or b'No value found at ' in (result.stderr or b''):
|
||||
return None
|
||||
raise Refused('bao_preflight_failed')
|
||||
try:
|
||||
data = json.loads(result.stdout)['data']['data']
|
||||
except Exception:
|
||||
raise Refused('bao_invalid_record') from None
|
||||
if set(data) != {FIELD, 'API_USER', 'API_BASE_URL', 'TOKEN_SCOPES', 'GENERATED_AT', 'CCR_ID'} or data['API_USER'] != USER or data['API_BASE_URL'] != 'https://forgejo.coulomb.social' or data['TOKEN_SCOPES'] != 'read:repository' or data['CCR_ID'] != CCR or not isinstance(data[FIELD], str) or not data[FIELD]:
|
||||
raise Refused('bao_record_conflict')
|
||||
return data
|
||||
def put(self, token, journal):
|
||||
journal.put('intent', operation='kv_create')
|
||||
timestamp = dt.datetime.now(dt.timezone.utc).isoformat()
|
||||
try:
|
||||
result = self.run(['kv', 'put', '-cas=0', KV, FIELD + '=-', 'API_USER=' + USER,
|
||||
'API_BASE_URL=https://forgejo.coulomb.social', 'TOKEN_SCOPES=read:repository',
|
||||
'GENERATED_AT=' + timestamp, 'CCR_ID=' + CCR], token.encode())
|
||||
except Refused:
|
||||
journal.put('outcome', operation='kv_create', status='ambiguous')
|
||||
raise
|
||||
journal.put('outcome', operation='kv_create', status='observed' if result.returncode == 0 else 'ambiguous')
|
||||
# Readback reconciles even a response failure after successful write.
|
||||
stored = self.get()
|
||||
if stored is None or not secrets.compare_digest(stored[FIELD], token):
|
||||
raise Refused('bao_write_requires_reconciliation')
|
||||
journal.put('verified', operation='kv_create', status='matched')
|
||||
|
||||
|
||||
def valid_user(user):
|
||||
return isinstance(user, dict) and user.get('login') == USER and user.get('restricted') is True and user.get('is_admin') is False and user.get('visibility') == 'private'
|
||||
|
||||
def valid_team(team):
|
||||
return isinstance(team, dict) and isinstance(team.get('id'), int) and team.get('name') == TEAM and team.get('permission') == 'read' and team.get('includes_all_repositories') is False and team.get('can_create_org_repo') is False and team.get('units_map') == UNITS
|
||||
|
||||
def repo_names(items):
|
||||
names = set()
|
||||
for repo in items:
|
||||
if repo.get('owner', {}).get('login') != ORG or repo.get('name') not in REPOS:
|
||||
raise Refused('unexpected_team_repository')
|
||||
names.add(repo['name'])
|
||||
if len(names) != len(items):
|
||||
raise Refused('duplicate_repository')
|
||||
return names
|
||||
|
||||
def inspect(client, bao):
|
||||
"""Read-only preflight before mutations.
|
||||
|
||||
Forgejo11.0.3 ListMyRepos uses OwnerID plus Collaborate=None; model
|
||||
ValueOrDefault(true) includes direct access and team grants (not only owned).
|
||||
Deployed-server API compatibility and live scope proof remain separate gates.
|
||||
"""
|
||||
for repo in sorted(REPOS):
|
||||
_, meta = client.request('GET', f'/repos/{ORG}/{repo}')
|
||||
if not isinstance(meta, dict) or meta.get('private') is not True or meta.get('full_name') != ORG + '/' + repo:
|
||||
raise Refused('source_repository_conflict')
|
||||
_, probe = client.request('GET', '/repos/coulomb/rapp-core-hub')
|
||||
if not isinstance(probe, dict) or probe.get('private') is not True:
|
||||
raise Refused('negative_probe_not_private')
|
||||
_, user = client.request('GET', '/users/' + USER, expected=(200, 404))
|
||||
if user is not None and not valid_user(user):
|
||||
raise Refused('user_conflict')
|
||||
teams = [x for x in client.pages(f'/orgs/{ORG}/teams') if x.get('name') == TEAM]
|
||||
if len(teams) > 1:
|
||||
raise Refused('duplicate_team')
|
||||
team = teams[0] if teams else None
|
||||
members, repos = [], set()
|
||||
if team is not None:
|
||||
if not valid_team(team):
|
||||
raise Refused('team_conflict')
|
||||
members = client.pages('/teams/' + str(team['id']) + '/members')
|
||||
if len(members) > 1 or any(x.get('login') != USER for x in members):
|
||||
raise Refused('team_members_conflict')
|
||||
repos = repo_names(client.pages('/teams/' + str(team['id']) + '/repos'))
|
||||
tokens = []
|
||||
if user is not None:
|
||||
orgs = client.pages('/user/orgs', sudo=True)
|
||||
if any(x.get('username') != ORG for x in orgs):
|
||||
raise Refused('extra_organization_affiliation')
|
||||
grants = client.pages('/user/teams', sudo=True)
|
||||
if any(x.get('name') != TEAM or x.get('organization', {}).get('username') != ORG for x in grants):
|
||||
raise Refused('extra_team_grant')
|
||||
for meta in client.pages('/user/repos?order_by=id', sudo=True):
|
||||
if meta.get('private') is True and meta.get('full_name') not in {ORG + '/' + x for x in REPOS}:
|
||||
raise Refused('extra_private_repository_grant')
|
||||
if meta.get('owner', {}).get('login') == USER:
|
||||
raise Refused('service_user_owned_repository')
|
||||
tokens = client.pages('/users/' + USER + '/tokens')
|
||||
if len(tokens) > 1 or any(not str(x.get('name', '')).startswith(PREFIX) or x.get('scopes') != SCOPES or not isinstance(x.get('id'), int) for x in tokens):
|
||||
raise Refused('token_conflict')
|
||||
stored = bao.get()
|
||||
if bool(tokens) != bool(stored):
|
||||
raise Refused('orphan_token_or_kv_requires_recovery')
|
||||
if tokens and (not isinstance(tokens[0].get('token_last_eight'), str) or len(tokens[0]['token_last_eight']) != 8 or not secrets.compare_digest(tokens[0]['token_last_eight'], stored[FIELD][-8:])):
|
||||
raise Refused('pat_kv_binding_unverifiable')
|
||||
if tokens and (not team or repos != REPOS or len(members) != 1):
|
||||
raise Refused('active_lane_grant_drift')
|
||||
return user, team, repos, members, tokens, stored
|
||||
|
||||
def verify(client, source_token, plan):
|
||||
for source in plan['sources']:
|
||||
_, repo = client.request('GET', '/repos/' + ORG + '/' + source['repo'], token=source_token)
|
||||
if repo.get('permissions') != {'admin': False, 'push': False, 'pull': True}:
|
||||
raise Refused('effective_permission_not_read')
|
||||
# Exact consented bytes fetched quietly; raw output is never retained.
|
||||
path = f"/repos/{ORG}/{source['repo']}/contents/registry/indexes/capabilities.yaml?ref={source['consented_commit']}"
|
||||
_, content = client.request('GET', path, token=source_token)
|
||||
try:
|
||||
raw = base64.b64decode(content['content'], validate=False)
|
||||
except Exception:
|
||||
raise Refused('invalid_index_encoding') from None
|
||||
if hashlib.sha256(raw).hexdigest() != source['index_sha256']:
|
||||
raise Refused('index_digest_mismatch')
|
||||
client.request('GET', f"/repos/{ORG}/{source['repo']}/actions/secrets?limit=1", token=source_token, expected=(403, 404))
|
||||
client.request('GET', '/admin/users', token=source_token, expected=(403, 404))
|
||||
# A known private excluded source is selected by reviewed contract.
|
||||
probe = plan['negative_private_probe']
|
||||
_, meta = client.request('GET', '/repos/' + ORG + '/' + probe)
|
||||
if not isinstance(meta, dict) or meta.get('private') is not True:
|
||||
raise Refused('negative_probe_not_private')
|
||||
client.request('GET', '/repos/' + ORG + '/' + probe, token=source_token, expected=(403, 404))
|
||||
client.journal.put('verified', operation='reader_scope', status='seven_exact_no_extra_private_grants')
|
||||
|
||||
def provision(client, bao, plan):
|
||||
user, team, repos, members, tokens, stored = inspect(client, bao)
|
||||
if tokens:
|
||||
verify(client, stored[FIELD], plan)
|
||||
client.journal.put('terminal', outcome='verified_noop')
|
||||
return 'noop'
|
||||
if user is None:
|
||||
_, user = client.request('POST', '/admin/users', {'username': USER, 'email': USER + '@coulomb.social', 'full_name': 'Reuse Surface seven-source reader', 'password': secrets.token_urlsafe(48), 'restricted': True, 'must_change_password': False, 'send_notify': False, 'visibility': 'private'}, expected=(201,), operation='user_create')
|
||||
if not valid_user(user):
|
||||
raise Refused('created_user_contract_failed')
|
||||
if team is None:
|
||||
_, team = client.request('POST', f'/orgs/{ORG}/teams', {'name': TEAM, 'description': CCR + ' exact seven consented source repos', 'permission': 'read', 'includes_all_repositories': False, 'can_create_org_repo': False, 'units': ['repo.code'], 'units_map': UNITS}, expected=(201,), operation='team_create')
|
||||
if not valid_team(team):
|
||||
raise Refused('created_team_contract_failed')
|
||||
team_id = team['id']
|
||||
for repo in sorted(REPOS - repos):
|
||||
client.request('PUT', f'/teams/{team_id}/repos/{ORG}/{repo}', expected=(204,), operation='team_repo_' + repo)
|
||||
if not members:
|
||||
client.request('PUT', f'/teams/{team_id}/members/{USER}', expected=(204,), operation='team_member_add')
|
||||
# Recheck complete grants before creating any PAT; refuse implicit grants.
|
||||
_, _, actual_repos, actual_members, _, _ = inspect(client, bao)
|
||||
if actual_repos != REPOS or len(actual_members) != 1:
|
||||
raise Refused('incomplete_team_scope')
|
||||
password = secrets.token_urlsafe(48)
|
||||
client.request('PATCH', '/admin/users/' + USER, {'password': password, 'restricted': True, 'admin': False, 'allow_create_organization': False, 'allow_git_hook': False, 'allow_import_local': False, 'max_repo_creation': 0, 'must_change_password': False, 'active': True, 'visibility': 'private'}, operation='service_password_randomize')
|
||||
basic = 'Basic ' + base64.b64encode((USER + ':' + password).encode()).decode()
|
||||
_, record = client.request('POST', '/users/' + USER + '/tokens', {'name': PREFIX + client.journal.attempt, 'scopes': SCOPES}, expected=(201,), basic=basic, operation='pat_create')
|
||||
if not isinstance(record, dict) or not isinstance(record.get('id'), int) or record.get('scopes') != SCOPES or not isinstance(record.get('sha1'), str) or not record['sha1']:
|
||||
raise Refused('created_pat_requires_recovery')
|
||||
client.journal.put('identified', operation='pat_create', token_id=record['id'], team_id=team_id)
|
||||
token = record['sha1']
|
||||
verify(client, token, plan)
|
||||
bao.put(token, client.journal)
|
||||
# Repeat complete grant inventory after custody; verify exactly one PAT.
|
||||
inspect(client, bao)
|
||||
client.journal.put('terminal', outcome='provisioned_verified_delivery_pending')
|
||||
return 'minted'
|
||||
|
||||
def approval(path):
|
||||
try:
|
||||
receipt = json.loads(Path(path).read_text())
|
||||
ccr = yaml.safe_load((ROOT / CCR_FILE).read_text())
|
||||
if receipt['ccr_id'] != CCR or receipt['decision'] != 'approved' or not receipt['supervisor'] or not receipt['decision_ref'] or ccr['status'] != 'approved':
|
||||
raise Refused('approval_missing')
|
||||
files = {CCR_FILE, PLAN_FILE, SCRIPT_FILE, 'openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl'}
|
||||
if set(receipt['approved_artifacts']) != files:
|
||||
raise Refused('approval_artifact_set')
|
||||
for rel in files:
|
||||
if hashlib.sha256((ROOT / rel).read_bytes()).hexdigest() != receipt['approved_artifacts'][rel]:
|
||||
raise Refused('approval_revision_mismatch')
|
||||
plan = json.loads((ROOT / PLAN_FILE).read_text())
|
||||
if {x['repo'] for x in plan['sources']} != REPOS or len(plan['sources']) != 7 or plan['negative_private_probe'] != 'rapp-core-hub' or any(not re.fullmatch(r'[a-f0-9]{40}', x['consented_commit']) or not re.fullmatch(r'[a-f0-9]{64}', x['index_sha256']) for x in plan['sources']):
|
||||
raise Refused('plan_scope_conflict')
|
||||
return plan
|
||||
except Refused:
|
||||
raise
|
||||
except Exception:
|
||||
raise Refused('approval_invalid') from None
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--approval', required=True)
|
||||
parser.add_argument('--receipt', required=True)
|
||||
parser.add_argument('--inner', action='store_true', help=argparse.SUPPRESS)
|
||||
args = parser.parse_args(argv)
|
||||
journal = None
|
||||
lane_lock = None
|
||||
try:
|
||||
plan = approval(args.approval)
|
||||
if not args.inner:
|
||||
# Governed OpenBao attended lane surrounds this command. Warden
|
||||
# supplies only Forgejo API_TOKEN to inner child, not via argv/files.
|
||||
result = subprocess.run(['warden', 'access', 'forgejo-admin-api-token', '--field', 'API_TOKEN', '--exec', '--', sys.executable, str(ROOT / SCRIPT_FILE), '--inner', '--approval', str(Path(args.approval).resolve()), '--receipt', str(Path(args.receipt).resolve())], capture_output=True, timeout=900, check=False, cwd=ROOT)
|
||||
return 0 if result.returncode == 0 else 1
|
||||
lane_lock = os.open('/tmp/' + CCR + '-reuse-reader-' + str(os.getuid()) + '.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
|
||||
if os.fstat(lane_lock).st_uid != os.getuid() or os.fstat(lane_lock).st_mode & 0o077:
|
||||
raise Refused('lane_lock_owner_or_permissions')
|
||||
fcntl.flock(lane_lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
journal = Journal(args.receipt)
|
||||
admin = os.environ.get('API_TOKEN')
|
||||
if not admin:
|
||||
raise Refused('admin_route_missing')
|
||||
provision(Client(admin, journal), Bao(), plan)
|
||||
return 0
|
||||
except Exception as error:
|
||||
if journal:
|
||||
journal.put('terminal', outcome='stopped_reconcile_before_retry', error_code=str(error) if isinstance(error, Refused) else 'internal_failure')
|
||||
return 1
|
||||
finally:
|
||||
if journal:
|
||||
journal.close()
|
||||
if lane_lock is not None:
|
||||
os.close(lane_lock)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
result = main()
|
||||
except Exception:
|
||||
result = 1
|
||||
raise SystemExit(result)
|
||||
109
scripts/revoke-reuse-surface-forgejo-source.py
Executable file
109
scripts/revoke-reuse-surface-forgejo-source.py
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Silent separately approved exact-PAT revocation; preserve KV and delivery.
|
||||
|
||||
Forgejo11.0.3 DELETE token requires Basic auth. Reset only the dedicated user
|
||||
password to process-memory random bytes, revoke the reviewed token and reconcile
|
||||
inventory absence. Never remint, rotate/write KV, or touch another identity.
|
||||
"""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
SCRIPT = 'scripts/revoke-reuse-surface-forgejo-source.py'
|
||||
spec = importlib.util.spec_from_file_location('reuse_reader_bootstrap', ROOT / 'scripts/provision-reuse-surface-forgejo-source.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
|
||||
def approved_target(path):
|
||||
try:
|
||||
record = json.loads(Path(path).read_text())
|
||||
expected_files = {SCRIPT, p.SCRIPT_FILE, p.CCR_FILE, p.PLAN_FILE}
|
||||
if record['ccr_id'] != p.CCR or record['decision'] != 'approved' or not record['supervisor'] or not record['decision_ref'] or record['action'] != 'revoke_exact_pat' or set(record['approved_artifacts']) != expected_files:
|
||||
raise p.Refused('recovery_approval_invalid')
|
||||
for rel in expected_files:
|
||||
if hashlib.sha256((ROOT / rel).read_bytes()).hexdigest() != record['approved_artifacts'][rel]:
|
||||
raise p.Refused('recovery_approved_revision_mismatch')
|
||||
target = record['target']
|
||||
if set(target) != {'id', 'name'} or type(target['id']) is not int or target['id'] <= 0 or not isinstance(target['name'], str) or not target['name'].startswith(p.PREFIX) or len(target['name']) > 100:
|
||||
raise p.Refused('recovery_target_invalid')
|
||||
return target
|
||||
except p.Refused:
|
||||
raise
|
||||
except Exception:
|
||||
raise p.Refused('recovery_approval_invalid') from None
|
||||
|
||||
|
||||
def recover(client, target):
|
||||
_, user = client.request('GET', '/users/' + p.USER)
|
||||
if not p.valid_user(user):
|
||||
raise p.Refused('recovery_user_conflict')
|
||||
tokens = client.pages('/users/' + p.USER + '/tokens')
|
||||
if not tokens:
|
||||
# Absence reconciles a prior ambiguous revocation without new writes.
|
||||
client.journal.put('terminal', outcome='revoke_verified_absent_noop')
|
||||
return
|
||||
if len(tokens) != 1 or tokens[0].get('id') != target['id'] or tokens[0].get('name') != target['name'] or tokens[0].get('scopes') != p.SCOPES:
|
||||
raise p.Refused('recovery_token_inventory_conflict')
|
||||
password = secrets.token_urlsafe(48)
|
||||
client.request('PATCH', '/admin/users/' + p.USER, {'password': password, 'must_change_password': False}, operation='recovery_password_randomize')
|
||||
basic = 'Basic ' + base64.b64encode((p.USER + ':' + password).encode()).decode()
|
||||
try:
|
||||
client.request('DELETE', '/users/' + p.USER + '/tokens/' + str(target['id']), expected=(204, 404), basic=basic, operation='exact_pat_revoke')
|
||||
except p.Refused:
|
||||
# Observation failure does not mean DELETE did not apply. Read-only
|
||||
# reconciliation follows; never issue another DELETE in this execution.
|
||||
pass
|
||||
remaining = client.pages('/users/' + p.USER + '/tokens')
|
||||
if remaining:
|
||||
raise p.Refused('revoke_unverified_stop')
|
||||
client.journal.put('verified', operation='exact_pat_revoke', token_id=target['id'], status='inventory_absent')
|
||||
client.journal.put('terminal', outcome='revoked_verified_kv_preserved')
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--approval', required=True)
|
||||
parser.add_argument('--receipt', required=True)
|
||||
parser.add_argument('--inner', action='store_true', help=argparse.SUPPRESS)
|
||||
args = parser.parse_args(argv)
|
||||
journal, lock = None, None
|
||||
try:
|
||||
target = approved_target(args.approval)
|
||||
if not args.inner:
|
||||
result = subprocess.run(['warden', 'access', 'forgejo-admin-api-token', '--field', 'API_TOKEN', '--exec', '--', sys.executable, str(ROOT / SCRIPT), '--inner', '--approval', str(Path(args.approval).resolve()), '--receipt', str(Path(args.receipt).resolve())], capture_output=True, check=False, timeout=900, cwd=ROOT)
|
||||
return 0 if result.returncode == 0 else 1
|
||||
lock = os.open('/tmp/' + p.CCR + '-reuse-reader-' + str(os.getuid()) + '.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600)
|
||||
if os.fstat(lock).st_uid != os.getuid() or os.fstat(lock).st_mode & 0o077:
|
||||
raise p.Refused('lane_lock_owner_or_permissions')
|
||||
p.fcntl.flock(lock, p.fcntl.LOCK_EX | p.fcntl.LOCK_NB)
|
||||
journal = p.Journal(args.receipt)
|
||||
admin = os.environ.get('API_TOKEN')
|
||||
if not admin:
|
||||
raise p.Refused('admin_route_missing')
|
||||
recover(p.Client(admin, journal), target)
|
||||
return 0
|
||||
except Exception as error:
|
||||
if journal:
|
||||
journal.put('terminal', outcome='recovery_stopped_reconcile_before_retry', error_code=str(error) if isinstance(error, p.Refused) else 'internal_failure')
|
||||
return 1
|
||||
finally:
|
||||
if journal:
|
||||
journal.close()
|
||||
if lock is not None:
|
||||
os.close(lock)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
result = main()
|
||||
except Exception:
|
||||
result = 1
|
||||
raise SystemExit(result)
|
||||
201
tests/test_reuse_surface_forgejo_source_provision.py
Normal file
201
tests/test_reuse_surface_forgejo_source_provision.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
"""Meaningful fail-closed tests; no credentials or network are used."""
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import hashlib
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
from contextlib import redirect_stdout, redirect_stderr
|
||||
from unittest.mock import patch
|
||||
|
||||
spec = importlib.util.spec_from_file_location('reuse_provision', Path(__file__).parents[1] / 'scripts/provision-reuse-surface-forgejo-source.py')
|
||||
p = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(p)
|
||||
|
||||
class Journal:
|
||||
attempt = 'mock-attempt'
|
||||
def __init__(self): self.rows = []
|
||||
def put(self, event, **kw): self.rows.append((event, kw))
|
||||
|
||||
class Fake:
|
||||
def __init__(self):
|
||||
self.journal = Journal()
|
||||
self.user = None
|
||||
self.team = None
|
||||
self.members = []
|
||||
self.repos = []
|
||||
self.tokens = []
|
||||
self.extra = []
|
||||
self.mutations = []
|
||||
def pages(self, path, sudo=False):
|
||||
if path == '/orgs/coulomb/teams': return [] if self.team is None else [self.team]
|
||||
if path.endswith('/members'): return self.members
|
||||
if path.endswith('/repos') and path.startswith('/teams/'): return self.repos
|
||||
if path == '/user/orgs': return [] if not self.members else [{'username':'coulomb'}]
|
||||
if path == '/user/teams': return [] if not self.members else [{**self.team,'organization':{'username':'coulomb'}}]
|
||||
if path == '/user/repos?order_by=id': return self.repos + self.extra
|
||||
if path.endswith('/tokens'): return self.tokens
|
||||
raise AssertionError(path)
|
||||
def request(self, method, path, payload=None, expected=(200,), **kwargs):
|
||||
if method != 'GET':
|
||||
self.mutations.append((method,path))
|
||||
raise AssertionError('mutation must not occur in preflight failure test')
|
||||
if path == '/users/' + p.USER: return (404,None) if self.user is None else (200,self.user)
|
||||
if path.startswith('/repos/'): return 200, {'private':True,'full_name':'coulomb/'+path.split('/')[3]}
|
||||
raise AssertionError(path)
|
||||
|
||||
class Bao:
|
||||
def __init__(self, data=None): self.data = data
|
||||
def get(self): return self.data
|
||||
|
||||
class Tests(unittest.TestCase):
|
||||
def complete(self):
|
||||
c = Fake()
|
||||
c.user = {'login':p.USER,'restricted':True,'is_admin':False,'visibility':'private'}
|
||||
c.team = {'id':10,'name':p.TEAM,'permission':'read','includes_all_repositories':False,'can_create_org_repo':False,'units_map':p.UNITS}
|
||||
c.members = [{'login':p.USER}]
|
||||
c.repos = [{'name':x,'owner':{'login':p.ORG},'private':True,'full_name':p.ORG+'/'+x} for x in sorted(p.REPOS)]
|
||||
return c
|
||||
def test_server_capped_pages_continue_until_empty(self):
|
||||
c=p.Client('mock-admin',Journal()); pages={1:[{'id':x} for x in range(1,51)],2:[{'id':51}],3:[]}
|
||||
def get(method,path,**kwargs):
|
||||
c.last_total_count=51
|
||||
page=int(path.split('page=')[1]); return 200,pages[page]
|
||||
with patch.object(c,'request',side_effect=get) as request:
|
||||
self.assertEqual(len(c.pages('/user/repos?order_by=id',sudo=True)),51)
|
||||
self.assertEqual(request.call_count,3)
|
||||
def test_late_collaborator_private_grant_zero_mutations(self):
|
||||
c=self.complete()
|
||||
original_pages=c.pages
|
||||
def pages(path,sudo=False):
|
||||
if path.startswith('/user/repos'):
|
||||
return p.Client.pages(c,path,sudo=sudo)
|
||||
return original_pages(path,sudo=sudo)
|
||||
original_request=c.request
|
||||
def request(method,path,**kwargs):
|
||||
if path.startswith('/user/repos?'):
|
||||
page=int(path.split('page=')[1])
|
||||
if page==1: return 200,[{'id':x,'private':False,'full_name':'someone/public-'+str(x),'owner':{'login':'someone'}} for x in range(1,51)]
|
||||
if page==2: return 200,[{'id':51,'private':True,'full_name':'other-org/excluded-collaborator','owner':{'login':'other-org'}}]
|
||||
return 200,[]
|
||||
return original_request(method,path,**kwargs)
|
||||
c.pages=pages; c.request=request
|
||||
with self.assertRaisesRegex(p.Refused,'extra_private_repository_grant'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_duplicate_page_ids_refuse(self):
|
||||
c=p.Client('mock-admin',Journal())
|
||||
with patch.object(c,'request',return_value=(200,[{'id':1}])):
|
||||
with self.assertRaisesRegex(p.Refused,'duplicate_page_id'): c.pages('/user/repos')
|
||||
def test_total_count_mismatch_refuses(self):
|
||||
c=p.Client('mock-admin',Journal())
|
||||
def request(method,path,**kwargs):
|
||||
c.last_total_count=2
|
||||
return 200,[{'id':1}] if 'page=1' in path else []
|
||||
with patch.object(c,'request',side_effect=request):
|
||||
with self.assertRaisesRegex(p.Refused,'total_count_mismatch'): c.pages('/user/repos')
|
||||
def test_unstable_total_count_refuses(self):
|
||||
c=p.Client('mock-admin',Journal())
|
||||
def request(method,path,**kwargs):
|
||||
page=int(path.split('page=')[1]); c.last_total_count=2 if page==1 else 3
|
||||
return 200,[{'id':page}]
|
||||
with patch.object(c,'request',side_effect=request):
|
||||
with self.assertRaisesRegex(p.Refused,'unstable_total_count'): c.pages('/user/repos')
|
||||
def test_excess_pagination_refuses(self):
|
||||
c=p.Client('mock-admin',Journal())
|
||||
def request(method,path,**kwargs):
|
||||
return 200,[{'id':int(path.split('page=')[1])}]
|
||||
with patch.object(c,'request',side_effect=request):
|
||||
with self.assertRaisesRegex(p.Refused,'pagination_bound_exceeded'): c.pages('/user/repos')
|
||||
def test_existing_all_repository_team_refused_before_mutation(self):
|
||||
c=self.complete(); c.team['includes_all_repositories']=True
|
||||
with self.assertRaisesRegex(p.Refused,'team_conflict'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_existing_excluded_team_repo_refused_before_mutation(self):
|
||||
c=self.complete(); c.repos.append({'name':'rapp-core-hub','owner':{'login':'coulomb'}})
|
||||
with self.assertRaisesRegex(p.Refused,'unexpected_team_repository'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_existing_admin_user_refused_before_mutation(self):
|
||||
c=self.complete(); c.user['is_admin']=True
|
||||
with self.assertRaisesRegex(p.Refused,'user_conflict'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_existing_token_kv_binding_mismatch_refused(self):
|
||||
c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'different'}]
|
||||
with self.assertRaisesRegex(p.Refused,'pat_kv_binding_unverifiable'): p.provision(c,Bao({p.FIELD:'mock-token'}),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_extra_private_grants_refused(self):
|
||||
c=self.complete(); c.extra=[{'private':True,'full_name':'coulomb/secret-outside'}]
|
||||
with self.assertRaisesRegex(p.Refused,'extra_private'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_orphan_pat_not_reminted(self):
|
||||
c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'ck-token'}]
|
||||
with self.assertRaisesRegex(p.Refused,'orphan'): p.provision(c,Bao(),{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_verified_noop_rechecks_secret_and_scope(self):
|
||||
c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'ck-token'}]
|
||||
with patch.object(p,'verify') as verify:
|
||||
self.assertEqual(p.provision(c,Bao({p.FIELD:'mock-token'}),{}),'noop')
|
||||
verify.assert_called_once_with(c,'mock-token',{})
|
||||
self.assertEqual(c.mutations,[])
|
||||
self.assertEqual(c.journal.rows[-1],('terminal',{'outcome':'verified_noop'}))
|
||||
def test_existing_write_unit_refused(self):
|
||||
c=self.complete(); c.team['units_map']={**p.UNITS,'repo.code':'write'}
|
||||
with self.assertRaisesRegex(p.Refused,'team_conflict'): p.inspect(c,Bao())
|
||||
def test_api_ambiguous_mutation_records_intent_without_raw_error(self):
|
||||
j=Journal(); c=p.Client('fake-secret-admin',j)
|
||||
with patch.object(c.opener,'open',side_effect=RuntimeError('fake-secret-admin sensitive provider body')):
|
||||
with self.assertRaisesRegex(p.Refused,'request_observation_failed'):
|
||||
c.request('POST','/admin/users',{'password':'fake-secret-password'},operation='user_create')
|
||||
self.assertEqual([x[0] for x in j.rows],['intent','outcome'])
|
||||
self.assertNotIn('secret',json.dumps(j.rows))
|
||||
self.assertEqual(j.rows[-1][1]['status'],'ambiguous')
|
||||
def test_network_error_never_reaches_stdout_stderr(self):
|
||||
out,err=io.StringIO(),io.StringIO()
|
||||
with patch.object(p,'approval',return_value={}), patch.object(p,'Journal',return_value=Journal()) as factory, patch.dict(p.os.environ,{'API_TOKEN':'fake-secret-admin'}), patch.object(p,'provision',side_effect=RuntimeError('fake-secret-admin body')):
|
||||
factory.return_value.close=lambda:None
|
||||
with redirect_stdout(out),redirect_stderr(err):
|
||||
code=p.main(['--inner','--approval','mock','--receipt','mock'])
|
||||
self.assertEqual(code,1); self.assertEqual(out.getvalue()+err.getvalue(),'')
|
||||
self.assertNotIn('secret',json.dumps(factory.return_value.rows))
|
||||
def test_unapproved_ccr_refuses_before_warden(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
f=Path(tmp)/'approval.json'; f.write_text(json.dumps({'ccr_id':p.CCR,'decision':'approved','supervisor':'mock','decision_ref':'mock'}))
|
||||
with patch.object(p.subprocess,'run') as run:
|
||||
self.assertEqual(p.main(['--approval',str(f),'--receipt',str(Path(tmp)/'receipt')]),1)
|
||||
run.assert_not_called()
|
||||
def test_approved_artifact_revision_mismatch_before_warden(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root=Path(tmp); files={p.CCR_FILE,p.PLAN_FILE,p.SCRIPT_FILE,'openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl'}
|
||||
for rel in files:
|
||||
f=root/rel; f.parent.mkdir(parents=True,exist_ok=True); f.write_text('mock')
|
||||
(root/p.CCR_FILE).write_text(yaml.safe_dump({'status':'approved'}))
|
||||
receipt={'ccr_id':p.CCR,'decision':'approved','supervisor':'founder','decision_ref':'mock-resolved-decision','approved_artifacts':{rel:hashlib.sha256((root/rel).read_bytes()).hexdigest() for rel in files}}
|
||||
f=root/'approval.json'; f.write_text(json.dumps(receipt)); (root/p.SCRIPT_FILE).write_text('modified-after-approval')
|
||||
with patch.object(p,'ROOT',root),patch.object(p.subprocess,'run') as run:
|
||||
self.assertEqual(p.main(['--approval',str(f),'--receipt',str(root/'receipt')]),1)
|
||||
run.assert_not_called()
|
||||
def test_journal_symlink_refused(self):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
f=Path(tmp)/'real'; f.write_text(''); link=Path(tmp)/'link'; link.symlink_to(f)
|
||||
with self.assertRaises(OSError): p.Journal(link)
|
||||
def test_redirects_never_follow_auth_header(self):
|
||||
with self.assertRaisesRegex(p.Refused,'redirect_refused'):
|
||||
p.NoRedirect().redirect_request(None,None,302,'',{ },'https://other.example')
|
||||
def test_bao_cas_write_uncertain_response_reconciled(self):
|
||||
b=p.Bao(); j=Journal()
|
||||
with patch.object(b,'run',return_value=type('R',(),{'returncode':1})()) as run, patch.object(b,'get',return_value={p.FIELD:'fake-source'}):
|
||||
b.put('fake-source',j)
|
||||
self.assertIn('-cas=0',run.call_args.args[0]); self.assertEqual(run.call_args.args[1],b'fake-source')
|
||||
self.assertEqual(j.rows[-1],('verified',{'operation':'kv_create','status':'matched'}))
|
||||
def test_bao_timeout_preserves_ambiguous_outcome(self):
|
||||
b=p.Bao(); j=Journal()
|
||||
with patch.object(b,'run',side_effect=p.Refused('bao_observation_failed')):
|
||||
with self.assertRaises(p.Refused): b.put('fake-source',j)
|
||||
self.assertEqual(j.rows[-1],('outcome',{'operation':'kv_create','status':'ambiguous'}))
|
||||
def test_kv_denial_not_treated_as_absence(self):
|
||||
b=p.Bao()
|
||||
with patch.object(b,'run',return_value=type('R',(),{'returncode':1,'stderr':b'Code: 403 Error: fake-secret'})()):
|
||||
with self.assertRaisesRegex(p.Refused,'bao_preflight_failed'): b.get()
|
||||
|
||||
if __name__=='__main__': unittest.main()
|
||||
43
tests/test_reuse_surface_forgejo_source_recovery.py
Normal file
43
tests/test_reuse_surface_forgejo_source_recovery.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import importlib.util
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
|
||||
spec=importlib.util.spec_from_file_location('reuse_recovery',Path(__file__).parents[1]/'scripts/revoke-reuse-surface-forgejo-source.py')
|
||||
r=importlib.util.module_from_spec(spec); spec.loader.exec_module(r)
|
||||
|
||||
class Journal:
|
||||
def __init__(self): self.rows=[]
|
||||
def put(self,event,**kw): self.rows.append((event,kw))
|
||||
class Fake:
|
||||
def __init__(self,ambiguous=False,deleted=True):
|
||||
self.journal=Journal(); self.mutations=[]; self.token={'id':123,'name':r.p.PREFIX+'attempt','scopes':r.p.SCOPES}; self.ambiguous=ambiguous; self.deleted=deleted; self.basic=None
|
||||
def pages(self,path): return [] if self.token is None else [self.token]
|
||||
def request(self,method,path,payload=None,**kw):
|
||||
if method=='GET': return 200,{'login':r.p.USER,'restricted':True,'is_admin':False,'visibility':'private'}
|
||||
self.mutations.append((method,path))
|
||||
if method=='PATCH':
|
||||
assert set(payload)=={'password','must_change_password'}
|
||||
return 200,{}
|
||||
if method=='DELETE':
|
||||
self.basic=kw.get('basic'); assert self.basic.startswith('Basic ')
|
||||
if self.deleted: self.token=None
|
||||
if self.ambiguous: raise r.p.Refused('request_observation_failed')
|
||||
return 204,None
|
||||
raise AssertionError(method)
|
||||
class Tests(unittest.TestCase):
|
||||
def target(self): return {'id':123,'name':r.p.PREFIX+'attempt'}
|
||||
def test_basic_exact_token_revocation(self):
|
||||
c=Fake(); r.recover(c,self.target()); self.assertEqual(c.mutations[-1],('DELETE','/users/'+r.p.USER+'/tokens/123')); self.assertEqual(c.journal.rows[-1][1]['outcome'],'revoked_verified_kv_preserved')
|
||||
def test_unexpected_token_refuses_without_password_change(self):
|
||||
c=Fake(); c.token['id']=124
|
||||
with self.assertRaisesRegex(r.p.Refused,'inventory_conflict'): r.recover(c,self.target())
|
||||
self.assertEqual(c.mutations,[])
|
||||
def test_ambiguous_delete_reconciles_absence_without_retry(self):
|
||||
c=Fake(ambiguous=True); r.recover(c,self.target()); self.assertEqual(sum(m=='DELETE' for m,p in c.mutations),1)
|
||||
def test_ambiguous_delete_still_present_stops(self):
|
||||
c=Fake(ambiguous=True,deleted=False)
|
||||
with self.assertRaisesRegex(r.p.Refused,'revoke_unverified'): r.recover(c,self.target())
|
||||
self.assertEqual(sum(m=='DELETE' for m,p in c.mutations),1)
|
||||
def test_absent_token_verified_noop_has_no_mutations(self):
|
||||
c=Fake(); c.token=None; r.recover(c,self.target()); self.assertEqual(c.mutations,[])
|
||||
if __name__=='__main__': unittest.main()
|
||||
84
workplans/RPF-WP-0058-reuse-private-source-reader.md
Normal file
84
workplans/RPF-WP-0058-reuse-private-source-reader.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
id: RPF-WP-0058
|
||||
type: workplan
|
||||
title: Prepare the exact seven private-source reader for reuse-surface
|
||||
domain: financials
|
||||
repo: railiance-platform
|
||||
status: proposed
|
||||
owner: codex
|
||||
topic_slug: platform
|
||||
created: '2026-10-15'
|
||||
updated: '2026-10-15'
|
||||
flavor: implementation
|
||||
depends_on: []
|
||||
---
|
||||
|
||||
Custody/Q1 work under platform INTENT.md: dependable shared secret custody and
|
||||
stable delivery with reviewed, delegated apply and revocation. Founder metadata
|
||||
consent is recorded separately and grants no credential or live rollout authority.
|
||||
Preparation occurs in isolated clone; no own supervision record means supervised
|
||||
preparation only. IDs CCR-2026-0046 and RPF-WP-0058 are provisional until integration
|
||||
rechecks the authoritative owner's latest allocation.
|
||||
|
||||
## Prepare bounded custody change
|
||||
|
||||
```task
|
||||
id: RPF-WP-0058-T01
|
||||
status: done
|
||||
priority: high
|
||||
```
|
||||
|
||||
Prepare CCR-2026-0046, exact seven-source bootstrap contract, silent bounded
|
||||
provisioner, tests and exact ESO policy delta. No provisioner invocation against
|
||||
live service, token read, policy apply, or cluster change has occurred.
|
||||
|
||||
## Independent owner review and founder disposition
|
||||
|
||||
```task
|
||||
id: RPF-WP-0058-T02
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Platform prepared-review covers own custody artifacts. Forge, apps and consumer
|
||||
owners independently review their scopes; their reviews remain pending. Founder
|
||||
credential provisioning approval must bind exact artifact hashes and confirmed
|
||||
ESO claims. No approval inferred from metadata disclosure. CCR stays proposed.
|
||||
Current consumer image cannot receive this credential: full authenticated
|
||||
redirect/HTTPS plus exact source URL allowlist patch and runtime config required.
|
||||
|
||||
## Execute and verify restricted reader custody
|
||||
|
||||
```task
|
||||
id: RPF-WP-0058-T03
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
After review and disposition, attended platform lane executes silent bootstrap.
|
||||
Read-only preflight checks all seven private repos, restricted non-admin private
|
||||
user, exact code-read team flags, no unrelated members/teams/private grants and
|
||||
PAT/KV drift. Mutation intent and observed/ambiguous outcome are fsynced. No
|
||||
uncertain mint is retried. CAS=0 never overwrites existing KV. Noop still verifies
|
||||
PAT metadata binding, all seven pinned index digests and excluded-private/admin/
|
||||
Actions denial. Raw provider output and credentials remain suppressed.
|
||||
|
||||
## Owner delivery and hosted completion
|
||||
|
||||
```task
|
||||
id: RPF-WP-0058-T04
|
||||
status: wait
|
||||
priority: high
|
||||
```
|
||||
|
||||
Apps owner integrates proposed data item and exact fourteen approved URL config,
|
||||
performs approved fixed-image rollout after CPU/PVC preflight and safe diff/dryrun.
|
||||
Confirm existing ESO SA reuse/reuse-surface-eso, role, audience and namespace
|
||||
condition from live metadata. Consumer owner native verification precedes private
|
||||
registration; Custodian final fresh hosted audit must prove all70 exactly once.
|
||||
Removing reader grants and delivery must preserve shared writer/webhook fields.
|
||||
|
||||
Revision2 of the same CCR preserves R1 capped-page failure and the independently
|
||||
corrected preliminary owned-only endpoint concern. Twenty bounded pages until
|
||||
empty, duplicate IDs and supplied total-count consistency prevent silent short
|
||||
page acceptance. No live complete-grant proof is claimed by these mocks.
|
||||
Loading…
Add table
Add a link
Reference in a new issue