ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PostgREST 防误删全表:集成 pg-safeupdate 扩展阻断无条件的 UPDATE 与 DELETE

PostgREST 防误删全表:集成 pg-safeupdate 扩展阻断无条件的 UPDATE 与 DELETE PostgREST 防误删全表集成 pg-safeupdate 扩展阻断无条件的 UPDATE 与 DELETE【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest导读PostgREST 把 PostgreSQL 表直接暴露为 REST 资源只要当前数据库角色对表拥有 DELETE 权限客户端就能发起删除请求——而一旦查询参数写漏一次“删除整张表”的灾难性操作就可能发生。本文围绕 docs/integrations/pg-safeupdate.rst 讲解如何通过集成 PostgreSQL 扩展 pg-safeupdate 为 PostgREST API 加上“无条件 UPDATE/DELETE 即报错”的安全护栏并深入仓库源码与测试用例说明 PostgREST 如何识别并映射该扩展抛出的错误、如何用db-pre-request按会话加载扩展以及为什么它只是“防手滑”而非“防恶意”的第一道防线。问题背景REST 语义下“删全表”有多容易PostgREST 对表的操作权限完全由数据库角色决定。当客户端请求所使用的活跃角色PostgREST 通过SET ROLE实现的用户模拟机制详见 user impersonation对该表拥有 DELETE 权限时DELETE动词就对客户端开放了。假设有一个logs日志表要删除 1991-08-06 之前的旧数据正常的请求是curl http://localhost:3000/logs?timelt.1991-08-06 -X DELETE这里通过timelt.1991-08-06过滤条件限定了删除范围。但问题在于省略查询参数同样是一个合法的 API 请求curl http://localhost:3000/logs -X DELETE这会在没有任何 WHERE 条件的情况下清空整张logs表。原文档特别指出这类事故很容易发生例如把一次请求从 GET 误改成 DELETEcurl命令、浏览器插件、脚本复用都可能导致这种手滑。在 RESTful 的语义里PostgREST 无从判断“删全表”是客户端的真实意图还是误操作。pg-safeupdate为无条件的 UPDATE/DELETE 直接报错针对上述风险PostgREST 官方文档推荐的防护手段是集成第三方 PostgreSQL 扩展pg-safeupdate由 eradman 维护属 PostgreSQL 生态中的常见安全插件也收录在项目 docs/ecosystem.rst 的生态清单中。该扩展的核心行为非常朴素当执行的 UPDATE 或 DELETE 语句没有指定任何条件即缺少 WHERE 子句时直接抛出错误从而把“删全表”从数据库层面拦截下来。安装方式一通过 PGXN 安装原文档给出的标准安装流程借助 PGXNPostgreSQL Extension Network完成sudo -E pgxn install safeupdate安装完成后需要把扩展加入 PostgreSQL 的预加载库配置编辑postgresql.confshared_preload_librariessafeupdate;之所以要求shared_preload_libraries是因为该扩展通过钩子hook拦截 SQL 语句的执行路径必须在 PostgreSQL 启动时随共享库一起加载才能生效。修改后需要重启 PostgreSQL 服务。安装方式二通过发行版包管理仓库内证据从本仓库的 Nix 构建配置可以看出 pg-safeupdate 在生态中的普及程度——default.nix 中为 PostgreSQL 1419 的每个测试环境都显式打包了pg_safeupdate{ name pg-19; postgresql pkgs.postgresql_19.withPackages (p: [ p.postgis p.pg_safeupdate ]); } { name pg-18; postgresql pkgs.postgresql_18.withPackages (p: [ p.postgis p.pg_safeupdate ]); } # ... 15/16/17 同理这说明在 Debian/Ubuntupostgresql-XX-safeupdate等发行版上同样可以通过系统包管理器直接安装该扩展无需从源码编译。安装后依旧需要在postgresql.conf中配置shared_preload_libraries并重启数据库。激活方式shared_preload_libraries与按会话LOAD除了随库预加载PostgreSQL 还允许在会话内通过LOAD safeupdate动态加载扩展。本仓库的集成测试正是利用了这一点测试夹具 test/spec/fixtures/schema.sql 中定义了一个SECURITY DEFINER函数CREATE OR REPLACE FUNCTION test.load_safeupdate() RETURNS VOID AS $$ BEGIN LOAD safeupdate; END; $$ LANGUAGE plpgsql SECURITY DEFINER;配合 PostgREST 的pre-request机制可以在每个请求的事务内、主查询执行前调用该函数从而按会话启用扩展。在 PostgREST 中按请求启用扩展db-pre-requestdb-pre-request是 PostgREST 的配置参数用于指定一个 schema 限定的函数名PostgREST 会在事务设置完成之后、主查询执行之前调用它详见 事务文档 Pre-Request 一节配置定义见 docs/references/configuration.rst配置项说明类型String默认值n/a可热加载Y环境变量PGRST_DB_PRE_REQUEST数据库内配置pgrst.db_pre_request兼容别名pre-request无前缀向后兼容将该函数配置到 PostgREST 后每个请求都会先执行LOAD safeupdate再进入真正的查询# postgrest.conf db-pre-request test.load_safeupdate这一方案的好处是无需在postgresql.conf中全局预加载扩展、也无需重启数据库即可在生产环境按需启用但要注意LOAD需要具备相应权限SECURITY DEFINER函数正是为了以函数属主测试环境中为超级用户的身份执行LOAD。仓库测试调度文件 test/spec/Main.hs 中的注释也印证了这一点“This test runs with a pre request to enable the pg-safeupdate library per-session”并且该测试必须最后运行因为 “once pg safe update is loaded, it cant be unloaded again”——即扩展一旦在某会话加载便无法卸载会污染后续测试。触发拦截后的报错形态与 HTTP 状态码映射启用 pg-safeupdate 后不带条件的 UPDATE/DELETE 会失败。仓库的集成测试 test/spec/Feature/Query/PgSafeUpdateSpec.hs 完整刻画了这一行为全表 UPDATEPATCH被拦截request methodPatch /safe_update_items [(Prefer, countexact)] [json| {name: New name} |] shouldRespondWith [json|{ code: 21000, details: null, hint: null, message: UPDATE requires a WHERE clause }|] { matchStatus 400 }带过滤条件的 UPDATE 正常放行request methodPatch /safe_update_items?idgt.0 mempty [json| {name: updated-item} |] shouldRespondWith 204全表 DELETE 被拦截返回同样的DELETE requires a WHERE clause错误HTTP 400request methodDelete /safe_delete_items [] mempty shouldRespondWith ... { matchStatus 400 }带过滤条件的 DELETE 正常放行request methodDelete /safe_delete_items?idgt.0 mempty mempty shouldRespondWith 204对客户端而言拦截结果是一个结构化的 JSON 错误响应SQLSTATE 错误码21000cardinality_violation基数违规、错误消息UPDATE requires a WHERE clause/DELETE requires a WHERE clause以及 HTTP 400 状态码。这里有一个值得注意的实现细节SQLSTATE21000在 PostgreSQL 语义中是“基数违规”cardinality_violationPostgREST 默认会将其视为 500 服务器错误例如子查询返回多行的情况。但 pg-safeupdate 抛出的同样是21000PostgREST 在错误映射源码 src/library/PostgREST/Error.hs 中做了专门的特殊处理21000 - -- cardinality_violation if BS.isSuffixOf requires a WHERE clause m then HTTP.status400 -- special case for pg-safeupdate, which we consider as client error else HTTP.status500 -- generic function or view server error即当21000错误的消息以requires a WHERE clause结尾时PostgREST 判定这是 pg-safeupdate 触发的拦截将其归类为客户端错误并返回 400否则仍按 500 处理。这意味着你可以在不修改任何业务代码的前提下让 PostgREST 将 pg-safeupdate 的拦截错误以 4xx 语义呈现给前端便于客户端识别“请求条件缺失”这一可修正问题。与禁用场景的对比未启用时的行为同一测试文件中的disabledSpec验证了对照场景在未启用 pg-safeupdate 时unsafe_update_items/unsafe_delete_items表不带条件的 PATCH 与 DELETE 均能正常返回 204request methodPatch /unsafe_update_items mempty [json| {name: updated-item} |] shouldRespondWith 204 request methodDelete /unsafe_delete_items mempty mempty shouldRespondWith 204两组对照safe_*vsunsafe_*测试表定义见 test/spec/fixtures/schema.sql清晰地证明了拦截行为完全由 pg-safeupdate 扩展驱动PostgREST 本身不内置“全表操作保护”是否安全取决于数据库侧是否加载了该扩展。局限性只能防“手滑”防不了“恶意”原文档在结尾给出了非常清醒的边界说明pg-safeupdate 不能防护恶意操作。原因在于攻击者只需要在请求 URL 中追加一个不影响结果集的查询参数就能绕过拦截。以 DELETE 为例下面两个请求在 pg-safeupdate 眼中都“带条件”但效果等同删全表# 真正的过滤 curl http://localhost:3000/logs?timelt.1991-08-06 -X DELETE # 伪装的“条件”——id 恒为真等价于无过滤 curl http://localhost:3000/logs?idgt.0 -X DELETEidgt.0这样的条件会被翻译成 SQL 中的WHERE id 0对结果集没有任何筛选作用但足以让 pg-safeupdate 的“存在 WHERE 子句”检查通过。因此pg-safeupdate 的实际定位是防止开发/运维/使用过程中的意外全表操作比如把 GET 误改成 DELETE、脚本参数拼接遗漏它降低的是“事故概率”而不是“攻击面”。纵深防御数据库权限 行级安全RLS要真正抵御恶意删除原文档给出的方向是把防线下沉到数据库权限体系收紧数据库权限GRANT/REVOKE严禁把 DELETE乃至 UPDATE权限授予不该删数据的角色。PostgREST 的用户模拟机制决定了每个 API 用户对应一个数据库角色应遵循最小权限原则只给必要角色授予必要的表级权限普通只读角色连 DELETE 权限都不应拥有。行级安全Row-Level SecurityRLS当需要更细粒度的访问控制时启用 PostgreSQL 的 RLS 策略。RLS 允许按行定义谁能删/改哪些行例如USING (owner current_user)使“即便角色有 DELETE 权限也只能删除自己名下的行”。PostgreSQL 官方文档对 RLS 有完整说明可参见其 DDL Row Security 章节。推荐的组合拳是pg-safeupdate 负责拦截“无条件的全表操作”这类低级事故权限与 RLS 负责真正限制“谁能删、能删哪些行”。二者叠加才能构成对全表误删的完整防护。小结防护层手段解决的问题语句级护栏pg-safeupdate 扩展shared_preload_libraries或按会话LOAD无 WHERE 条件的 UPDATE/DELETE 直接报错防手滑删全表请求级激活PostgRESTdb-pre-request调用LOAD safeupdate按会话启用扩展无需重启数据库错误语义PostgREST 将21000requires a WHERE clause映射为 HTTP 400客户端可识别并修正“条件缺失”问题权限控制按角色 GRANT/REVOKE 最小授权从根源上禁止无权角色删除数据行级控制PostgreSQL RLS 策略精细到“每行谁能删/改”集成 pg-safeupdate 的完整落地路径是安装扩展 → 在postgresql.conf配置shared_preload_librariessafeupdate或通过db-pre-request配置按会话LOAD→ 验证 PostgREST 返回的 400 错误形态 → 最后仍以数据库权限与 RLS 收口恶意场景。仓库中的 集成测试 是理解该扩展与 PostgREST 交互行为的绝佳参考——它同时覆盖了启用、禁用、带条件放行、无条件拦截四种场景可作为你在自己环境中复现与验证的蓝本。【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表