刘光辉
15 小时以前 34981c30a78e8bbd7791131059a9210f9928b62c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
-- 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;