428 lines
15 KiB
Python
Executable File
428 lines
15 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""One-interaction bootstrap for the AIOS autonomous release campaign.
|
|
|
|
A single real local `aiman` login confirmation does two things atomically at the
|
|
policy layer:
|
|
|
|
1. Writes a root-owned, digest-sidecar protected campaign authorization receipt
|
|
in PENDING_CONTROL_PLANE_MERGE state. It grants no immediate production side
|
|
effect; the campaign becomes ACTIVE only after the campaign control-plane PR
|
|
is merged and machine activation verifies lineage and implementation.
|
|
2. Invokes the existing OwnerAuthorizationBroker to issue only the narrow
|
|
engineering writer needed to implement that control-plane PR.
|
|
|
|
No reusable Owner token is minted.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import fcntl
|
|
import hashlib
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
import socket
|
|
import struct
|
|
import tempfile
|
|
import time
|
|
from typing import Any, Mapping
|
|
|
|
from owner_local_auth_ingress import (
|
|
BootstrapError,
|
|
canonical_json,
|
|
read_frame,
|
|
verify_peer_session,
|
|
write_frame,
|
|
)
|
|
from release_campaign_authorization import (
|
|
ALLOWED_ENGINEERING,
|
|
ALLOWED_PRODUCTION,
|
|
ALLOWED_RECONCILIATION,
|
|
ALLOWED_RELEASE,
|
|
DENIED,
|
|
SCHEMA,
|
|
validate_policy,
|
|
)
|
|
|
|
BEGIN = "aios.release-campaign-bootstrap.begin.v1"
|
|
CHALLENGE = "aios.release-campaign-bootstrap.challenge.v1"
|
|
CONFIRM = "aios.release-campaign-bootstrap.confirm.v1"
|
|
RESULT = "aios.release-campaign-bootstrap.result.v1"
|
|
|
|
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
|
|
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
|
|
REPOSITORY = "aios/aios"
|
|
|
|
|
|
class CampaignBootstrapError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def sha256_file(path: Path) -> str:
|
|
h = hashlib.sha256()
|
|
with path.open("rb") as stream:
|
|
for block in iter(lambda: stream.read(1024 * 1024), b""):
|
|
h.update(block)
|
|
return h.hexdigest()
|
|
|
|
|
|
def atomic_json_new(path: Path, value: Mapping[str, Any]) -> None:
|
|
if path.exists() or path.is_symlink():
|
|
raise CampaignBootstrapError(f"target_exists:{path}")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
if os.geteuid() == 0:
|
|
os.fchown(fd, 0, 0)
|
|
with os.fdopen(fd, "wb", closefd=True) as stream:
|
|
stream.write(canonical_json(dict(value)))
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
os.replace(name, path)
|
|
os.chmod(path, 0o600)
|
|
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
|
try:
|
|
os.fsync(dfd)
|
|
finally:
|
|
os.close(dfd)
|
|
finally:
|
|
try:
|
|
os.unlink(name)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def atomic_sidecar(path: Path, digest: str, target_name: str) -> None:
|
|
if path.exists() or path.is_symlink():
|
|
raise CampaignBootstrapError(f"sidecar_exists:{path}")
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
if os.geteuid() == 0:
|
|
os.fchown(fd, 0, 0)
|
|
with os.fdopen(fd, "w", encoding="ascii", closefd=True) as stream:
|
|
stream.write(f"{digest} {target_name}\n")
|
|
stream.flush()
|
|
os.fsync(stream.fileno())
|
|
os.replace(name, path)
|
|
os.chmod(path, 0o600)
|
|
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
|
|
try:
|
|
os.fsync(dfd)
|
|
finally:
|
|
os.close(dfd)
|
|
finally:
|
|
try:
|
|
os.unlink(name)
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def writer_request(base_oid: str) -> dict[str, Any]:
|
|
return {
|
|
"action": "AUTHORIZE_TASK_WRITER_PROVISIONING",
|
|
"task_id": TASK_ID,
|
|
"repository": REPOSITORY,
|
|
"base_ref": "refs/heads/main",
|
|
"base_oid": base_oid,
|
|
"target_branch": TARGET_BRANCH,
|
|
"one_time_use": True,
|
|
"expires_in_seconds": 300,
|
|
"production_change_authorized": False,
|
|
"tag_change_authorized": False,
|
|
"baseline_change_authorized": False,
|
|
"main_write_authorized": False,
|
|
"allowed_operations": [
|
|
"create_worktree",
|
|
"create_target_branch_if_absent",
|
|
"commit_to_target_branch",
|
|
"push_target_branch",
|
|
],
|
|
"denied_operations": [
|
|
"direct_main_push",
|
|
"force_push",
|
|
"tag_create",
|
|
"tag_update",
|
|
"tag_delete",
|
|
"branch_delete",
|
|
"repository_admin",
|
|
"baseline_write",
|
|
"production_deploy",
|
|
"production_marker_write",
|
|
"history_rewrite",
|
|
],
|
|
}
|
|
|
|
|
|
def campaign_policy(campaign_id: str, base_oid: str) -> dict[str, Any]:
|
|
value = {
|
|
"schema": SCHEMA,
|
|
"campaign_id": campaign_id,
|
|
"generation": 1,
|
|
"owner_identity": "aiman",
|
|
"owner_role": "owner",
|
|
"repository": REPOSITORY,
|
|
"lineage_anchor_oid": base_oid,
|
|
"control_plane_branch": TARGET_BRANCH,
|
|
"activation_state": "PENDING_CONTROL_PLANE_MERGE",
|
|
"active": False,
|
|
"revoked": False,
|
|
"valid_until_revoked": True,
|
|
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
|
|
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
|
|
"allowed_release": sorted(ALLOWED_RELEASE),
|
|
"allowed_production": sorted(ALLOWED_PRODUCTION),
|
|
"denied": sorted(DENIED),
|
|
"normal_owner_interaction_required": False,
|
|
}
|
|
return validate_policy(value)
|
|
|
|
|
|
def invoke_writer_broker(base_oid: str) -> dict[str, Any]:
|
|
from owner_authorization_broker import (
|
|
OwnerAuthorizationBroker,
|
|
OwnerAuthorizationBrokerConfig,
|
|
OwnerPrincipal,
|
|
)
|
|
|
|
config = OwnerAuthorizationBrokerConfig.from_environment()
|
|
if "aiman" not in config.allowed_owner_identities:
|
|
raise CampaignBootstrapError("aiman_not_allowed_by_live_broker")
|
|
if "commit:create" not in config.allowed_intake_scopes:
|
|
raise CampaignBootstrapError("commit_create_not_allowed_by_live_broker")
|
|
principal = OwnerPrincipal(
|
|
identity="aiman",
|
|
role="owner",
|
|
authenticated_scope="commit:create",
|
|
token_scopes=frozenset({"commit:create"}),
|
|
credential_id="",
|
|
)
|
|
return OwnerAuthorizationBroker(config=config).authorize_and_provision(
|
|
writer_request(base_oid),
|
|
principal,
|
|
)
|
|
|
|
|
|
def safe_broker_result(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
allowed = {
|
|
"status",
|
|
"approval_plan_id",
|
|
"grant_id",
|
|
"grant_digest_sha256",
|
|
"branch_oid",
|
|
"branch_ref_created",
|
|
"grant_consumed",
|
|
"owner_authorization_event_id",
|
|
"owner_authorization_reference",
|
|
"owner_authorization_digest_sha256",
|
|
"owner_authorization_consumed",
|
|
"owner_authorization_idempotent",
|
|
}
|
|
lowered = " ".join(str(k).lower() for k in value)
|
|
for secret_word in ("token", "password", "private_key", "secret_value", "bearer"):
|
|
if secret_word in lowered:
|
|
raise CampaignBootstrapError("broker_response_sensitive_field_rejected")
|
|
return {k: value[k] for k in sorted(allowed) if k in value}
|
|
|
|
|
|
def serve(
|
|
*,
|
|
socket_path: Path,
|
|
state_root: Path,
|
|
base_oid: str,
|
|
campaign_id: str,
|
|
timeout: int,
|
|
) -> int:
|
|
if os.geteuid() != 0:
|
|
raise CampaignBootstrapError("server_root_required")
|
|
if len(base_oid) != 40 or any(c not in "0123456789abcdef" for c in base_oid):
|
|
raise CampaignBootstrapError("base_oid_invalid")
|
|
|
|
policy = campaign_policy(campaign_id, base_oid)
|
|
campaign_dir = state_root / campaign_id
|
|
campaign_path = campaign_dir / "authorization.json"
|
|
sidecar_path = campaign_dir / "authorization.json.sha256"
|
|
|
|
socket_path.parent.mkdir(parents=True, exist_ok=True)
|
|
if socket_path.exists() or socket_path.is_symlink():
|
|
raise CampaignBootstrapError("socket_already_exists")
|
|
|
|
import pwd
|
|
owner = pwd.getpwnam("aiman")
|
|
os.chown(socket_path.parent, 0, owner.pw_gid)
|
|
os.chmod(socket_path.parent, 0o750)
|
|
|
|
listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
try:
|
|
listener.bind(str(socket_path))
|
|
os.chown(socket_path, 0, owner.pw_gid)
|
|
os.chmod(socket_path, 0o660)
|
|
listener.listen(1)
|
|
listener.settimeout(timeout)
|
|
|
|
conn, _ = listener.accept()
|
|
with conn:
|
|
raw = conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
|
|
pid, uid, gid = struct.unpack("3i", raw)
|
|
peer = verify_peer_session(pid, uid, gid, "aiman")
|
|
if read_frame(conn) != {"schema": BEGIN, "action": "BEGIN"}:
|
|
raise CampaignBootstrapError("client_begin_invalid")
|
|
|
|
challenge = secrets.token_hex(12)
|
|
issued = time.monotonic()
|
|
summary = {
|
|
"campaign_id": campaign_id,
|
|
"repository": REPOSITORY,
|
|
"lineage_anchor_oid": base_oid,
|
|
"control_plane_branch": TARGET_BRANCH,
|
|
"normal_owner_interaction_after_activation": False,
|
|
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
|
|
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
|
|
"allowed_release": sorted(ALLOWED_RELEASE),
|
|
"allowed_production": sorted(ALLOWED_PRODUCTION),
|
|
"denied": sorted(DENIED),
|
|
"writer_request": writer_request(base_oid),
|
|
}
|
|
write_frame(
|
|
conn,
|
|
{
|
|
"schema": CHALLENGE,
|
|
"challenge": challenge,
|
|
"expires_in_seconds": 120,
|
|
"summary": summary,
|
|
},
|
|
)
|
|
confirm = read_frame(conn)
|
|
phrase = f"AUTHORIZE CAMPAIGN {campaign_id} {challenge}"
|
|
if (
|
|
confirm.get("schema") != CONFIRM
|
|
or confirm.get("challenge") != challenge
|
|
or confirm.get("confirmation") != phrase
|
|
):
|
|
raise CampaignBootstrapError("campaign_confirmation_invalid")
|
|
if time.monotonic() - issued > 120:
|
|
raise CampaignBootstrapError("campaign_confirmation_expired")
|
|
|
|
# First issue the narrow engineering writer. If this fails, do not
|
|
# persist a campaign authorization that cannot be implemented.
|
|
broker = safe_broker_result(invoke_writer_broker(base_oid))
|
|
if broker.get("status") != "READY_FOR_CODE_AUTHORING":
|
|
raise CampaignBootstrapError("writer_not_ready")
|
|
|
|
atomic_json_new(campaign_path, policy)
|
|
digest = sha256_file(campaign_path)
|
|
atomic_sidecar(sidecar_path, digest, campaign_path.name)
|
|
|
|
receipt = {
|
|
"schema": "aios.release-campaign-bootstrap-receipt.v1",
|
|
"status": "CAMPAIGN_PENDING_CONTROL_PLANE_MERGE",
|
|
"campaign_id": campaign_id,
|
|
"campaign_authorization_path": str(campaign_path),
|
|
"campaign_authorization_sha256": digest,
|
|
"owner_identity": "aiman",
|
|
"owner_role": "owner",
|
|
"authentication": {
|
|
"method": "local_so_peercred_loginuid_tty",
|
|
"uid": peer["uid"],
|
|
"loginuid": peer["loginuid"],
|
|
"tty": peer["tty"],
|
|
},
|
|
"writer": broker,
|
|
"reusable_owner_token_created": False,
|
|
"production_side_effect_executed": False,
|
|
"completed_at_unix": int(time.time()),
|
|
}
|
|
receipt_path = campaign_dir / "bootstrap-receipt.json"
|
|
atomic_json_new(receipt_path, receipt)
|
|
write_frame(
|
|
conn,
|
|
{
|
|
"schema": RESULT,
|
|
"ok": True,
|
|
"campaign_id": campaign_id,
|
|
"campaign_authorization_path": str(campaign_path),
|
|
"campaign_authorization_sha256": digest,
|
|
"writer": broker,
|
|
},
|
|
)
|
|
return 0
|
|
finally:
|
|
listener.close()
|
|
try:
|
|
socket_path.unlink()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def client(socket_path: Path) -> int:
|
|
import sys
|
|
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
raise CampaignBootstrapError("interactive_tty_required")
|
|
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
conn.connect(str(socket_path))
|
|
with conn:
|
|
write_frame(conn, {"schema": BEGIN, "action": "BEGIN"})
|
|
challenge = read_frame(conn)
|
|
if challenge.get("schema") != CHALLENGE:
|
|
raise CampaignBootstrapError("challenge_invalid")
|
|
print("\nAIOS Autonomous Release Campaign authorization")
|
|
print(json.dumps(challenge["summary"], indent=2, sort_keys=True))
|
|
phrase = (
|
|
f"AUTHORIZE CAMPAIGN "
|
|
f"{challenge['summary']['campaign_id']} "
|
|
f"{challenge['challenge']}"
|
|
)
|
|
typed = input(f"\nType exactly:\n{phrase}\n> ").strip()
|
|
if typed != phrase:
|
|
raise CampaignBootstrapError("confirmation_mismatch")
|
|
write_frame(
|
|
conn,
|
|
{
|
|
"schema": CONFIRM,
|
|
"challenge": challenge["challenge"],
|
|
"confirmation": typed,
|
|
},
|
|
)
|
|
result = read_frame(conn)
|
|
print(json.dumps(result, indent=2, sort_keys=True))
|
|
return 0 if result.get("ok") is True else 3
|
|
|
|
|
|
def main() -> int:
|
|
p = argparse.ArgumentParser()
|
|
mode = p.add_mutually_exclusive_group(required=True)
|
|
mode.add_argument("--serve", action="store_true")
|
|
mode.add_argument("--client", action="store_true")
|
|
p.add_argument("--socket", type=Path, default=Path("/run/aios-release-campaign/owner.sock"))
|
|
p.add_argument(
|
|
"--state-root",
|
|
type=Path,
|
|
default=Path("/var/lib/aios-gitops/release-campaigns"),
|
|
)
|
|
p.add_argument("--base-oid")
|
|
p.add_argument("--campaign-id")
|
|
p.add_argument("--timeout", type=int, default=300)
|
|
a = p.parse_args()
|
|
if a.serve:
|
|
if not a.base_oid or not a.campaign_id:
|
|
raise CampaignBootstrapError("server_binding_required")
|
|
return serve(
|
|
socket_path=a.socket,
|
|
state_root=a.state_root,
|
|
base_oid=a.base_oid,
|
|
campaign_id=a.campaign_id,
|
|
timeout=a.timeout,
|
|
)
|
|
return client(a.socket)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except (CampaignBootstrapError, BootstrapError) as exc:
|
|
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
|
|
raise SystemExit(2)
|