Prepare bounded Coulomb API identity bootstrap with private restartable custody

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
This commit is contained in:
codex 2026-10-28 12:43:05 +01:00
parent bd8ca57dc9
commit 7aa38fcacd
4 changed files with 741 additions and 0 deletions

View file

@ -0,0 +1,383 @@
"""Coulomb-only identity bootstrap; stdin auth is temporary, API pairs stay private.
No personal Console session is a routine credential. Default preview has no
effects. Native S3/escrow boundary checks remain mandatory after this IAM stage.
"""
from __future__ import annotations
import argparse
import fcntl
import json
import os
import stat
import sys
import tempfile
from datetime import UTC, datetime, timedelta
from pathlib import Path
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import HTTPRedirectHandler, Request, build_opener
PUBLISHER = "681d00bf-b965-4323-b657-97bfde423511"
PROJECT = "coulomb-social-recovery"
DECISION = "27bbc1ac-e275-42be-ba34-6cf16eae4e78"
PROGRAMMATIC_DIRECTION = "6139e02b-3fb7-4eff-be8a-efe127c45ce5"
ROLES = {
"bootstrap": {"name": "coulomb-social-recovery-bootstrap", "permissions": [
"ObjectStorageFullAccess", "SecretManagerFullAccess"]},
"reader": {"name": "coulomb-social-recovery-reader", "permissions": [
"ObjectStorageReadOnly"]},
"custodian": {"name": "coulomb-social-recovery-custodian", "permissions": [
"ObjectStorageReadOnly", "ObjectStorageObjectsWrite", "SecretManagerReadOnly",
"SecretManagerSecretAccess"]},
"publisher": {"name": "coulomb-social-backup-primary", "permissions": [
"ObjectStorageObjectsRead", "ObjectStorageObjectsWrite", "ObjectStorageObjectsDelete",
"ObjectStorageBucketsRead"]},
}
class Refused(RuntimeError):
pass
class ProviderError(Refused):
def __init__(self, status):
super().__init__("provider_request_refused")
self.status = status
def inside_git(path):
return any((parent / ".git").exists() for parent in [path.resolve(), *path.resolve().parents])
def private_directory(path):
info = path.lstat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077:
raise Refused("private_owner_directory_required")
if inside_git(path):
raise Refused("credential_directory_must_be_outside_git")
def fsync_directory(path):
fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(fd)
finally:
os.close(fd)
def private_json(path):
info = path.lstat()
if not stat.S_ISREG(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077:
raise Refused("private_owner_file_required")
return json.loads(path.read_text())
def checkpoint(path, value):
fd, name = tempfile.mkstemp(prefix=".checkpoint-", dir=path.parent)
try:
with os.fdopen(fd, "w") as stream:
json.dump(value, stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
fsync_directory(path.parent)
finally:
if os.path.exists(name):
os.unlink(name)
class Transport:
def __init__(self, auth):
if set(auth) != {"session_token", "user_agent"} or any(
not isinstance(v, str) or not v or "\n" in v or "\r" in v for v in auth.values()
):
raise Refused("temporary_stdin_auth_required")
self.headers = {"X-Session-Token": auth["session_token"],
"User-Agent": auth["user_agent"], "Content-Type": "application/json"}
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
raise Refused("provider_redirect_refused")
self.opener = build_opener(NoRedirect())
def __call__(self, method, path, data=None):
if not path.startswith(("/iam/v1alpha1/", "/account/v3/")) or ".." in path:
raise Refused("provider_api_scope_refused")
request = Request("https://api.scaleway.com" + path, method=method,
headers=self.headers,
data=None if data is None else json.dumps(data).encode())
try:
with self.opener.open(request, timeout=20) as response:
raw = response.read(8 * 1024**2)
return json.loads(raw) if raw else {}
except HTTPError as error:
raise ProviderError(error.code) from None
class Bootstrap:
def __init__(self, api, directory, now=None):
self.api, self.directory = api, directory
private_directory(directory)
self.now = now or datetime.now(UTC)
self.path = directory / "identity-state.json"
self.state = private_json(self.path) if self.path.exists() else {
"schema": "platform.coulomb-recovery-identities.v1", "decision": DECISION,
"access_decision": PROGRAMMATIC_DIRECTION, "apps": {}, "policies": {}, "keys": {},
}
if self.state.get("decision") != DECISION or self.state.get("access_decision") != (
PROGRAMMATIC_DIRECTION
) or self.state.get("schema") != "platform.coulomb-recovery-identities.v1":
raise Refused("journal_scope_refused")
if self.state.get("pending_key_issue"):
raise Refused("uncertain_key_issue_needs_reconciliation")
def save(self):
checkpoint(self.path, self.state)
def inventory(self, path, key, **filters):
rows = []
for page in range(1, 101):
value = self.api("GET", path + "?" + urlencode({**filters, "page_size": 100,
"page": page}))
batch = value[key]
if not isinstance(batch, list):
raise Refused("invalid_provider_inventory")
rows.extend(batch)
count = int(value["total_count"])
if len(rows) == count:
return rows
if not batch or len(rows) > count:
break
raise Refused("complete_inventory_required")
def project(self, organization):
if self.state.get("project"):
project = self.api("GET", "/account/v3/projects/" + self.state["project"]["id"])
else:
found = [v for v in self.inventory("/account/v3/projects", "projects",
organization_id=organization) if v["name"] == PROJECT]
if found:
raise Refused("untracked_existing_project_requires_review")
project = self.api("POST", "/account/v3/projects", {
"name": PROJECT, "organization_id": organization,
"description": "Coulomb-only recovery custody; decision " + DECISION,
})
self.state["project"] = project
self.save()
if project["name"] != PROJECT or project["organization_id"] != organization:
raise Refused("project_binding_refused")
return project
def application(self, role, organization, applications):
spec = ROLES[role]
if role == "publisher":
app = self.api("GET", "/iam/v1alpha1/applications/" + PUBLISHER)
elif role in self.state["apps"]:
app = self.api("GET", "/iam/v1alpha1/applications/" + self.state["apps"][role]["id"])
else:
if any(v["name"] == spec["name"] for v in applications):
raise Refused("untracked_existing_application_requires_review")
app = self.api("POST", "/iam/v1alpha1/applications", {
"name": spec["name"], "organization_id": organization,
"description": "Coulomb-only " + role + "; " + DECISION,
})
self.state["apps"][role] = app
self.save()
if app["name"] != spec["name"] or app["organization_id"] != organization:
raise Refused("application_binding_refused")
return app
def policy(self, role, app, project, organization):
name = "coulomb-recovery-project-" + role
if role in self.state["policies"]:
policy = self.api("GET", "/iam/v1alpha1/policies/" + self.state["policies"][role]["id"])
else:
policies = self.inventory("/iam/v1alpha1/policies", "policies",
organization_id=organization)
if any(p["name"] == name for p in policies):
raise Refused("untracked_existing_policy_requires_review")
policy = self.api("POST", "/iam/v1alpha1/policies", {
"name": name, "description": "Coulomb recovery project only; " + DECISION,
"organization_id": organization, "application_id": app["id"],
"rules": [{"project_ids": [project["id"]],
"permission_set_names": ROLES[role]["permissions"]}],
})
self.state["policies"][role] = policy
self.save()
if (policy["application_id"] != app["id"] or policy["name"] != name
or policy["organization_id"] != organization):
raise Refused("policy_binding_refused")
rules = self.api("GET", "/iam/v1alpha1/rules?" + urlencode({"policy_id": policy["id"]}))
if len(rules["rules"]) != 1:
raise Refused("single_project_rule_required")
rule = rules["rules"][0]
if (rule.get("organization_id") or rule.get("project_ids") != [project["id"]]
or set(rule.get("permission_set_names", [])) != set(ROLES[role]["permissions"])
or rule.get("condition") or rule.get("deny")):
raise Refused("effective_project_rule_refused")
def key(self, role, app, project):
path = self.directory / (role + "-pair.json")
if role in self.state["keys"]:
pair = private_json(path)
if set(pair) != {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}:
raise Refused("exact_pair_fields_required")
current = self.api("GET", "/iam/v1alpha1/api-keys/" + pair["AWS_ACCESS_KEY_ID"])
if (current["application_id"] != app["id"]
or current["default_project_id"] != project["id"]):
raise Refused("existing_key_binding_refused")
return
if path.exists() or path.is_symlink():
raise Refused("untracked_pair_requires_review")
self.state["pending_key_issue"] = {"role": role, "application_id": app["id"]}
self.save()
payload = {"application_id": app["id"], "default_project_id": project["id"],
"description": "Coulomb recovery " + role + "; " + DECISION}
if role == "bootstrap":
payload["expires_at"] = (self.now + timedelta(hours=2)).isoformat()
try:
issued = self.api("POST", "/iam/v1alpha1/api-keys", payload)
except ProviderError as error:
if error.status in {400, 401, 403, 404, 422, 429}:
self.state.pop("pending_key_issue")
self.save()
raise
try:
if issued.get("application_id") != app["id"]:
raise Refused("issued_key_binding_refused")
if role == "bootstrap":
expires = datetime.fromisoformat(
(issued.get("expires_at") or "").replace("Z", "+00:00"))
if not self.now < expires <= self.now + timedelta(hours=2):
raise Refused("bounded_bootstrap_expiry_required")
pair = {"AWS_ACCESS_KEY_ID": issued["access_key"],
"AWS_SECRET_ACCESS_KEY": issued["secret_key"]}
if any(not isinstance(v, str) or not v for v in pair.values()):
raise Refused("complete_pair_required")
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
with os.fdopen(fd, "w") as stream:
json.dump(pair, stream)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
fsync_directory(path.parent)
except (Refused, OSError, KeyError, ValueError, TypeError):
if issued.get("access_key") and issued.get("application_id") == app["id"]:
try:
self.api("DELETE", "/iam/v1alpha1/api-keys/" + issued["access_key"])
self.state.pop("pending_key_issue")
self.save()
except (Refused, OSError, KeyError, ValueError, TypeError):
raise Refused("pair_persistence_and_revocation_failed_review_required") from None
raise Refused("pair_persistence_failed_review_required") from None
self.state["keys"][role] = {"application_id": app["id"], "file": path.name,
"expires_at": issued.get("expires_at")}
self.state.pop("pending_key_issue")
self.save()
def run(self):
publisher = self.api("GET", "/iam/v1alpha1/applications/" + PUBLISHER)
if publisher["id"] != PUBLISHER or publisher["name"] != ROLES["publisher"]["name"]:
raise Refused("publisher_binding_refused")
organization = publisher["organization_id"]
permissions = self.inventory("/iam/v1alpha1/permission-sets", "permission_sets",
organization_id=organization)
names = {v["name"] for v in permissions}
if any(set(role["permissions"]) - names for role in ROLES.values()):
raise Refused("required_permission_sets_unavailable")
applications = self.inventory("/iam/v1alpha1/applications", "applications",
organization_id=organization)
project = self.project(organization)
for role in ROLES:
app = self.application(role, organization, applications)
self.policy(role, app, project, organization)
if role != "publisher":
self.key(role, app, project)
self.state["status"] = "identity_bootstrap_complete_native_boundaries_pending"
self.save()
return {"status": self.state["status"], "new_project": PROJECT,
"managed_recovery_applications": 3, "project_policies_verified": 4,
"private_pairs": 3,
"publisher_pair_replaced": False, "organization_wide_grants": False,
"bootstrap_key_max_hours": 2, "native_s3_escrow_boundaries_verified": False,
"credential_values_printed": False}
def retire_bootstrap(self):
if self.state.get("status") != "identity_bootstrap_complete_native_boundaries_pending":
raise Refused("completed_identity_stage_required")
app = self.state["apps"]["bootstrap"]
actual = self.api("GET", "/iam/v1alpha1/applications/" + app["id"])
if actual["name"] != ROLES["bootstrap"]["name"]:
raise Refused("bootstrap_application_binding_refused")
path = self.directory / "bootstrap-pair.json"
if self.state["keys"]["bootstrap"].get("revoked_verified") and not path.exists():
return {"status": "bootstrap_api_key_retired", "credential_values_printed": False}
pair = private_json(path)
key_path = "/iam/v1alpha1/api-keys/" + pair["AWS_ACCESS_KEY_ID"]
try:
current = self.api("GET", key_path)
except ProviderError as error:
if error.status != 404:
raise
else:
if (current["application_id"] != app["id"] or current["default_project_id"] != (
self.state["project"]["id"]
)):
raise Refused("retirement_key_binding_refused")
self.api("DELETE", key_path)
try:
self.api("GET", key_path)
except ProviderError as error:
if error.status != 404:
raise
else:
raise Refused("bootstrap_revocation_unconfirmed")
self.state["keys"]["bootstrap"]["revoked_verified"] = True
self.save()
path.unlink() # Only this just-revoked temporary pair, never retained archives/pairs.
fsync_directory(path.parent)
return {"status": "bootstrap_api_key_retired", "own_temporary_pair_removed": True,
"credential_values_printed": False}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--execute", action="store_true")
parser.add_argument("--directory", type=Path)
parser.add_argument("--retire-bootstrap", action="store_true")
args = parser.parse_args()
if not args.execute:
print(json.dumps({"status": "preview", "project": PROJECT,
"applications": [v["name"] for k, v in ROLES.items() if k != "publisher"],
"provider_contacted": False, "credential_values_printed": False}))
return 0
if not args.directory or not args.directory.is_absolute():
raise Refused("private_absolute_directory_required")
private_directory(args.directory)
fd = os.open(args.directory / "identity-bootstrap.lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW,
0o600)
with os.fdopen(fd, "w") as lock:
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
raw = sys.stdin.read(65537)
if len(raw) > 65536:
raise Refused("bounded_temporary_auth_required")
auth = json.loads(raw)
bootstrap = Bootstrap(Transport(auth), args.directory)
print(json.dumps(bootstrap.retire_bootstrap() if args.retire_bootstrap else bootstrap.run()))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (Refused, OSError, KeyError, ValueError, TypeError) as error:
print(json.dumps({"status": "failed", "failure_type": type(error).__name__,
"http_status": getattr(error, "status", None),
"refusal": str(error) if isinstance(error, Refused) and all(
c.islower() or c == "_" for c in str(error)) else None,
"credential_values_printed": False}))
raise SystemExit(1) from None

View file

@ -0,0 +1,100 @@
#!/usr/bin/env node
/** One attended bootstrap; committed helper creates scoped routine API identities. */
import {createHash} from 'node:crypto';
import {execFileSync,spawn} from 'node:child_process';
import {readFileSync,mkdirSync,lstatSync} from 'node:fs';
import {homedir} from 'node:os';
import {dirname,join,resolve} from 'node:path';
import {fileURLToPath,pathToFileURL} from 'node:url';
import {parseArgs} from 'node:util';
import {createInterface} from 'node:readline/promises';
const {values}=parseArgs({options:{'playwright-module':{type:'string'},'preflight-only':{type:'boolean'}}});
const root=resolve(dirname(fileURLToPath(import.meta.url)),'..');
const factory=join(root,'scripts','application_recovery_identities.py');
const directory=join(homedir(),'.config','railiance-platform','coulomb-social-recovery');
const digest=data=>createHash('sha256').update(data).digest('hex');
let browser,reader,child;
function sourcePreflight(){
const committed=execFileSync('git',['show','HEAD:scripts/application_recovery_identities.py'],{cwd:root});
if(digest(committed)!==digest(readFileSync(factory)))throw new Error('committed_factory_required');
mkdirSync(directory,{recursive:true,mode:0o700});
const info=lstatSync(directory);
if(!info.isDirectory()||info.uid!==process.getuid()||(info.mode&0o077))throw new Error('private_directory_required');
const validation="import importlib.util,sys;from pathlib import Path;"+
"s=importlib.util.spec_from_file_location('recovery_bootstrap',sys.argv[1]);"+
"m=importlib.util.module_from_spec(s);s.loader.exec_module(m);m.private_directory(Path(sys.argv[2]))";
execFileSync('python3',['-c',validation,factory,directory],{stdio:['ignore','pipe','pipe']});
return digest(committed);
}
async function runFactory(page,retire=false){
const sourceHash=sourcePreflight();
if(new URL(page.url()).origin!=='https://console.scaleway.com')throw new Error('console_page_required');
const auth=await page.evaluate(()=>{
const names=Object.keys(localStorage).filter(n=>n.startsWith('_scw_session_console_scaleway_com_'));
if(names.length!==1)throw new Error('unambiguous_session_required');
const session=JSON.parse(localStorage.getItem(names[0]));
if(new Date(session.jwt.expiresAt).getTime()<Date.now()+30000)throw new Error('fresh_bootstrap_session_required');
return {session_token:session.token,user_agent:session.jwt.userAgent};
});
const result=await new Promise((resolveResult,reject)=>{
child=spawn('python3',[factory,'--execute','--directory',directory,
...(retire?['--retire-bootstrap']:[])],{stdio:['pipe','pipe','pipe']});
let output='';
child.stdout.on('data',data=>{if(output.length<65536)output+=data.toString();});
child.stderr.on('data',()=>{}); // Provider/credential diagnostics never enter chat.
child.on('error',()=>reject(new Error('bootstrap_child_failed')));
child.on('close',code=>{
child=null;
try {resolveResult({exit_code:code,result:JSON.parse(output.trim()),factory_source_sha256:sourceHash});}
catch {reject(new Error('redacted_bootstrap_result_required'));}
});
child.stdin.end(JSON.stringify(auth));
});
return result;
}
try{
const sourceHash=sourcePreflight();
const modulePath=resolve(values['playwright-module']||'');
const pkg=JSON.parse(readFileSync(join(dirname(modulePath),'package.json'),'utf8'));
if(pkg.name!=='playwright'||pkg.version!=='1.62.1')throw new Error('locked_playwright_required');
if(values['preflight-only']){
console.log(JSON.stringify({status:'preflight_passed',factory_source_sha256:sourceHash,
private_directory_validated:true,locked_playwright_verified:true,provider_contacted:false}));
process.exit(0);
}
const {chromium}=await import(pathToFileURL(modulePath).href);
browser=await chromium.launch({headless:false});
const context=await browser.newContext();const page=await context.newPage();
await page.goto('https://console.scaleway.com/',{waitUntil:'domcontentloaded',timeout:60000});
reader=createInterface({input:process.stdin,output:process.stdout});
console.log('One-time bootstrap browser opened. Sign in privately; no personal state is saved.');
for(;;){
const answer=await reader.question('After Console login: Enter/run = bootstrap; finish = retire temporary key and close; stop = close. ');
if(answer.trim()==='stop')break;
try{
const retire=answer.trim()==='finish';
const outcome=await runFactory(page,retire);
console.log(JSON.stringify(outcome));
if(outcome.exit_code===0&&retire){
try{await page.goto('https://account.scaleway.com/logout',{waitUntil:'domcontentloaded',timeout:30000});}catch{}
await context.clearCookies();
break;
}
if(outcome.exit_code===0){
console.log('Identity stage complete. Use scoped APIs for resource/boundary proofs, then finish. Keep this window for bootstrap only.');
continue;
}
}catch{console.error('Bootstrap stopped safely. No credential values printed. Keep this window for review.');}
console.log('No automatic retry. Review the private journal; keep this browser open until the next reviewed attempt.');
}
}catch{
console.error('Bootstrap preflight/session failed; no credential values printed.');process.exitCode=1;
}finally{
if(child)child.kill('SIGTERM');
if(reader)reader.close();
if(browser)await browser.close();
}