-- Backlog A4(2026-07-17):业务连库账号与超管分离 —— 三库三角色 -- jnpf_app → jnpf_init (9 个业务服务,Nacos datasource.yaml 消费) -- jnpf_flow_app → jnpf_flow (独立流程引擎,compose env FLOW_DB_* 消费) -- nacos_app → nacos_config (Nacos 服务端,compose env NACOS_DB_* 消费) -- -- 动机(L029):业务用 postgres 超管连库使 superuser_reserved_connections=3 形同虚设, -- 连接打满时运维无法救火;且权限过大。 -- -- 执行入口:同目录 create-app-roles.sh(从仓库根 .env 读密码,docker exec 进容器执行)。 -- 幂等:可重复执行;每次执行都会按 .env 重置角色密码。 -- -- psql 变量(由 wrapper 传入): -- nacos_enabled:true/false —— 补丁 #8 后新栈默认 false(.env 未设 JNPF_NACOS_DB_PASSWORD), -- nacos_app 段整体跳过;旧实例兼容时 wrapper 会传 true,行为与本文件历史版本一致 -- 密码:app_pw / flow_pw / nacos_pw(nacos_enabled=false 时 nacos_pw 不会被用到) -- 连接配额:app_conn_limit / flow_conn_limit / nacos_conn_limit(整数,直接内插,wrapper 已校验为非负整数) -- -- 设计要点: -- 1. 存量对象所有权移交给对应角色 —— 在线开发(visualdev)运行时会对既有表 ALTER/DROP, -- Flowable/Nacos 升级也可能改 schema;仅 GRANT 不够。 -- 覆盖 relkind r/p/S/v/m(表/分区表/序列/视图/物化视图);如未来引入 FDW 外部表(f)或 -- 自定义复合类型(c)需在循环里补充处理 -- 2. 排除扩展对象(pg_depend deptype='e',如 pg_stat_statements 的视图),不动扩展归属 -- 3. REVOKE PUBLIC 的默认 CONNECT —— 角色只能连自己的库 -- 4. postgres 建的新对象通过 ALTER DEFAULT PRIVILEGES 自动授予(运维补丁场景) \set ON_ERROR_STOP on -- ========== 角色(幂等创建 + 每次重置密码) ========== SELECT 'CREATE ROLE jnpf_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION' WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'jnpf_app') \gexec SELECT 'CREATE ROLE jnpf_flow_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION' WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'jnpf_flow_app') \gexec \if :nacos_enabled SELECT 'CREATE ROLE nacos_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION' WHERE NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'nacos_app') \gexec \else \echo '跳过 nacos_app(补丁 #8 后栈不再使用;旧实例兼容需 wrapper 传 nacos_enabled=true)' \endif ALTER ROLE jnpf_app PASSWORD :'app_pw'; ALTER ROLE jnpf_flow_app PASSWORD :'flow_pw'; \if :nacos_enabled ALTER ROLE nacos_app PASSWORD :'nacos_pw'; \endif -- ========== 连接数配额(L029 纵深防御:防单角色占满连接槽饿死其他角色,如流程引擎) ========== -- 值由 wrapper 从 .env 传入(未设用脚本默认 150/30/10)。铁律:三者之和须 < -- (max_connections − superuser_reserved_connections),否则等于没预留。整数由 wrapper 校验后直接内插。 ALTER ROLE jnpf_app CONNECTION LIMIT :app_conn_limit; ALTER ROLE jnpf_flow_app CONNECTION LIMIT :flow_conn_limit; \if :nacos_enabled ALTER ROLE nacos_app CONNECTION LIMIT :nacos_conn_limit; \endif -- 角色只能连各自的库。nacos_config 的 REVOKE 不放进 \if:即便本次跳过 nacos_app, -- 该库也不该继续留着默认的 PUBLIC CONNECT——只有随后的 GRANT(把连接权还给 nacos_app)才需要条件跳过。 REVOKE CONNECT ON DATABASE jnpf_init FROM PUBLIC; REVOKE CONNECT ON DATABASE jnpf_flow FROM PUBLIC; REVOKE CONNECT ON DATABASE nacos_config FROM PUBLIC; GRANT CONNECT ON DATABASE jnpf_init TO jnpf_app; GRANT CONNECT ON DATABASE jnpf_flow TO jnpf_flow_app; \if :nacos_enabled GRANT CONNECT ON DATABASE nacos_config TO nacos_app; \endif -- ========== jnpf_init → jnpf_app ========== \c jnpf_init GRANT USAGE, CREATE ON SCHEMA public TO jnpf_app; DO $$ DECLARE r record; BEGIN FOR r IN SELECT c.oid::regclass AS obj FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind IN ('r','p','S','v','m') AND c.relowner <> 'jnpf_app'::regrole AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = c.oid AND d.deptype = 'e') -- 表自有序列(serial/identity)不可单独改 owner,随其表的 ALTER TABLE 自动移交 AND NOT (c.relkind = 'S' AND EXISTS ( SELECT 1 FROM pg_depend d2 WHERE d2.classid = 'pg_class'::regclass AND d2.objid = c.oid AND d2.deptype IN ('a','i'))) LOOP EXECUTE format('ALTER TABLE %s OWNER TO jnpf_app', r.obj); END LOOP; END $$; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO jnpf_app; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO jnpf_app; -- ========== jnpf_flow → jnpf_flow_app ========== \c jnpf_flow GRANT USAGE, CREATE ON SCHEMA public TO jnpf_flow_app; DO $$ DECLARE r record; BEGIN FOR r IN SELECT c.oid::regclass AS obj FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind IN ('r','p','S','v','m') AND c.relowner <> 'jnpf_flow_app'::regrole AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = c.oid AND d.deptype = 'e') -- 表自有序列(serial/identity)不可单独改 owner,随其表的 ALTER TABLE 自动移交 AND NOT (c.relkind = 'S' AND EXISTS ( SELECT 1 FROM pg_depend d2 WHERE d2.classid = 'pg_class'::regclass AND d2.objid = c.oid AND d2.deptype IN ('a','i'))) LOOP EXECUTE format('ALTER TABLE %s OWNER TO jnpf_flow_app', r.obj); END LOOP; END $$; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO jnpf_flow_app; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO jnpf_flow_app; -- ========== nacos_config → nacos_app ========== \if :nacos_enabled \c nacos_config GRANT USAGE, CREATE ON SCHEMA public TO nacos_app; DO $$ DECLARE r record; BEGIN FOR r IN SELECT c.oid::regclass AS obj FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = 'public' AND c.relkind IN ('r','p','S','v','m') AND c.relowner <> 'nacos_app'::regrole AND NOT EXISTS (SELECT 1 FROM pg_depend d WHERE d.objid = c.oid AND d.deptype = 'e') -- 表自有序列(serial/identity)不可单独改 owner,随其表的 ALTER TABLE 自动移交 AND NOT (c.relkind = 'S' AND EXISTS ( SELECT 1 FROM pg_depend d2 WHERE d2.classid = 'pg_class'::regclass AND d2.objid = c.oid AND d2.deptype IN ('a','i'))) LOOP EXECUTE format('ALTER TABLE %s OWNER TO nacos_app', r.obj); END LOOP; END $$; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON TABLES TO nacos_app; ALTER DEFAULT PRIVILEGES FOR ROLE postgres IN SCHEMA public GRANT ALL ON SEQUENCES TO nacos_app; \endif -- ========== 汇总 ========== \c postgres SELECT rolname, rolsuper, rolcanlogin, rolconnlimit FROM pg_roles WHERE rolname IN ('postgres','jnpf_app','jnpf_flow_app','nacos_app') ORDER BY rolname;