Postgres audit_ledger append-only guard gains exactly one execution entry: cloud/ledger_pg_guard.apply_pg_guard, runnable as 'python3 -m cloud.ledger_pg_guard' (explicit ops command, not a migration script). It executes PG_GUARD_SQL verbatim from the constant -- no second copy of the SQL anywhere -- refuses non-PgConnection targets, does not commit itself (caller/CLI commits), and refuses with exit 2 on a non-postgres backend so a no-PG environment can never claim hardening. ensure_schema still skips PG triggers; POSTGRES_SCHEMA still builds only the table. Tests lock all of this with a recording fake connection (no PG execution is faked), a source scan asserting only ledger.py and ledger_pg_guard.py reference the constant, and CLI dry-run/refusal cases. Real-PG syntax and UPDATE/DELETE blocking verification is explicitly left to B5, not claimed here. (cherry picked from commit b1f6c9158d628186965a9e57576ff8c9500e3f73)
130 lines
5.3 KiB
Python
130 lines
5.3 KiB
Python
"""Postgres 审计账本只追加护栏:唯一受控执行入口(A2,代码侧)。
|
||
|
||
本模块是 ``cloud/ledger.py`` 的 ``PG_GUARD_SQL`` 常量在 Postgres
|
||
上的唯一执行入口——二选一里选的是“显式运维命令”,不是迁移脚本:
|
||
|
||
- 函数入口:``apply_pg_guard(conn)``,逐条执行且仅执行
|
||
``PG_GUARD_SQL``(从常量引用,不另写、不复制、不拼接 SQL)。
|
||
入口本身不 commit:调用方在自己的事务边界提交(与
|
||
``ledger.ensure_schema`` 的约定一致)。
|
||
- 命令入口:``python3 -m cloud.ledger_pg_guard``(``main``),
|
||
连接参数只走 ``CLOUD_DB_*`` 环境变量(经
|
||
``cloud.db.PostgresDatabase``),不接受命令行密码参数。
|
||
|
||
受控含义(grep 可证,全仓库除本模块外无其它执行路径):
|
||
- ``ledger.ensure_schema`` 在 PG 适配连接上跳过触发器建;
|
||
``cloud/db.py`` 的 POSTGRES_SCHEMA 只建 audit_ledger 表,
|
||
不建触发器;迁移脚本不执行本常量。
|
||
- 入口先验连接类型:不是 ``PgConnection``(按类名判定,与
|
||
``ledger._is_pg_conn`` 同款)即拒绝执行,绝不把 PG 方言 SQL
|
||
发往 SQLite 连接。
|
||
- 无 PG 环境下入口不得 claim 已加固:``main`` 在
|
||
``CLOUD_DB_BACKEND`` 不是 postgres/postgresql 时直接以
|
||
exit 2 拒绝(不连接、不执行、不打印“已应用”);``--dry-run``
|
||
只打印将执行的常量语句,不连接、不执行。
|
||
|
||
诚实边界(A2 只做代码侧,真实 PG 验证待 B5):
|
||
- 本机无真实 PG,``PG_GUARD_SQL`` 的语法与 UPDATE/DELETE 阻挡
|
||
均未在真实 PG 上验证;B5 环境到齐后须在真实 PG 上跑本命令
|
||
并实测 UPDATE/DELETE 被拒绝,之后才可认“已加固”。在此之前,
|
||
任何“PG 已加固”的表述都是冒充。
|
||
- 本模块不打印、不返回任何密码或密钥:输出只有语句计数与
|
||
DDL 常量本身。
|
||
|
||
只用标准库 + cloud.db / cloud.ledger,不引第三方。
|
||
"""
|
||
|
||
import argparse
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
try:
|
||
from cloud.db import PostgresDatabase
|
||
from cloud.ledger import PG_GUARD_SQL
|
||
except ImportError: # 直接以脚本方式从仓库根外运行时的回退。
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(
|
||
os.path.abspath(__file__))))
|
||
from cloud.db import PostgresDatabase
|
||
from cloud.ledger import PG_GUARD_SQL
|
||
|
||
|
||
def apply_pg_guard(conn):
|
||
"""在给定 PG 适配连接上执行只追加护栏(唯一执行入口)。
|
||
|
||
conn 须是 PgConnection(按类名判定);其它连接一律拒绝,
|
||
绝不把 PG 方言发往 SQLite。按 PG_GUARD_SQL 原顺序逐条执行,
|
||
返回执行过的语句元组(即常量本身,供调用方/测试比对)。
|
||
不 commit:调用方提交。
|
||
"""
|
||
if type(conn).__name__ != "PgConnection":
|
||
raise TypeError(
|
||
"apply_pg_guard requires a PgConnection, got %s; "
|
||
"refusing to run Postgres guard SQL on a non-PG connection"
|
||
% type(conn).__name__)
|
||
for statement in PG_GUARD_SQL:
|
||
conn.execute(statement)
|
||
return tuple(PG_GUARD_SQL)
|
||
|
||
|
||
def main(argv=None):
|
||
"""运维命令:``python3 -m cloud.ledger_pg_guard [--dry-run]``。
|
||
|
||
返回进程退出码:0 已应用(或 dry-run 展示完毕),1 执行失败,
|
||
2 用法/环境错误(非 PG 后端、非 PG 连接——一律不 claim 已应用)。
|
||
"""
|
||
parser = argparse.ArgumentParser(
|
||
description="Apply the audit_ledger append-only guard "
|
||
"(PG_GUARD_SQL) to Postgres. Single controlled "
|
||
"entry point; see module docstring.")
|
||
parser.add_argument(
|
||
"--dry-run", action="store_true",
|
||
help="Print the statements that would run, without "
|
||
"connecting or executing anything.")
|
||
args = parser.parse_args(argv)
|
||
if args.dry_run:
|
||
print(json.dumps({"applied": False, "dry_run": True,
|
||
"statements": list(PG_GUARD_SQL)},
|
||
ensure_ascii=False, indent=2))
|
||
return 0
|
||
backend = (os.environ.get("CLOUD_DB_BACKEND") or "").strip().lower()
|
||
if backend not in ("postgres", "postgresql"):
|
||
print("refused: CLOUD_DB_BACKEND is not postgres "
|
||
"(got %r); nothing applied, nothing claimed"
|
||
% backend, file=sys.stderr)
|
||
return 2
|
||
try:
|
||
conn = PostgresDatabase().connect()
|
||
except Exception as exc:
|
||
print("failed to connect to Postgres: %s" % exc,
|
||
file=sys.stderr)
|
||
return 1
|
||
try:
|
||
if type(conn).__name__ != "PgConnection":
|
||
print("refused: connection is not a PgConnection "
|
||
"(got %s); nothing applied"
|
||
% type(conn).__name__, file=sys.stderr)
|
||
return 2
|
||
statements = apply_pg_guard(conn)
|
||
conn.commit()
|
||
except Exception as exc:
|
||
try:
|
||
conn.rollback()
|
||
except Exception:
|
||
pass
|
||
print("failed to apply PG guard: %s" % exc, file=sys.stderr)
|
||
return 1
|
||
finally:
|
||
try:
|
||
conn.close()
|
||
except Exception:
|
||
pass
|
||
print(json.dumps({"applied": True, "dry_run": False,
|
||
"statements": list(statements)},
|
||
ensure_ascii=False, indent=2))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|