feat(delivery): initialize PostgreSQL in a bounded ordinary unit
This commit is contained in:
@@ -22,6 +22,7 @@ service_install = importlib.import_module("scripts.release.service_install")
|
||||
factory_service = importlib.import_module("scripts.release.factory_service")
|
||||
dependencies = importlib.import_module("scripts.release.dependencies")
|
||||
dependency_install = importlib.import_module("scripts.release.dependency_install")
|
||||
dependency_initialize = importlib.import_module("scripts.release.dependency_initialize")
|
||||
|
||||
|
||||
def test_delivery_authorities_and_layout():
|
||||
@@ -687,3 +688,40 @@ def test_dependency_receipt_excludes_foreign_files_and_health_claims():
|
||||
def test_dependency_pointer_does_not_adopt_unknown_units_or_paths(target):
|
||||
with pytest.raises(policy.DeliveryError, match="unknown dependency unit"):
|
||||
dependency_install.previous_batch(target, "kf-postgres.service")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"contents",
|
||||
[
|
||||
b"",
|
||||
b"123\n",
|
||||
b"b" * 64,
|
||||
b"b" * 64 + b"\nextra\n",
|
||||
b"b" * 32 + b" " + b"b" * 31 + b"\n",
|
||||
],
|
||||
)
|
||||
def test_database_initializer_refuses_empty_multiline_or_noncanonical_password(contents):
|
||||
with pytest.raises(policy.DeliveryError, match="bootstrap password"):
|
||||
dependency_initialize.validate_password(contents)
|
||||
|
||||
|
||||
def test_database_initialization_receipt_does_not_prove_migration_or_authentication():
|
||||
receipt = {
|
||||
"schema_version": 1,
|
||||
"source_sha": "b" * 40,
|
||||
"configuration_id": "cfg-" + "e" * 24,
|
||||
"cluster_identity": {"device": 5, "inode": 900, "major_version": 18, "uid": 1203},
|
||||
"bootstrap_sha256": "f" * 64,
|
||||
"initialized": True,
|
||||
"migrated": False,
|
||||
"services_started": False,
|
||||
"execution_verified": False,
|
||||
}
|
||||
dependency_initialize.validate_receipt(receipt)
|
||||
receipt["migrated"] = True
|
||||
with pytest.raises(policy.DeliveryError, match="binding"):
|
||||
dependency_initialize.validate_receipt(receipt)
|
||||
receipt["migrated"] = False
|
||||
receipt["cluster_identity"]["uid"] = 0
|
||||
with pytest.raises(policy.DeliveryError, match="cluster identity"):
|
||||
dependency_initialize.validate_receipt(receipt)
|
||||
|
||||
@@ -223,3 +223,23 @@ System unit pointers may replace only recorded root-owned dependency snapshots.
|
||||
The command retains old batches, verifies loaded/inactive metadata, starts no
|
||||
service and initializes no database. Cluster initialization, actual effective
|
||||
limits, broker authorization and startup/readiness still require VM acceptance.
|
||||
|
||||
`python3.13 -m scripts.release.dependency_initialize --password-file PRIVATE_FILE
|
||||
--password-sha256 SHA` is the separate explicit PostgreSQL cluster initialization
|
||||
step. It requires the selected dependency snapshot, stopped services, the dedicated
|
||||
storage mount and an operator-supplied hexadecimal bootstrap password file. Only
|
||||
a fresh unrecorded target is initialized. The fixed ordinary PostgreSQL identity
|
||||
runs `initdb` in a transient network-isolated unit with node/service/process/disk
|
||||
budgets checked before execution. systemd passes the password as a private file
|
||||
credential; its bytes are neither unit arguments nor environment values. Both
|
||||
local and host authentication use SCRAM; incomplete data is retained for explicit
|
||||
recovery, not silently deleted or reused. The initializer never starts a database
|
||||
server or migrates business tables; Alembic and role provisioning remain separate.
|
||||
|
||||
A root-owned initialization receipt binds the physical cluster identity and initial
|
||||
credential fingerprint. Repeat initialization requires that recorded identity and
|
||||
does not reset a password or claim successful DB authentication. The operator
|
||||
reads the transient unit result and confirms stop; foreign/residual units and
|
||||
unrecorded/partial targets are refused. These are implementation interfaces, not
|
||||
actual VM acceptance. See [PostgreSQL initdb](https://www.postgresql.org/docs/18/app-initdb.html)
|
||||
and [systemd credentials](https://systemd.io/CREDENTIALS/) for the underlying interfaces.
|
||||
|
||||
@@ -75,7 +75,9 @@ def validate_storage(name: str, observation: dict) -> None:
|
||||
raise DeliveryError("dedicated hard dependency storage budget differs")
|
||||
|
||||
|
||||
def check_runtime(name: str) -> dict:
|
||||
def check_runtime(name: str, *, initializing: bool = False) -> dict:
|
||||
if initializing and name != "postgres":
|
||||
raise DeliveryError("unexpected dependency initializer")
|
||||
uid = {"postgres": 1203, "rabbitmq": 1204}[name]
|
||||
if (
|
||||
os.getuid() != uid
|
||||
@@ -92,7 +94,8 @@ def check_runtime(name: str) -> dict:
|
||||
):
|
||||
raise DeliveryError("dependency privilege boundary differs")
|
||||
group = preflight.current_cgroup()
|
||||
if str(group) != "/sys/fs/cgroup/kfonlinejudge.slice/kf-" + name + ".service":
|
||||
unit = name + "-initialize" if initializing else name
|
||||
if str(group) != "/sys/fs/cgroup/kfonlinejudge.slice/kf-" + unit + ".service":
|
||||
raise DeliveryError("dependency cgroup identity differs")
|
||||
limits = {
|
||||
key: (group / key).read_text().strip()
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
"""Explicit ordinary PostgreSQL initialization; no schema migration or server start."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
from . import configuration, dependencies, dependency_install, install, preflight, service_install
|
||||
from .policy import DeliveryError, canonical, digest, fields, read_json, sha256
|
||||
from .services import python_environment
|
||||
|
||||
UNIT = "kf-postgres-initialize.service"
|
||||
DATA = Path(dependencies.STORAGE["postgres"]["path"]) / "data"
|
||||
RECEIPT = dependency_install.ROOT / "postgres.initialization.json"
|
||||
CREDENTIAL = Path("/run/credentials") / UNIT / "bootstrap"
|
||||
|
||||
|
||||
def validate_password(contents: bytes) -> None:
|
||||
if re.fullmatch(rb"[0-9a-f]{64}\n", contents) is None:
|
||||
raise DeliveryError("explicit 256-bit hexadecimal bootstrap password required")
|
||||
|
||||
|
||||
def cluster_identity() -> dict:
|
||||
root = DATA.lstat()
|
||||
version = DATA / "PG_VERSION"
|
||||
info = version.lstat()
|
||||
if (
|
||||
not stat.S_ISDIR(root.st_mode)
|
||||
or (root.st_uid, root.st_gid, stat.S_IMODE(root.st_mode)) != (1203, 1203, 0o700)
|
||||
or not stat.S_ISREG(info.st_mode)
|
||||
or info.st_nlink != 1
|
||||
or (info.st_uid, info.st_gid, stat.S_IMODE(info.st_mode)) != (1203, 1203, 0o600)
|
||||
or info.st_size != 3
|
||||
or version.read_bytes() != b"18\n"
|
||||
):
|
||||
raise DeliveryError("initialized PostgreSQL identity/version differs")
|
||||
return {"device": root.st_dev, "inode": root.st_ino, "major_version": 18, "uid": 1203}
|
||||
|
||||
|
||||
def validate_receipt(value: dict) -> dict:
|
||||
fields(
|
||||
value,
|
||||
{
|
||||
"schema_version",
|
||||
"source_sha",
|
||||
"configuration_id",
|
||||
"cluster_identity",
|
||||
"bootstrap_sha256",
|
||||
"initialized",
|
||||
"migrated",
|
||||
"services_started",
|
||||
"execution_verified",
|
||||
},
|
||||
"database initialization receipt",
|
||||
)
|
||||
if (
|
||||
type(value["schema_version"]) is not int
|
||||
or value["schema_version"] != 1
|
||||
or not isinstance(value["source_sha"], str)
|
||||
or not re.fullmatch(r"[0-9a-f]{40}", value["source_sha"])
|
||||
or not isinstance(value["configuration_id"], str)
|
||||
or not re.fullmatch(r"cfg-[0-9a-f]{24}", value["configuration_id"])
|
||||
or value["initialized"] is not True
|
||||
or any(
|
||||
value[key] is not False
|
||||
for key in ("migrated", "services_started", "execution_verified")
|
||||
)
|
||||
):
|
||||
raise DeliveryError("initialization receipt binding differs")
|
||||
sha256(value["bootstrap_sha256"])
|
||||
identity = fields(
|
||||
value["cluster_identity"], {"device", "inode", "major_version", "uid"}, "cluster identity"
|
||||
)
|
||||
if (
|
||||
any(type(identity[key]) is not int or identity[key] < 1 for key in identity)
|
||||
or identity["major_version"] != 18
|
||||
or identity["uid"] != 1203
|
||||
):
|
||||
raise DeliveryError("initialization receipt cluster identity differs")
|
||||
return value
|
||||
|
||||
|
||||
def run_in_unit() -> dict:
|
||||
observed = dependencies.check_runtime("postgres", initializing=True)
|
||||
interfaces = Path("/proc/net/dev").read_text().splitlines()[2:]
|
||||
if {line.split(":", 1)[0].strip() for line in interfaces} != {"lo"}:
|
||||
raise DeliveryError("network-isolated database initializer required")
|
||||
credential = CREDENTIAL.lstat()
|
||||
if (
|
||||
not stat.S_ISREG(credential.st_mode)
|
||||
or credential.st_nlink != 1
|
||||
or credential.st_uid not in {0, 1203}
|
||||
or stat.S_IMODE(credential.st_mode) not in {0o400, 0o600}
|
||||
or credential.st_size != 65
|
||||
):
|
||||
raise DeliveryError("private database initialization credential required")
|
||||
validate_password(CREDENTIAL.read_bytes())
|
||||
if DATA.exists() or DATA.is_symlink():
|
||||
raise DeliveryError("database initializer refuses any existing target")
|
||||
preflight.protected(Path("/usr/bin/initdb"))
|
||||
env = {"PATH": "/usr/bin:/bin", "LC_ALL": "C.UTF-8", "HOME": "/nonexistent"}
|
||||
version = subprocess.run(
|
||||
["/usr/bin/initdb", "--version"], capture_output=True, timeout=5, env=env
|
||||
)
|
||||
if version.returncode or version.stdout.strip() != b"initdb (PostgreSQL) 18.6":
|
||||
raise DeliveryError("database initializer tool version differs")
|
||||
result = subprocess.run(
|
||||
[
|
||||
"/usr/bin/initdb",
|
||||
"--pgdata=" + str(DATA),
|
||||
"--username=postgres",
|
||||
"--pwfile=" + str(CREDENTIAL),
|
||||
"--auth-host=scram-sha-256",
|
||||
"--auth-local=scram-sha-256",
|
||||
"--encoding=UTF8",
|
||||
"--locale=C.UTF-8",
|
||||
"--data-checksums",
|
||||
"--no-clean",
|
||||
"--no-instructions",
|
||||
"--set=shared_buffers=64MB",
|
||||
"--set=max_connections=32",
|
||||
"--set=work_mem=1MB",
|
||||
"--set=maintenance_work_mem=32MB",
|
||||
"--set=max_wal_senders=0",
|
||||
"--set=listen_addresses=",
|
||||
],
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=165,
|
||||
)
|
||||
if result.returncode:
|
||||
raise DeliveryError("ordinary database initialization failed; partial data retained")
|
||||
return {
|
||||
**observed,
|
||||
"initialized": True,
|
||||
"cluster_identity": cluster_identity(),
|
||||
"services_started": False,
|
||||
}
|
||||
|
||||
|
||||
def status() -> dict:
|
||||
return dict(
|
||||
line.split("=", 1)
|
||||
for line in service_install.systemctl(
|
||||
"show",
|
||||
UNIT,
|
||||
"--property=LoadState,ActiveState,SubState,MainPID,Result,Description,Transient",
|
||||
).splitlines()
|
||||
if "=" in line
|
||||
)
|
||||
|
||||
|
||||
def initialize(password_file: Path, password_sha: str) -> dict:
|
||||
if os.getuid() != 0 or os.geteuid() != 0:
|
||||
raise DeliveryError("explicit database initialization requires root operator")
|
||||
preflight.protected(password_file)
|
||||
configuration.private(password_file)
|
||||
if (
|
||||
not re.fullmatch(r"/[A-Za-z0-9_./-]+", str(password_file))
|
||||
or password_file.stat().st_size != 65
|
||||
or digest(password_file) != sha256(password_sha)
|
||||
):
|
||||
raise DeliveryError("private database bootstrap fingerprint/path differs")
|
||||
validate_password(password_file.read_bytes())
|
||||
selected, original = service_install.current_receipt()
|
||||
payload = install.RELEASES / selected["release_id"] / "payload"
|
||||
for name in ("dependencies.py", "dependency_install.py", "dependency_initialize.py"):
|
||||
shipped = payload / "delivery/scripts/release" / name
|
||||
preflight.protected(shipped)
|
||||
if digest(shipped) != digest(Path(__file__).parent / name):
|
||||
raise DeliveryError("database initializer differs from selected delivery")
|
||||
dependency_install.require_stopped()
|
||||
dependency_install.check_accounts()
|
||||
dependencies.storage_observation("postgres")
|
||||
if dependency_install.known_link(
|
||||
service_install.SYSTEM_UNITS / "kf-postgres.service"
|
||||
) != dependency_install.ROOT / (selected["release_id"] + "." + selected["configuration_id"]):
|
||||
raise DeliveryError("database dependency snapshot is not the selected binding")
|
||||
current = status()
|
||||
if (
|
||||
current.get("LoadState") != "not-found"
|
||||
or current.get("ActiveState") != "inactive"
|
||||
or current.get("MainPID") != "0"
|
||||
):
|
||||
raise DeliveryError("unknown or residual database initialization unit")
|
||||
if RECEIPT.exists() or RECEIPT.is_symlink():
|
||||
preflight.protected(RECEIPT)
|
||||
configuration.private(RECEIPT)
|
||||
recorded = validate_receipt(read_json(RECEIPT, 8192))
|
||||
if recorded["bootstrap_sha256"] != password_sha or canonical(
|
||||
recorded["cluster_identity"]
|
||||
) != canonical(cluster_identity()):
|
||||
raise DeliveryError(
|
||||
"database initialization identity/credential changed; explicit recovery required"
|
||||
)
|
||||
return {
|
||||
"initialized": True,
|
||||
"reused": True,
|
||||
"credentials_authenticated": False,
|
||||
"migrated": False,
|
||||
"services_started": False,
|
||||
"execution_verified": False,
|
||||
}
|
||||
if DATA.exists() or DATA.is_symlink():
|
||||
raise DeliveryError("unrecorded or partial database target; explicit recovery required")
|
||||
description = (
|
||||
"KF PostgreSQL initialization "
|
||||
+ original["source_sha"]
|
||||
+ "."
|
||||
+ selected["configuration_id"]
|
||||
)
|
||||
command = [
|
||||
"/usr/bin/systemd-run",
|
||||
"--quiet",
|
||||
"--unit=" + UNIT,
|
||||
"--description=" + description,
|
||||
"--slice=kfonlinejudge.slice",
|
||||
]
|
||||
properties = {
|
||||
"Type": "exec",
|
||||
"RemainAfterExit": "yes",
|
||||
"User": "postgres",
|
||||
"Group": "postgres",
|
||||
"UMask": "0077",
|
||||
"MemoryMax": "512M",
|
||||
"MemorySwapMax": "0",
|
||||
"TasksMax": "64",
|
||||
"CPUQuota": "100%",
|
||||
"LimitCORE": "0",
|
||||
"LimitFSIZE": "256M",
|
||||
"LimitNOFILE": "256",
|
||||
"LimitSTACK": "16M",
|
||||
"RuntimeMaxSec": "180",
|
||||
"TimeoutStartSec": "10",
|
||||
"TimeoutStopSec": "15",
|
||||
"Restart": "no",
|
||||
"KillMode": "control-group",
|
||||
"NoNewPrivileges": "yes",
|
||||
"CapabilityBoundingSet": "",
|
||||
"AmbientCapabilities": "",
|
||||
"ProtectSystem": "strict",
|
||||
"ProtectHome": "yes",
|
||||
"ProtectControlGroups": "yes",
|
||||
"PrivateDevices": "yes",
|
||||
"PrivateTmp": "yes",
|
||||
"PrivateNetwork": "yes",
|
||||
"RestrictAddressFamilies": "AF_UNIX",
|
||||
"ReadWritePaths": dependencies.STORAGE["postgres"]["path"],
|
||||
"RequiresMountsFor": dependencies.STORAGE["postgres"]["path"],
|
||||
"LoadCredential": "bootstrap:" + str(password_file),
|
||||
}
|
||||
for key, value in properties.items():
|
||||
command += ["--property=" + key + "=" + value]
|
||||
command += [
|
||||
"/usr/bin/env",
|
||||
"-i",
|
||||
"PATH=/usr/bin:/bin",
|
||||
"LC_ALL=C.UTF-8",
|
||||
*python_environment(payload).split(),
|
||||
str(payload / "python/bin/python3.13"),
|
||||
"-s",
|
||||
"-P",
|
||||
"-m",
|
||||
"scripts.release.dependency_initialize",
|
||||
"--run-in-unit",
|
||||
]
|
||||
dispatched = False
|
||||
try:
|
||||
dispatched = True
|
||||
result = subprocess.run(
|
||||
command,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
timeout=15,
|
||||
env={"PATH": "/usr/bin:/bin", "LC_ALL": "C"},
|
||||
)
|
||||
if result.returncode:
|
||||
raise DeliveryError("database initialization dispatch failed")
|
||||
deadline = time.monotonic() + 185
|
||||
while True:
|
||||
actual = status()
|
||||
if actual.get("Description") != description or actual.get("Transient") != "yes":
|
||||
raise DeliveryError("database initialization unit ownership differs")
|
||||
if actual.get("SubState") == "exited" and actual.get("ActiveState") == "active":
|
||||
break
|
||||
if (
|
||||
actual.get("ActiveState") not in {"active", "activating"}
|
||||
or time.monotonic() >= deadline
|
||||
):
|
||||
raise DeliveryError(
|
||||
"database initialization failed/expired; partial state retained"
|
||||
)
|
||||
time.sleep(0.5)
|
||||
if actual.get("Result") != "success" or actual.get("MainPID") != "0":
|
||||
raise DeliveryError("database initialization unit failed; partial state retained")
|
||||
recorded = validate_receipt(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"source_sha": original["source_sha"],
|
||||
"configuration_id": selected["configuration_id"],
|
||||
"cluster_identity": cluster_identity(),
|
||||
"bootstrap_sha256": password_sha,
|
||||
"initialized": True,
|
||||
"migrated": False,
|
||||
"services_started": False,
|
||||
"execution_verified": False,
|
||||
}
|
||||
)
|
||||
if install.selection() != selected:
|
||||
raise DeliveryError("selected binding changed during database initialization")
|
||||
install.exclusive_file(RECEIPT, canonical(recorded), 0o600)
|
||||
finally:
|
||||
if dispatched:
|
||||
actual = status()
|
||||
if actual.get("LoadState") != "not-found":
|
||||
if actual.get("Description") != description or actual.get("Transient") != "yes":
|
||||
raise DeliveryError("foreign initialization unit; refused cleanup")
|
||||
service_install.systemctl("stop", UNIT)
|
||||
actual = status()
|
||||
if actual.get("ActiveState") != "inactive" or actual.get("MainPID") != "0":
|
||||
raise DeliveryError("database initialization cleanup unconfirmed")
|
||||
return {
|
||||
"initialized": True,
|
||||
"reused": False,
|
||||
"credentials_authenticated": False,
|
||||
"migrated": False,
|
||||
"services_started": False,
|
||||
"execution_verified": False,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Explicit bounded PostgreSQL initialization")
|
||||
parser.add_argument("--password-file", type=Path)
|
||||
parser.add_argument("--password-sha256")
|
||||
parser.add_argument("--run-in-unit", action="store_true")
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
if args.run_in_unit:
|
||||
if args.password_file is not None or args.password_sha256 is not None:
|
||||
raise DeliveryError("unexpected initialization unit override")
|
||||
result = run_in_unit()
|
||||
else:
|
||||
if args.password_file is None or args.password_sha256 is None:
|
||||
raise DeliveryError("explicit private bootstrap inputs required")
|
||||
result = initialize(args.password_file, args.password_sha256)
|
||||
print(json.dumps(result, sort_keys=True))
|
||||
except (OSError, ValueError, subprocess.SubprocessError) as error:
|
||||
parser.exit(
|
||||
1,
|
||||
"Database initialization refused: "
|
||||
+ (str(error) if isinstance(error, DeliveryError) else type(error).__name__)
|
||||
+ "\n",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user