932 lines
42 KiB
Python
932 lines
42 KiB
Python
"""Unix-socket service for governed local writer operations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import hashlib
|
|
import argparse
|
|
import importlib.util
|
|
import os
|
|
import re
|
|
import socket
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import uuid
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any, Callable, Mapping, Protocol
|
|
|
|
from governed_local_writer import (
|
|
AppendCommitRequest,
|
|
AuthorizationError,
|
|
BranchPolicy,
|
|
CreateBranchRequest,
|
|
GovernedAuthorization,
|
|
PeerIdentity,
|
|
PRINCIPAL,
|
|
TASK_ID,
|
|
extract_peer_identity,
|
|
contains_secret_like_content,
|
|
load_authorization_file,
|
|
validate_file_changes,
|
|
)
|
|
|
|
|
|
class ExistingGitOpsHandler:
|
|
"""Delegate repository mutations to the installed GitOps handler seam."""
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
workspace_by_branch: Mapping[str, str | os.PathLike[str]],
|
|
bare_repo: str | os.PathLike[str],
|
|
api_source: str | os.PathLike[str],
|
|
) -> None:
|
|
self.workspace_by_branch = {str(k): Path(v) for k, v in workspace_by_branch.items()}
|
|
self.bare_repo = Path(bare_repo)
|
|
self.api_source = Path(api_source)
|
|
|
|
@staticmethod
|
|
def _restore_env(previous: Mapping[str, str | None]) -> None:
|
|
for key, value in previous.items():
|
|
if value is None:
|
|
os.environ.pop(key, None)
|
|
else:
|
|
os.environ[key] = value
|
|
|
|
def _load_api(self, workspace: Path):
|
|
if not self.api_source.is_file() or self.api_source.is_symlink():
|
|
raise RuntimeError("installed GitOps handler source is unavailable")
|
|
keys = (
|
|
"AIOS_GITOPS_WORKSPACE",
|
|
"AIOS_GITOPS_BARE_REPO",
|
|
"AIOS_GITOPS_WRITES_ENABLED",
|
|
"PYTHONDONTWRITEBYTECODE",
|
|
"GIT_CONFIG_COUNT",
|
|
"GIT_CONFIG_KEY_0",
|
|
"GIT_CONFIG_VALUE_0",
|
|
"GIT_CONFIG_KEY_1",
|
|
"GIT_CONFIG_VALUE_1",
|
|
)
|
|
previous = {key: os.environ.get(key) for key in keys}
|
|
os.environ.update(
|
|
{
|
|
"AIOS_GITOPS_WORKSPACE": str(workspace),
|
|
"AIOS_GITOPS_BARE_REPO": str(self.bare_repo),
|
|
"AIOS_GITOPS_WRITES_ENABLED": "true",
|
|
"PYTHONDONTWRITEBYTECODE": "1",
|
|
"GIT_CONFIG_COUNT": "2",
|
|
"GIT_CONFIG_KEY_0": "safe.directory",
|
|
"GIT_CONFIG_VALUE_0": str(workspace),
|
|
"GIT_CONFIG_KEY_1": "safe.directory",
|
|
"GIT_CONFIG_VALUE_1": str(self.bare_repo),
|
|
}
|
|
)
|
|
module_name = f"_governed_gitops_api_{uuid.uuid4().hex}"
|
|
try:
|
|
spec = importlib.util.spec_from_file_location(module_name, self.api_source)
|
|
if spec is None or spec.loader is None:
|
|
raise RuntimeError("unable to load installed GitOps handler")
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[module_name] = module
|
|
spec.loader.exec_module(module)
|
|
decision = module.evaluate_git_write(module.WS)
|
|
if not decision.allowed:
|
|
raise RuntimeError("existing GitOps write gate denied bootstrap")
|
|
return module, previous, module_name
|
|
except Exception:
|
|
sys.modules.pop(module_name, None)
|
|
self._restore_env(previous)
|
|
raise
|
|
|
|
def _workspace(self, branch: str) -> Path:
|
|
workspace = self.workspace_by_branch.get(branch)
|
|
if workspace is None or not workspace.is_dir():
|
|
raise RuntimeError("branch workspace is not authorized")
|
|
return workspace
|
|
|
|
@staticmethod
|
|
def _git(workspace: Path, args: list[str]) -> str:
|
|
result = subprocess.run(
|
|
["git", *args], cwd=workspace, text=True, capture_output=True, check=False
|
|
)
|
|
if result.returncode != 0:
|
|
raise RuntimeError("governed handler git probe failed")
|
|
return result.stdout.strip()
|
|
|
|
def create_branch(self, request: CreateBranchRequest) -> Mapping[str, object]:
|
|
workspace = self._workspace(request.branch)
|
|
# A prior handler call may have completed the fast-forward sync before
|
|
# the process observed a post-commit error. Reconcile only an exact,
|
|
# metadata-only result; never overwrite or move an existing ref here.
|
|
try:
|
|
existing = self._git(
|
|
workspace,
|
|
[
|
|
"--git-dir",
|
|
str(self.bare_repo),
|
|
"rev-parse",
|
|
f"refs/heads/{request.branch}",
|
|
],
|
|
)
|
|
except RuntimeError:
|
|
existing = ""
|
|
if existing:
|
|
parent = self._git(workspace, ["--git-dir", str(self.bare_repo), "rev-parse", f"{existing}^1"])
|
|
changed = self._git(
|
|
workspace,
|
|
[
|
|
"--git-dir",
|
|
str(self.bare_repo),
|
|
"diff-tree",
|
|
"--no-commit-id",
|
|
"--name-only",
|
|
"-r",
|
|
existing,
|
|
],
|
|
).splitlines()
|
|
safe_branch = re.sub(r"[^A-Za-z0-9_.-]+", "-", request.branch).strip("-")
|
|
if parent != request.base_oid or changed != [f"patches/{safe_branch}.branch.json"]:
|
|
raise RuntimeError("existing branch is not an exact metadata-only bootstrap")
|
|
return {"head_oid": existing, "parent_oid": parent, "handler_status": "reconciled"}
|
|
module, previous, module_name = self._load_api(workspace)
|
|
phase = "loaded"
|
|
try:
|
|
phase = "verify_base"
|
|
parent = self._git(
|
|
workspace,
|
|
["--git-dir", str(self.bare_repo), "rev-parse", "refs/heads/main"],
|
|
)
|
|
if parent != request.base_oid:
|
|
raise RuntimeError("authorized base OID drift")
|
|
phase = "checkout"
|
|
module.checkout(request.branch, base=request.base_oid, create=True)
|
|
phase = "write_metadata"
|
|
safe_branch = re.sub(r"[^A-Za-z0-9_.-]+", "-", request.branch).strip("-")
|
|
metadata_path = f"patches/{safe_branch}.branch.json"
|
|
module.write_json(
|
|
metadata_path,
|
|
{
|
|
"schema": "aios.governed-local-writer-bootstrap.v1",
|
|
"task_id": TASK_ID,
|
|
"principal": PRINCIPAL,
|
|
"branch": request.branch,
|
|
"parent_oid": request.base_oid,
|
|
"authorization_sha256": request.authorization_digest,
|
|
},
|
|
)
|
|
phase = "commit_and_sync"
|
|
result = module.commit(
|
|
[metadata_path], "chore(governance): bootstrap repair lease branch"
|
|
)
|
|
head_oid = self._git(workspace, ["rev-parse", "HEAD"])
|
|
first_parent = self._git(workspace, ["rev-parse", "HEAD^1"])
|
|
if first_parent != request.base_oid or (request.head_oid and head_oid != request.head_oid):
|
|
raise RuntimeError("bootstrap branch lineage check failed")
|
|
return {
|
|
"head_oid": head_oid,
|
|
"parent_oid": first_parent,
|
|
"handler_status": str(result.get("status")),
|
|
}
|
|
except Exception as exc:
|
|
raise RuntimeError(f"handler_phase={phase};error={exc.__class__.__name__}") from exc
|
|
finally:
|
|
sys.modules.pop(module_name, None)
|
|
self._restore_env(previous)
|
|
|
|
def append_commit(self, request: AppendCommitRequest) -> Mapping[str, object]:
|
|
workspace = self._workspace(request.branch)
|
|
module, previous, module_name = self._load_api(workspace)
|
|
phase = "loaded"
|
|
try:
|
|
phase = "verify_head"
|
|
current = self._git(workspace, ["rev-parse", "HEAD"])
|
|
if current != request.expected_head_oid:
|
|
raise RuntimeError("branch head advanced or is stale")
|
|
phase = "checkout"
|
|
module.checkout(request.branch, base=request.expected_head_oid, create=False)
|
|
phase = "write_files"
|
|
paths: list[str] = []
|
|
for change in request.changes:
|
|
paths.append(module.write_file(change.path, change.content))
|
|
phase = "commit_and_sync"
|
|
result = module.commit(paths, request.commit_message)
|
|
head_oid = self._git(workspace, ["rev-parse", "HEAD"])
|
|
first_parent = self._git(workspace, ["rev-parse", "HEAD^1"])
|
|
if first_parent != request.expected_head_oid or head_oid == first_parent:
|
|
raise RuntimeError("append commit lineage check failed")
|
|
return {
|
|
"head_oid": head_oid,
|
|
"parent_oid": first_parent,
|
|
"handler_status": str(result.get("status")),
|
|
}
|
|
except Exception as exc:
|
|
raise RuntimeError(f"handler_phase={phase};error={exc.__class__.__name__}") from exc
|
|
finally:
|
|
sys.modules.pop(module_name, None)
|
|
self._restore_env(previous)
|
|
|
|
|
|
class GovernedWriterHandler(Protocol):
|
|
"""Small adapter interface for real writer backend handlers."""
|
|
|
|
def create_branch(self, request: CreateBranchRequest) -> Mapping[str, object]:
|
|
"""Create a branch using internal repo services."""
|
|
|
|
def append_commit(self, request: AppendCommitRequest) -> Mapping[str, object]:
|
|
"""Append a commit using internal repo services."""
|
|
|
|
|
|
class AuditSink(Protocol):
|
|
"""Pluggable sink for sanitized audit records."""
|
|
|
|
def append(self, record: "AuditRecord") -> None:
|
|
"""Persist one audit record."""
|
|
|
|
|
|
@dataclass
|
|
class AuditRecord:
|
|
"""Sanitized audit log entry."""
|
|
|
|
authorization_digest: str
|
|
peer_pid: int
|
|
peer_uid: int
|
|
peer_gid: int
|
|
peer_executable: str | None
|
|
timestamp: datetime
|
|
operation: str
|
|
branch: str
|
|
result: str
|
|
|
|
|
|
class InMemoryAuditSink:
|
|
"""Simple in-memory audit log used by tests."""
|
|
|
|
def __init__(self) -> None:
|
|
self.records: list[AuditRecord] = []
|
|
|
|
def append(self, record: AuditRecord) -> None:
|
|
self.records.append(record)
|
|
|
|
|
|
@dataclass
|
|
class _AuthorizationState:
|
|
"""Tracks one-shot operation consumption."""
|
|
|
|
create_done: set[str] = field(default_factory=set)
|
|
append_done: set[str] = field(default_factory=set)
|
|
consumed: bool = False
|
|
|
|
|
|
class GovernedLocalWriterService:
|
|
"""Policy-aware AF_UNIX service that calls an injected handler."""
|
|
|
|
def __init__(
|
|
self,
|
|
socket_path: str | os.PathLike[str],
|
|
authorization: GovernedAuthorization,
|
|
handler: GovernedWriterHandler,
|
|
*,
|
|
audit_sink: AuditSink | None = None,
|
|
peer_identity_fn: Callable[[socket.socket], PeerIdentity] | None = None,
|
|
state_path: str | os.PathLike[str] | None = None,
|
|
) -> None:
|
|
self.socket_path = Path(socket_path)
|
|
self.authorization = authorization
|
|
self.handler = handler
|
|
self.peer_identity_fn = peer_identity_fn or extract_peer_identity
|
|
self.state = _AuthorizationState()
|
|
self.audit_sink = audit_sink or InMemoryAuditSink()
|
|
self.state_path = Path(state_path) if state_path is not None else None
|
|
self._load_state()
|
|
|
|
def _load_state(self) -> None:
|
|
"""Restore one-shot state without restoring request or secret material."""
|
|
if self.state_path is None or not self.state_path.exists():
|
|
return
|
|
try:
|
|
raw = json.loads(self.state_path.read_text(encoding="utf-8"))
|
|
if not isinstance(raw, dict):
|
|
raise ValueError("state must be an object")
|
|
if raw.get("authorization_sha256") != self.authorization.authorization_sha256:
|
|
raise ValueError("state authorization digest mismatch")
|
|
self.state.create_done = {str(v) for v in raw.get("create_done", [])}
|
|
self.state.append_done = {str(v) for v in raw.get("append_done", [])}
|
|
self.state.consumed = bool(raw.get("consumed", False))
|
|
except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc:
|
|
raise AuthorizationError("authorization state is invalid") from exc
|
|
|
|
def _persist_state(self) -> None:
|
|
if self.state_path is None:
|
|
return
|
|
self.state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = {
|
|
"authorization_sha256": self.authorization.authorization_sha256,
|
|
"create_done": sorted(self.state.create_done),
|
|
"append_done": sorted(self.state.append_done),
|
|
"consumed": self.state.consumed,
|
|
}
|
|
fd, temp_name = tempfile.mkstemp(prefix=".writer-state-", dir=self.state_path.parent)
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
with os.fdopen(fd, "w", encoding="utf-8") as stream:
|
|
json.dump(payload, stream, sort_keys=True, separators=(",", ":"))
|
|
stream.write("\n")
|
|
os.replace(temp_name, self.state_path)
|
|
os.chmod(self.state_path, 0o600)
|
|
finally:
|
|
if os.path.exists(temp_name):
|
|
os.unlink(temp_name)
|
|
|
|
def _new_audit(self, peer: PeerIdentity, operation: str, branch: str, result: str) -> None:
|
|
self.audit_sink.append(
|
|
AuditRecord(
|
|
authorization_digest=self.authorization.authorization_sha256,
|
|
peer_pid=peer.pid,
|
|
peer_uid=peer.uid,
|
|
peer_gid=peer.gid,
|
|
peer_executable=peer.executable,
|
|
timestamp=datetime.now(tz=timezone.utc),
|
|
operation=operation,
|
|
branch=branch,
|
|
result=result,
|
|
)
|
|
)
|
|
|
|
def _require_request_field(self, request: Mapping[str, Any], field_name: str) -> str:
|
|
value = request.get(field_name)
|
|
if not isinstance(value, str) or not value:
|
|
raise AuthorizationError(f"{field_name} must be a non-empty string")
|
|
return value
|
|
|
|
def _branch_policy(self, branch: str) -> BranchPolicy:
|
|
policy = self.authorization.branch_operations.get(branch)
|
|
if policy is None:
|
|
raise AuthorizationError("branch is not authorized")
|
|
return policy
|
|
|
|
def _ensure_not_consumed(self) -> None:
|
|
if self.state.consumed:
|
|
raise AuthorizationError("authorization consumed")
|
|
if datetime.now(tz=timezone.utc) >= self.authorization.expires_at:
|
|
raise AuthorizationError("authorization expired")
|
|
|
|
def _mark_success(self, op: str, branch: str) -> None:
|
|
if op == "create_branch":
|
|
self.state.create_done.add(branch)
|
|
elif op == "append_commit":
|
|
self.state.append_done.add(branch)
|
|
|
|
if self.state.create_done.issuperset(self.authorization.create_branches) and self.state.append_done.issuperset(
|
|
self.authorization.append_branches
|
|
):
|
|
self.state.consumed = True
|
|
self._persist_state()
|
|
|
|
def _validate_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
|
|
task_id = self._require_request_field(request, "task_id")
|
|
if task_id != TASK_ID:
|
|
raise AuthorizationError("invalid task_id")
|
|
|
|
principal = self._require_request_field(request, "principal")
|
|
if principal != PRINCIPAL:
|
|
raise AuthorizationError("invalid principal")
|
|
|
|
operation = self._require_request_field(request, "op")
|
|
if operation not in {"create_branch", "append_commit"}:
|
|
raise AuthorizationError("unsupported operation")
|
|
if operation in self.authorization.denied_operations:
|
|
raise AuthorizationError("operation denied by policy")
|
|
|
|
branch = self._require_request_field(request, "branch")
|
|
self._ensure_not_consumed()
|
|
policy = self._branch_policy(branch)
|
|
|
|
if operation == "create_branch":
|
|
if policy.create_branch is None:
|
|
raise AuthorizationError("branch does not allow create_branch")
|
|
if branch in self.state.create_done:
|
|
raise AuthorizationError("create_branch already used for this branch")
|
|
|
|
base_oid = self._require_request_field(request, "base_oid")
|
|
raw_head_oid = request.get("head_oid")
|
|
if raw_head_oid is not None and (not isinstance(raw_head_oid, str) or not raw_head_oid):
|
|
raise AuthorizationError("head_oid must be a non-empty string when supplied")
|
|
head_oid = str(raw_head_oid) if raw_head_oid is not None else None
|
|
if base_oid != policy.create_branch.base_oid:
|
|
raise AuthorizationError("base_oid mismatch")
|
|
if policy.create_branch.head_oid is not None and head_oid != policy.create_branch.head_oid:
|
|
raise AuthorizationError("head_oid mismatch")
|
|
|
|
return {
|
|
"operation": operation,
|
|
"branch": branch,
|
|
"base_oid": base_oid,
|
|
"head_oid": head_oid,
|
|
}
|
|
|
|
if policy.append_commit is None:
|
|
raise AuthorizationError("branch does not allow append_commit")
|
|
if policy.create_branch is not None and branch not in self.state.create_done:
|
|
raise AuthorizationError("append_commit requires prior successful create_branch")
|
|
if branch in self.state.append_done:
|
|
raise AuthorizationError("append_commit already used for this branch")
|
|
|
|
expected_head = policy.append_commit.expected_head_oid
|
|
if "expected_head_oid" in request:
|
|
supplied_head = self._require_request_field(request, "expected_head_oid")
|
|
elif "current_head_oid" in request:
|
|
supplied_head = self._require_request_field(request, "current_head_oid")
|
|
else:
|
|
raise AuthorizationError("expected_head_oid or current_head_oid required")
|
|
if not re.fullmatch(r"[0-9a-fA-F]{40}", supplied_head):
|
|
raise AuthorizationError("head check must be a full commit OID")
|
|
if expected_head is not None and supplied_head != expected_head:
|
|
raise AuthorizationError("head check failed")
|
|
|
|
if request.get("force") is True or request.get("non_fast_forward") is True:
|
|
raise AuthorizationError("non-fast-forward operations are not allowed")
|
|
if request.get("history_rewrite") is True:
|
|
raise AuthorizationError("history rewrite is not allowed")
|
|
parent_oids = request.get("parent_oids")
|
|
if parent_oids is not None:
|
|
if not isinstance(parent_oids, list) or parent_oids != [supplied_head]:
|
|
raise AuthorizationError("parent_oids must contain exactly expected_head_oid")
|
|
|
|
changes = request.get("changes")
|
|
if not isinstance(changes, list):
|
|
raise AuthorizationError("changes must be a list")
|
|
validated_changes = validate_file_changes(changes, policy.append_commit.allowed_paths)
|
|
commit_message = request.get("commit_message", "chore(governance): append governed commit")
|
|
if not isinstance(commit_message, str) or not commit_message.strip() or len(commit_message) > 200:
|
|
raise AuthorizationError("commit_message must be a short non-empty string")
|
|
if "\n" in commit_message or "\r" in commit_message or contains_secret_like_content(commit_message):
|
|
raise AuthorizationError("commit_message failed policy")
|
|
|
|
return {
|
|
"operation": operation,
|
|
"branch": branch,
|
|
"expected_head_oid": supplied_head,
|
|
"changes": validated_changes,
|
|
"commit_message": commit_message.strip(),
|
|
}
|
|
|
|
def process_request(self, raw_request: Mapping[str, Any], peer: PeerIdentity) -> Mapping[str, Any]:
|
|
"""Run one request through policy checks and handler adapter."""
|
|
operation = raw_request.get("op") if isinstance(raw_request.get("op"), str) else "unknown"
|
|
branch = raw_request.get("branch") if isinstance(raw_request.get("branch"), str) else ""
|
|
try:
|
|
details = self._validate_request(raw_request)
|
|
operation = str(details["operation"])
|
|
branch = str(details["branch"])
|
|
|
|
if operation == "create_branch":
|
|
self.handler.create_branch(
|
|
CreateBranchRequest(
|
|
branch=branch,
|
|
base_oid=str(details["base_oid"]),
|
|
head_oid=(
|
|
str(details["head_oid"])
|
|
if details.get("head_oid") is not None
|
|
else None
|
|
),
|
|
authorization_digest=self.authorization.authorization_sha256,
|
|
)
|
|
)
|
|
self._mark_success("create_branch", branch)
|
|
self._new_audit(peer, operation, branch, "ok")
|
|
return {
|
|
"status": "ok",
|
|
"operation": operation,
|
|
"branch": branch,
|
|
"result": "applied",
|
|
}
|
|
|
|
self.handler.append_commit(
|
|
AppendCommitRequest(
|
|
branch=branch,
|
|
expected_head_oid=str(details["expected_head_oid"]),
|
|
changes=tuple(details["changes"]),
|
|
authorization_digest=self.authorization.authorization_sha256,
|
|
commit_message=str(details["commit_message"]),
|
|
)
|
|
)
|
|
self._mark_success("append_commit", branch)
|
|
self._new_audit(peer, operation, branch, "ok")
|
|
return {
|
|
"status": "ok",
|
|
"operation": operation,
|
|
"branch": branch,
|
|
"result": "applied",
|
|
}
|
|
except Exception as error:
|
|
self._new_audit(peer, operation or "unknown", branch, f"error:{error.__class__.__name__}")
|
|
if isinstance(error, PermissionError):
|
|
code = "permission_denied"
|
|
elif isinstance(error, AuthorizationError):
|
|
code = "request_denied"
|
|
else:
|
|
code = "handler_failed"
|
|
if code == "handler_failed" and str(error).startswith("handler_phase="):
|
|
code = str(error)
|
|
return {
|
|
"status": "error",
|
|
"operation": operation or "unknown",
|
|
"branch": branch,
|
|
"error": code,
|
|
}
|
|
|
|
def _write_json(self, connection: socket.socket, payload: Mapping[str, Any]) -> None:
|
|
connection.sendall((json.dumps(payload) + "\n").encode("utf-8"))
|
|
|
|
def _handle_client(self, connection: socket.socket) -> None:
|
|
with connection:
|
|
try:
|
|
peer = self.peer_identity_fn(connection)
|
|
except Exception:
|
|
self._write_json(
|
|
connection,
|
|
{
|
|
"status": "error",
|
|
"operation": "connection",
|
|
"branch": "",
|
|
"error": "peer identity validation failed",
|
|
},
|
|
)
|
|
return
|
|
|
|
with connection.makefile("r", encoding="utf-8") as stream:
|
|
for raw_line in stream:
|
|
line = raw_line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
request = json.loads(line)
|
|
except json.JSONDecodeError:
|
|
self._write_json(
|
|
connection,
|
|
{
|
|
"status": "error",
|
|
"operation": "request",
|
|
"branch": "",
|
|
"error": "invalid json",
|
|
},
|
|
)
|
|
continue
|
|
if not isinstance(request, Mapping):
|
|
self._write_json(
|
|
connection,
|
|
{
|
|
"status": "error",
|
|
"operation": "request",
|
|
"branch": "",
|
|
"error": "request must be a JSON object",
|
|
},
|
|
)
|
|
continue
|
|
self._write_json(connection, self.process_request(request, peer))
|
|
|
|
def _bind_socket(self) -> socket.socket:
|
|
if self.socket_path.exists():
|
|
raise RuntimeError("governed writer socket already exists")
|
|
parent = self.socket_path.parent
|
|
if not parent.is_dir():
|
|
raise RuntimeError("governed writer socket parent is missing")
|
|
if parent.is_symlink():
|
|
raise RuntimeError("governed writer socket parent must not be a symlink")
|
|
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
server.bind(str(self.socket_path))
|
|
os.chmod(self.socket_path, 0o600)
|
|
server.listen(16)
|
|
return server
|
|
|
|
def serve(self) -> None:
|
|
"""Serve newline-delimited JSON over a filesystem AF_UNIX socket."""
|
|
server = self._bind_socket()
|
|
try:
|
|
while True:
|
|
connection, _ = server.accept()
|
|
self._handle_client(connection)
|
|
finally:
|
|
server.close()
|
|
if self.socket_path.exists():
|
|
self.socket_path.unlink()
|
|
|
|
|
|
class JsonAuditSink:
|
|
"""Append-only root-owned audit sink containing no request payloads."""
|
|
|
|
def __init__(self, path: str | os.PathLike[str]) -> None:
|
|
self.path = Path(path)
|
|
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
def append(self, record: AuditRecord) -> None:
|
|
payload = {
|
|
"authorization_sha256": record.authorization_digest,
|
|
"peer_pid": record.peer_pid,
|
|
"peer_uid": record.peer_uid,
|
|
"peer_gid": record.peer_gid,
|
|
"peer_executable": record.peer_executable,
|
|
"timestamp": record.timestamp.isoformat(),
|
|
"operation": record.operation,
|
|
"branch": record.branch,
|
|
"result": record.result,
|
|
}
|
|
line = (json.dumps(payload, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
|
|
fd = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
os.write(fd, line)
|
|
finally:
|
|
os.close(fd)
|
|
|
|
|
|
|
|
# V2R1C6 durable receipt overlay. This subclasses the original root-only
|
|
# writer in-place so the existing AF_UNIX/SO_PEERCRED and handler policy remain
|
|
# the only mutation path.
|
|
_LegacyGovernedLocalWriterService = GovernedLocalWriterService
|
|
|
|
class GovernedLocalWriterService(_LegacyGovernedLocalWriterService):
|
|
"""Existing writer with durable request receipts and safe recovery."""
|
|
|
|
def _load_state(self) -> None:
|
|
super()._load_state()
|
|
self.state.receipts = {}
|
|
if self.state_path is None or not self.state_path.exists():
|
|
return
|
|
raw = json.loads(self.state_path.read_text(encoding="utf-8"))
|
|
if not isinstance(raw, dict):
|
|
raise AuthorizationError("authorization state is invalid")
|
|
receipts = raw.get("receipts", {})
|
|
if not isinstance(receipts, dict):
|
|
raise AuthorizationError("authorization receipts are invalid")
|
|
self.state.receipts = {
|
|
str(key): dict(value)
|
|
for key, value in receipts.items()
|
|
if isinstance(value, dict)
|
|
}
|
|
|
|
def _persist_state(self) -> None:
|
|
if self.state_path is None:
|
|
return
|
|
self.state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
payload = {
|
|
"authorization_sha256": self.authorization.authorization_sha256,
|
|
"create_done": sorted(self.state.create_done),
|
|
"append_done": sorted(self.state.append_done),
|
|
"consumed": bool(self.state.consumed),
|
|
"receipts": self.state.receipts,
|
|
}
|
|
fd, temp_name = tempfile.mkstemp(prefix=".writer-state-", dir=self.state_path.parent)
|
|
try:
|
|
os.fchmod(fd, 0o600)
|
|
with os.fdopen(fd, "w", encoding="utf-8") as stream:
|
|
json.dump(payload, stream, sort_keys=True, separators=(",", ":"))
|
|
stream.write("\n")
|
|
os.replace(temp_name, self.state_path)
|
|
os.chmod(self.state_path, 0o600)
|
|
finally:
|
|
if os.path.exists(temp_name):
|
|
os.unlink(temp_name)
|
|
|
|
def _request_identity(self, request: Mapping[str, Any]) -> dict[str, str]:
|
|
canonical = json.dumps(dict(request), sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
fallback = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
request_id = request.get("request_id")
|
|
idempotency_key = request.get("idempotency_key")
|
|
request_id = request_id.strip() if isinstance(request_id, str) and request_id.strip() else "legacy-" + fallback
|
|
idempotency_key = idempotency_key.strip() if isinstance(idempotency_key, str) and idempotency_key.strip() else "legacy-" + fallback
|
|
for field_name, value in (("request_id", request_id), ("idempotency_key", idempotency_key)):
|
|
if len(value) > 200 or "\n" in value or "\r" in value:
|
|
raise AuthorizationError(field_name + " is invalid")
|
|
return {
|
|
"request_id": request_id,
|
|
"idempotency_key": idempotency_key,
|
|
"request_fingerprint": fallback,
|
|
}
|
|
|
|
def _find_receipt(self, details: Mapping[str, Any], compare_fingerprint: bool = True):
|
|
request_id = str(details["request_id"])
|
|
idempotency_key = str(details["idempotency_key"])
|
|
fingerprint = str(details.get("request_fingerprint", ""))
|
|
for receipt in self.state.receipts.values():
|
|
if receipt.get("request_id") == request_id or receipt.get("idempotency_key") == idempotency_key:
|
|
if compare_fingerprint and receipt.get("request_fingerprint") != fingerprint:
|
|
raise AuthorizationError("idempotency key reused with different request")
|
|
return receipt
|
|
return None
|
|
|
|
@staticmethod
|
|
def _receipt_response(receipt: Mapping[str, object], status: str = "receipt") -> dict[str, object]:
|
|
response = {
|
|
"status": status,
|
|
"request_id": receipt.get("request_id"),
|
|
"idempotency_key": receipt.get("idempotency_key"),
|
|
"operation": receipt.get("operation"),
|
|
"branch": receipt.get("branch", ""),
|
|
"receipt_status": receipt.get("status"),
|
|
}
|
|
for field_name in ("commit_oid", "final_head", "parent_oid", "audit_sha256", "error_code", "safe_to_retry"):
|
|
if field_name in receipt:
|
|
response[field_name] = receipt[field_name]
|
|
return response
|
|
|
|
def _new_receipt(self, details: Mapping[str, Any]) -> dict[str, object]:
|
|
return {
|
|
"request_id": str(details["request_id"]),
|
|
"idempotency_key": str(details["idempotency_key"]),
|
|
"request_fingerprint": str(details["request_fingerprint"]),
|
|
"operation": str(details["operation"]),
|
|
"branch": str(details.get("branch", "")),
|
|
"expected_head_oid": str(details.get("expected_head_oid", "")),
|
|
"status": "accepted",
|
|
"created_at": datetime.now(tz=timezone.utc).isoformat(),
|
|
"safe_to_retry": False,
|
|
}
|
|
|
|
def _new_audit(self, peer: PeerIdentity, operation: str, branch: str, result: str) -> str:
|
|
super()._new_audit(peer, operation, branch, result)
|
|
payload = {
|
|
"authorization_sha256": self.authorization.authorization_sha256,
|
|
"peer_pid": peer.pid,
|
|
"peer_uid": peer.uid,
|
|
"peer_gid": peer.gid,
|
|
"peer_executable": peer.executable,
|
|
"operation": operation,
|
|
"branch": branch,
|
|
"result": result,
|
|
}
|
|
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest()
|
|
|
|
def _mark_success(self, op: str, branch: str) -> None:
|
|
if op == "create_branch":
|
|
self.state.create_done.add(branch)
|
|
elif op in {"append_commit", "append_files", "append_batch"}:
|
|
self.state.append_done.add(branch)
|
|
# The provisioning grant is one-shot; the task writer lease itself must
|
|
# support multiple append receipts for the exact authorized branches.
|
|
self.state.consumed = False
|
|
self._persist_state()
|
|
|
|
def _validate_durable(self, raw_request: Mapping[str, Any]) -> dict[str, Any]:
|
|
operation = self._require_request_field(raw_request, "op")
|
|
identity = self._request_identity(raw_request)
|
|
if operation == "query_receipt":
|
|
return {"operation": operation, **identity}
|
|
if operation == "recover_request":
|
|
branch = self._require_request_field(raw_request, "branch")
|
|
policy = self._branch_policy(branch)
|
|
if policy.append_commit is None:
|
|
raise AuthorizationError("branch does not allow recovery")
|
|
expected = raw_request.get("expected_head_oid") or policy.append_commit.expected_head_oid
|
|
if not isinstance(expected, str) or not re.fullmatch(r"[0-9a-fA-F]{40}", expected):
|
|
raise AuthorizationError("recovery requires expected_head_oid")
|
|
return {"operation": operation, "branch": branch, "expected_head_oid": expected.lower(), **identity}
|
|
if operation not in {"create_branch", "append_commit", "append_files", "append_batch"}:
|
|
raise AuthorizationError("unsupported operation")
|
|
request = dict(raw_request)
|
|
if operation in {"append_files", "append_batch"}:
|
|
request["op"] = "append_commit"
|
|
if operation in {"append_commit", "append_files", "append_batch"}:
|
|
self.state.consumed = False
|
|
branch = request.get("branch")
|
|
removed = False
|
|
dynamic_expected = False
|
|
if isinstance(branch, str) and branch in self.state.append_done:
|
|
self.state.append_done.remove(branch)
|
|
removed = True
|
|
policy = self.authorization.branch_operations.get(branch)
|
|
if policy is not None and policy.append_commit is not None and policy.append_commit.expected_head_oid is not None:
|
|
from governed_local_writer import BranchAppendPolicy, BranchPolicy
|
|
self.authorization.branch_operations[branch] = BranchPolicy(create_branch=policy.create_branch, append_commit=BranchAppendPolicy(branch=branch, expected_head_oid=None, allowed_paths=policy.append_commit.allowed_paths))
|
|
dynamic_expected = True
|
|
try:
|
|
details = super()._validate_request(request)
|
|
finally:
|
|
if dynamic_expected:
|
|
policy = self.authorization.branch_operations.get(str(branch))
|
|
if policy is not None and policy.append_commit is not None:
|
|
from governed_local_writer import BranchAppendPolicy, BranchPolicy
|
|
self.authorization.branch_operations[str(branch)] = BranchPolicy(create_branch=policy.create_branch, append_commit=BranchAppendPolicy(branch=str(branch), expected_head_oid=str(raw_request.get("expected_head_oid") or ""), allowed_paths=policy.append_commit.allowed_paths))
|
|
if removed:
|
|
self.state.append_done.add(str(branch))
|
|
details.update(identity)
|
|
details["operation"] = operation
|
|
return details
|
|
|
|
def _recover_backend(self, details: Mapping[str, Any]) -> dict[str, object]:
|
|
if not hasattr(self.handler, "_workspace") or not hasattr(self.handler, "_git"):
|
|
raise RuntimeError("recovery backend unavailable")
|
|
workspace = self.handler._workspace(str(details["branch"]))
|
|
expected = str(details["expected_head_oid"])
|
|
current = self.handler._git(workspace, ["rev-parse", "HEAD"])
|
|
if current == expected:
|
|
return {"status": "not_started", "final_head": current}
|
|
parent = self.handler._git(workspace, ["rev-parse", "HEAD^1"])
|
|
if parent != expected:
|
|
raise RuntimeError("external branch advance during recovery")
|
|
return {"status": "applied", "commit_oid": current, "final_head": current, "parent_oid": parent}
|
|
|
|
def process_request(self, raw_request: Mapping[str, Any], peer: PeerIdentity) -> Mapping[str, Any]:
|
|
operation = raw_request.get("op") if isinstance(raw_request.get("op"), str) else "unknown"
|
|
branch = raw_request.get("branch") if isinstance(raw_request.get("branch"), str) else ""
|
|
try:
|
|
details = self._validate_durable(raw_request)
|
|
operation = str(details["operation"])
|
|
branch = str(details.get("branch", ""))
|
|
if operation == "query_receipt":
|
|
receipt = self._find_receipt(details, compare_fingerprint=False)
|
|
return self._receipt_response(receipt) if receipt is not None else {"status": "not_found", "request_id": details["request_id"], "idempotency_key": details["idempotency_key"]}
|
|
if operation == "recover_request":
|
|
receipt = self._find_receipt(details, compare_fingerprint=False)
|
|
if receipt is None:
|
|
return {"status": "not_found", "request_id": details["request_id"], "idempotency_key": details["idempotency_key"], "safe_to_retry": True}
|
|
if receipt.get("status") == "applied":
|
|
return self._receipt_response(receipt)
|
|
recovered = self._recover_backend(details)
|
|
if recovered.get("status") == "applied":
|
|
receipt.update({"status": "applied", "commit_oid": recovered.get("commit_oid"), "final_head": recovered.get("final_head"), "parent_oid": recovered.get("parent_oid"), "safe_to_retry": False})
|
|
self._mark_success("append_commit", branch)
|
|
return self._receipt_response(receipt)
|
|
receipt["safe_to_retry"] = True
|
|
self._persist_state()
|
|
return self._receipt_response(receipt)
|
|
receipt = self._find_receipt(details)
|
|
if receipt is not None:
|
|
return self._receipt_response(receipt)
|
|
receipt = self._new_receipt(details)
|
|
self.state.receipts[str(details["request_id"])] = receipt
|
|
self._persist_state()
|
|
if operation == "create_branch":
|
|
result = self.handler.create_branch(CreateBranchRequest(branch=branch, base_oid=str(details["base_oid"]), head_oid=str(details["head_oid"]) if details.get("head_oid") is not None else None, authorization_digest=self.authorization.authorization_sha256))
|
|
commit_oid = str(result.get("head_oid", ""))
|
|
parent_oid = str(result.get("parent_oid", ""))
|
|
audit_sha = self._new_audit(peer, operation, branch, "ok")
|
|
receipt.update({"status": "applied", "commit_oid": commit_oid, "final_head": commit_oid, "parent_oid": parent_oid, "audit_sha256": audit_sha, "safe_to_retry": False})
|
|
self._mark_success("create_branch", branch)
|
|
return self._receipt_response(receipt, status="ok") | {"result": "applied"}
|
|
result = self.handler.append_commit(AppendCommitRequest(branch=branch, expected_head_oid=str(details["expected_head_oid"]), changes=tuple(details["changes"]), authorization_digest=self.authorization.authorization_sha256, commit_message=str(details["commit_message"])))
|
|
commit_oid = str(result.get("head_oid", ""))
|
|
parent_oid = str(result.get("parent_oid", ""))
|
|
audit_sha = self._new_audit(peer, operation, branch, "ok")
|
|
receipt.update({"status": "applied", "commit_oid": commit_oid, "final_head": commit_oid, "parent_oid": parent_oid, "audit_sha256": audit_sha, "safe_to_retry": False})
|
|
self._mark_success("append_commit", branch)
|
|
return self._receipt_response(receipt, status="ok") | {"result": "applied"}
|
|
except Exception as error:
|
|
try:
|
|
identity = self._request_identity(raw_request)
|
|
receipt = self.state.receipts.get(identity["request_id"])
|
|
if receipt is not None and receipt.get("status") == "accepted":
|
|
receipt.update({"status": "failed", "error_code": error.__class__.__name__, "safe_to_retry": False})
|
|
self._persist_state()
|
|
except Exception:
|
|
pass
|
|
self._new_audit(peer, operation, branch, "error:" + error.__class__.__name__)
|
|
return {"status": "error", "operation": operation, "branch": branch, "error": "request_denied" if isinstance(error, AuthorizationError) else "handler_failed"}
|
|
|
|
def _parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="AIOS governed local writer")
|
|
parser.add_argument("--socket", required=True)
|
|
parser.add_argument("--authorization", required=True)
|
|
parser.add_argument("--state", required=True)
|
|
parser.add_argument("--audit", required=True)
|
|
parser.add_argument("--repair-branch", required=True)
|
|
parser.add_argument("--repair-workspace", required=True)
|
|
parser.add_argument("--candidate-branch", required=True)
|
|
parser.add_argument("--candidate-workspace", required=True)
|
|
parser.add_argument("--bare-repo", required=True)
|
|
parser.add_argument("--api-source", required=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
if hasattr(os, "geteuid") and os.geteuid() != 0:
|
|
print("governed local writer requires uid 0", file=sys.stderr)
|
|
return 2
|
|
args = _parse_args()
|
|
authorization = load_authorization_file(args.authorization)
|
|
handler = ExistingGitOpsHandler(
|
|
workspace_by_branch={
|
|
args.repair_branch: args.repair_workspace,
|
|
args.candidate_branch: args.candidate_workspace,
|
|
},
|
|
bare_repo=args.bare_repo,
|
|
api_source=args.api_source,
|
|
)
|
|
service = GovernedLocalWriterService(
|
|
args.socket,
|
|
authorization,
|
|
handler,
|
|
audit_sink=JsonAuditSink(args.audit),
|
|
state_path=args.state,
|
|
)
|
|
service.serve()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover - systemd entrypoint
|
|
raise SystemExit(main())
|