仅向结算报表提供所需数据

PostgreSQLBeginner
立即练习

介绍

MarketLedger 需要为小型集市的商户生成结算报表。报表应该读取昨天的合计,而非客户邮箱或银行参考信息,也不应该写入订单。当前账户创建策略授予的权限超出了这项工作所需。

你将收紧策略,通过 OpenBao 获取真实的临时 PostgreSQL 登录账户,并使用提供的查询保存完整的制表符分隔报表。你需要掌握基本终端和路径操作。SQL 权限、凭据文件和相关命令选项都会在使用前讲解;无需编写 Python 或拥有云账户。

查看商店和它需要的报表

本步骤先查看正常业务和报表,再改变权限。整个实验都使用 Terminal。cd 改变目录;绝对路径可以从任何位置使用:

cd /home/labex/project/market-report

PostgreSQL 是准备好的数据库。OpenBao 是准备好的秘密管理服务,可以请求数据库创建有时限的新登录账户。它们是在本实验容器中运行的真实进程,并非托管数据库或生产环境的秘密管理部署。

准备好的 shop.py 是正常商店服务的小型客户端。运行它无需编写 Python。list 显示今天的订单:

python3 shop.py list

初始预期为空列表 []。下一笔正常订单。--order 指定订单,--vendor 指定商户,--cents 以整数分为单位指定金额:

python3 shop.py place --order O201 --vendor V101 --cents 600

预期存储一笔合成交易日期 2026-10-29 的已付款订单。重复命令时,服务会拒绝重复 ID;使用 list 查看现有订单,不要删除或覆盖它。

结算涵盖前一天 2026-10-28。今天的新订单不能改变昨天的合计。cat 显示文本文件;查看准备好的正常示例报表:

cat demo.tsv

预期有三行商户数据:

vendor_id vendor_name paid_orders total_cents
V101 Birch Pantry 2 4700
V102 Elm Bakery 1 2000
V103 Maple Kitchen 1 2700

分隔符是制表符,所以文件名以 .tsv 结尾。四笔已付款订单共计 9400 分。已取消的 Elm 订单不在其中。示例由单独准备的读取账户读取;复制它不能证明你的新数据库身份能够运行报表。

视图是一个有名称的数据库查询,提供选定结果。reporting.daily_settlement 将商户名称与已付款订单合计关联起来,不包含客户邮箱和银行参考信息。原始记录保留在私有的 public.orders 和 public.vendors 表中。这里 reporting 和 public 是用于分组数据库对象的模式名称;public 这个词不表示所有角色都应该有访问权限。

你可以编辑 role.sql、注册 settlement-report OpenBao 角色、获取该角色凭据,并创建报表和凭据文件。保持商店、原始数据库记录、另一个报表角色、提供的查询、环境文件、操作员令牌和预置运行程序不变。无需数据库管理员密码或 OpenBao root 令牌。

出现普通的 No such file or directory 错误时,用上面的绝对路径 cd 返回,用 ls 检查实际文件名,然后重试。不要为修复拼写错误重置环境。

将账户创建策略限定于结算视图

本步骤去除报表不需要的权限。从 Terminal 的 /home/labex/project/market-report 目录开始。

阅读当前账户创建语句:

cat role.sql

OpenBao 签发凭据时替换三个占位符:{{name}} 变为新的数据库用户名,{{password}} 变为生成的密码,{{expiration}} 变为账户到期时间。保留占位符;不要替换为自己固定的用户名或密码。

CREATE ROLE ... WITH LOGIN 创建 PostgreSQL 登录账户。VALID UNTIL 限定其密码可以验证新连接的时间。它不会终止所有已打开的连接。下一个引导实验会研究显式撤销和租约自然到期。

GRANT 授予权限。USAGE ON SCHEMA reporting 允许查找该模式中的对象。SELECT ON reporting.daily_settlement 允许读取这一个视图。当前对 public 的额外授权允许读取私有表并修改订单;报表两者都不需要。

最后的 ALTER ROLE ... SET log_statement = 'all' 为每个新创建的报表登录账户启用原生 SQL 审计。保留它,让该身份执行的查询可以归因。含密码的管理账户创建语句不会通过此逐登录账户设置记入日志。预置的 OpenBao 审计日志也会对秘密值进行哈希处理。

用以下更窄的语句替换文件。cat > role.sql 将提供的文本重定向到该文件。<<'ROLESQL' 开始 here-document;带引号的标记使其中的文本原样保留,直到仅包含 ROLESQL 的一行:

cat > role.sql <<'ROLESQL'
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT USAGE ON SCHEMA reporting TO "{{name}}";
GRANT SELECT ON reporting.daily_settlement TO "{{name}}";
ALTER ROLE "{{name}}" SET log_statement = 'all';
ROLESQL

重新读取:

cat role.sql

角色具有生成的登录信息、有界密码寿命、模式查找权限,以及对单一视图的读取权限。它没有原始表访问权限、写授权或管理员属性。视图权限在这里有用,是因为给定视图刻意省略了私有数据;暴露这些私有字段的视图无法提供同样边界。

如果因遗漏结束标记导致终端等待更多输入,在单独一行输入 ROLESQL。若保存文件不完整,重复整个代码块。这只改变你的策略文件,尚不改变运行中的数据库。

使用受限 OpenBao 操作员注册策略

本步骤不使用秘密服务管理员身份注册账户策略。留在 Terminal。

加载之前,先阅读预置环境文件:

cat env.sh

source env.sh 在当前终端 shell 中运行这些赋值。export 使子命令可以使用值。文件把预置 bao 工具加入 PATH,也就是 shell 搜索命令的列表,并设置本地 OpenBao 和 PostgreSQL 地址。$(cat ...) 从私有文件读取预置操作员令牌;令牌本身不会打印。这些设置属于当前终端会话。新终端必须重新加载:

source env.sh

OpenBao 操作员令牌授权对秘密服务的请求。它不是 PostgreSQL 用户名或密码。此令牌可以管理和获取 settlement-report 凭据,并撤销该角色的精确租约。它不能读取数据库管理连接,也不能管理独立的库存报表。

bao write 向指定 OpenBao 路径写入配置。db_name=market 选择预置数据库连接。creation_statements=@role.sql 读取 SQL 文件作为该字段的值;@ 是 OpenBao 的文件输入语法。default_ttl=10m 使新租约默认有效十分钟,max_ttl=15m 限制其寿命配置上限。TTL 是存活时间。为报表使用这些短时限,不要使用数小时或无时限的账户:

bao write database/roles/settlement-report db_name=market creation_statements=@role.sql default_ttl=10m max_ttl=15m

预期确认数据已写入 database/roles/settlement-report。这改变创建策略,尚未签发数据库登录账户。策略改变影响未来账户,不会去除已签发登录账户的权限。 每次注册策略变更后,获取新凭据并使用它重新生成报表。完成检查要求报表身份在最终策略注册之后签发。不要让先前权限过大的报表账户保持有效;下一步的条件恢复说明如何在替换凭据文件之前结束其精确租约。

策略自检读取当前注册策略,在一次性 PostgreSQL 测试环境中运行其 SQL。它不签发租约、不改动线上数据库,也不运行线上报表。sudo 让预置检查器能够查看受保护配置并运行隔离的普通账户;你的正常 OpenBao 和数据库命令仍使用受限身份:

sudo python3 /opt/labex-report/verify.py policy

如果报告额外权限或缺少到期限制,修正 role.sql 并重新注册。保留占位符和审计条款。实现所述边界的等效权限语句都可接受;检查不要求特定编辑器或 SQL 拼写。

获取临时登录账户并运行真实报表

本步骤用新签发的数据库身份生成结算文件。在已加载 env.sh 的同一个 Terminal 会话中开始。

凭据响应包含三种不同内容:标识 OpenBao 中本次签发的租约 ID、生成的 PostgreSQL 用户名以及对应的密码。上一步操作员令牌授权了签发;它不属于这些数据库凭据。

保持凭据文件私密。umask 077 让新文件仅能由你的账户读写;它不改变已有文件:

umask 077

bao read 请求角色凭据。-format=json 选择结构化 JSON 输出。> 将输出保存到 lease.json,替换已有同名文件。此命令确实创建新的 PostgreSQL 登录账户,并启动租约计时;不要只为查看同一租约而反复运行:

bao read -format=json database/creds/settlement-report > lease.json

jq 从 JSON 选择字段。-r 打印字符串时去掉 JSON 引号。读取非秘密的身份、租约 ID 和租约时长;不要打印密码:

jq '.lease_id, .lease_duration, .data.username' lease.json

预期看到 database/creds/settlement-report/... 租约 ID、600 秒时长和生成的用户名。随机后缀每次不同。

PostgreSQL 客户端 psql 从环境读取 PGUSER 和 PGPASSWORD。带引号的 $(...) 替换从本次签发中设置它们。这些值留在本终端会话及其子命令中:

export PGUSER="$(jq -r .data.username lease.json)"
export PGPASSWORD="$(jq -r .data.password lease.json)"

psql -X 忽略个人 psql 启动定制,-v ON_ERROR_STOP=1 遇 SQL 错误停止,-c 提供一条 SQL 命令。SELECT current_user 询问 PostgreSQL 此连接实际使用的身份:

psql -X -v ON_ERROR_STOP=1 -c 'SELECT current_user;'

预期用户名与 lease.json 中生成的相同,而非商店写入账户、示例读取账户或数据库管理员。

预置 report.sql 选择四个公开报表列,按商户 ID 排序,并以带表头的制表符分隔数据复制到标准输出。保持查询不变。-f 从该文件读取 SQL,> 仅保存其报表输出:

psql -X -v ON_ERROR_STOP=1 -f report.sql > settlement.tsv

读取实际结果:

cat settlement.tsv

三行商户数据必须与先前业务示例一致:Birch 2 / 4700、Elm 1 / 2000、Maple 1 / 2700。不应有客户邮箱或银行参考信息。总额仍为 9400 分,因为今天的 O201 订单不在昨天的结算范围内。

如果后续连接报 password authentication failed,十分钟租约可能在阅读期间已到期。这是凭据失败,并非表查询被拒绝。将凭据签发命令再运行一次,从新文件重新加载 PGUSER 和 PGPASSWORD,再运行身份和报表命令。用户名变新是预期行为。不要编辑记录的用户名或手工延长到期时间。

如果在签发凭据之后更改了策略,则需要另一种恢复。较早账户保留原有授权。在替换它的 lease.json 前,运行 bao lease revoke "$(jq -r .lease_id lease.json)"。带引号的替换提供该完整保存的租约 ID;它不是数据库用户名或租约前缀。绝不撤销库存报表或整个租约前缀。结束较早签发后,获取新凭据、重新加载两个数据库变量,并用上述命令重新生成报表。这样可以避免旧的过宽权限账户在新受限账户旁仍然可用。

确认私有数据和写入仍不可用

本步骤测试账户边界,同时保留商店及其原始数据。使用同一个 Terminal 和已签发的 PostgreSQL 身份。

下面的读取应该失败。SELECT * 请求所有列;策略必须在账户读取原始订单前阻止它:

psql -X -v ON_ERROR_STOP=1 -c 'SELECT * FROM public.orders;'

预期 PostgreSQL 对私有模式或表报告 permission denied。$? 是紧前一条命令的退出状态;echo 将它打印出来。立即读取:

echo $?

预期为 1,表示 SQL 查询被拒绝。状态 2 的连接失败不能证明此权限边界;如果租约到期,使用上一步的重新签发并加载恢复方法。

现在尝试对数据无害的写操作:UPDATE ... WHERE false 不匹配任何行,即使账户错误地拥有过多权限,也无法改变订单。它仍必须因权限不足而被拒绝:

psql -X -v ON_ERROR_STOP=1 -c 'UPDATE public.orders SET amount_cents = 0 WHERE false;'
echo $?

同样预期权限错误和状态 1。账户能读取预期视图,却不能读取原始私有数据或写订单。

预置商店客户端使用商店服务独立的写入身份,而非你的报表凭据。确认所下订单仍可使用:

python3 shop.py list

运行只读完成检查。它先读取已有的原生 OpenBao 签发和 PostgreSQL 查询证据,再检查实际报表与保留的业务数据。它不会替你创建缺失的登录账户或生成报表:

sudo python3 /opt/labex-report/verify.py result

现在你拥有使用真实动态签发且受限的数据库身份生成的完整结算报表。下一个独立实验从正确权限策略开始,重点是结束访问并观察新连接失败。

总结

你注册了权限受限的账户创建策略,通过受限 OpenBao 操作员获取了全新的 PostgreSQL 登录账户,并用该身份保存真实结算报表。视图提供必要业务合计,而私有表、写入和管理员权限仍在报表范围之外。

操作员令牌、租约 ID 和数据库凭据各有不同用途。此练习仍依赖可信的预置管理组件;临时账户不会消除这种引导信任。下一实验将研究精确租约撤销、自然到期,以及通过新签发凭据恢复访问。