ha/safe
三节点高可用与安全加固配置示例。
ha/safe 基于三节点高可用拓扑,示范 TLS、客户端证书、口令检查、备份加密和 CRIT 参数模板等安全配置。它是可修改的配置样例,不是合规认证模板。
配置概览
- 配置名称:
ha/safe - 节点数量:3 个 INFRA、etcd 和 PostgreSQL 节点;可选延迟副本
- 适用系统:
el8、el9、el10、d12、d13、u22、u24、u26 - 适用架构:
x86_64;部分安全扩展没有 ARM64 软件包 - 相关配置:
ha/trio、ha/full
生成配置:
./configure -c ha/safe -g [-i <primary_ip>]
-g 只能随机化配置向导识别的凭据。生成后仍需手工替换 MinIO 用户、pgBackRest cipher_pass 和其他模板示例值。
加固内容
| 配置项 | 模板行为 | 边界与后续操作 |
|---|---|---|
| PostgreSQL HBA | 主要 TCP 规则使用 ssl,公网管理员使用 cert | 本地 ident 和部分 localhost pwd 规则保留 |
| PgBouncer | pgbouncer_sslmode: require | 客户端仍需按需要验证服务端证书 |
| Patroni | REST API 启用 HTTPS,并限制监听地址 | 仍使用 Basic Auth,应轮换口令 |
| 口令检查 | 在 pg_libs 中预加载 passwordcheck | 只影响新设置或修改的口令 |
| 用户有效期 | 内置用户和示例业务用户设置 expire_in: 7300 | 20 年不是轮换策略,应按组织要求缩短 |
| 监听地址 | PostgreSQL 收敛到 ${ip},${vip},${lo} | 仍需配合防火墙和 HBA |
| 备份 | 使用 MinIO,启用 AES-256-CBC | pgBR.${pg_cluster} 是可预测示例值,必须替换 |
| PostgreSQL 参数 | pg-meta 使用 crit.yml | 严格同步模式可能在无同步副本时阻塞写入 |
| 日志 | CRIT 记录连接和断开事件 | SQL 细粒度审计需另行启用 pgaudit |
| 安全扩展 | 安装 passwordcheck、credcheck、pgaudit 等软件包 | 安装不等于预加载、创建或配置 |
| 延迟副本 | 提供注释掉的 1 小时延迟集群示例 | 默认不会创建,需要显式启用 |
使用前检查
- 替换所有公开示例凭据,重点检查
minio_users、pgbackrest_repo、业务用户和 API 口令; - 确认 3 个节点位于独立故障域,并按实际网络修改 IP、VIP 和域名;
- 为数据库客户端配置
sslmode=verify-full与可信 CA; - 确认严格同步模式的可用性影响符合业务要求;
- 根据需要预加载并配置
pgaudit、credcheck等扩展; - 检查 ARM64 环境中的扩展软件包可用性;
- 完成备份恢复、故障切换和证书验证测试。
安全机制说明见 安全模型、身份认证、加密通信 和 数据安全。
配置内容
---
#==============================================================#
# File : safe.yml
# Desc : Pigsty 3-node security enhance template
# Ctime : 2020-05-22
# Mtime : 2025-12-12
# Docs : https://pigsty.io/docs/conf/safe
# License : Apache-2.0 @ https://pigsty.io/docs/about/license/
# Copyright : 2018-2026 Ruohang Feng / Vonng (rh@vonng.com)
#==============================================================#
#===== SECURITY ENHANCEMENT CONFIG TEMPLATE WITH 3 NODES ======#
# * 3 infra nodes, 3 etcd nodes, single minio node
# * 3-instance pgsql cluster with an extra delayed instance
# * crit.yml templates, no data loss, checksum enforced
# * enforce ssl on postgres & pgbouncer, use postgres by default
# * enforce an expiration date for all users (20 years by default)
# * enforce strong password policy with passwordcheck extension
# * enforce changing default password for all users
# * log connections and disconnections
# * restrict listen ip address for postgres/patroni/pgbouncer
all:
children:
infra: # infra cluster for proxy, monitor, alert, etc
hosts: # 1 for common usage, 3 nodes for production
10.10.10.10: { infra_seq: 1 } # identity required
10.10.10.11: { infra_seq: 2, repo_enabled: false }
10.10.10.12: { infra_seq: 3, repo_enabled: false }
vars: { patroni_watchdog_mode: 'off' }
minio: # minio cluster, s3 compatible object storage
hosts: { 10.10.10.10: { minio_seq: 1 } }
vars: { minio_cluster: minio }
etcd: # dcs service for postgres/patroni ha consensus
hosts: # 1 node for testing, 3 or 5 for production
10.10.10.10: { etcd_seq: 1 } # etcd_seq required
10.10.10.11: { etcd_seq: 2 } # assign from 1 ~ n
10.10.10.12: { etcd_seq: 3 } # odd number please
vars: # cluster level parameter override roles/etcd
etcd_cluster: etcd # mark etcd cluster name etcd
etcd_safeguard: false # safeguard against purging
pg-meta: # 3 instance postgres cluster `pg-meta`
hosts:
10.10.10.10: { pg_seq: 1, pg_role: primary }
10.10.10.11: { pg_seq: 2, pg_role: replica }
10.10.10.12: { pg_seq: 3, pg_role: replica , pg_offline_query: true }
vars:
pg_cluster: pg-meta
pg_conf: crit.yml
pg_users:
- { name: dbuser_meta , password: Pleas3-ChangeThisPwd ,expire_in: 7300 ,pgbouncer: true ,roles: [ dbrole_admin ] ,comment: pigsty admin user }
- { name: dbuser_view , password: Make.3ure-Compl1ance ,expire_in: 7300 ,pgbouncer: true ,roles: [ dbrole_readonly ] ,comment: read-only viewer for meta database }
pg_databases:
- { name: meta ,baseline: cmdb.sql ,comment: pigsty meta database ,schemas: [ pigsty ] ,extensions: [ { name: vector } ] }
pg_services:
- { name: standby , ip: "*" ,port: 5435 , dest: default ,selector: "[]" , backup: "[? pg_role == `primary`]" }
pg_crontab: # https://pigsty.io/docs/pgsql/admin/crontab
- '00 01 * * * /pg/bin/pg-backup full'
pg_listen: '${ip},${vip},${lo}'
pg_vip_enabled: true
pg_vip_address: 10.10.10.2/24
# OPTIONAL delayed cluster for pg-meta
#pg-meta-delay: # delayed instance for pg-meta (1 hour ago)
# hosts: { 10.10.10.13: { pg_seq: 1, pg_role: primary, pg_upstream: 10.10.10.10, pg_delay: 1h } }
# vars: { pg_cluster: pg-meta-delay }
####################################################################
# Parameters #
####################################################################
vars: # global variables
version: v4.4.0 # pigsty version string
admin_ip: 10.10.10.10 # admin node ip address
region: default # upstream mirror region: default|china|europe
node_tune: oltp # node tuning specs: oltp,olap,tiny,crit
pg_conf: oltp.yml # pgsql tuning specs: {oltp,olap,tiny,crit}.yml
#docker_registry_mirrors: ["https://docker.1panel.live","https://docker.1ms.run","https://docker.xuanyuan.me","https://registry-1.docker.io"]
patroni_ssl_enabled: true # secure patroni RestAPI communications with SSL?
pgbouncer_sslmode: require # pgbouncer client ssl mode: disable|allow|prefer|require|verify-ca|verify-full, disable by default
pg_default_service_dest: postgres # default service destination to postgres instead of pgbouncer
pgbackrest_method: minio # pgbackrest repo method: local,minio,[user-defined...]
#----------------------------------#
# MinIO Related Options
#----------------------------------#
minio_users: # and configure `pgbackrest_repo` & `minio_users` accordingly
- { access_key: dba , secret_key: S3User.DBA.Strong.Password, policy: consoleAdmin }
- { access_key: pgbackrest , secret_key: Min10.bAckup ,policy: readwrite }
pgbackrest_repo: # pgbackrest repo: https://pgbackrest.org/configuration.html#section-repository
local: # default pgbackrest repo with local posix fs
path: /pg/backup # local backup directory, `/pg/backup` by default
retention_full_type: count # retention full backups by count
retention_full: 2 # keep 2, at most 3 full backup when using local fs repo
minio: # optional minio repo for pgbackrest
s3_key: pgbackrest # <-------- CHANGE THIS, SAME AS `minio_users` access_key
s3_key_secret: Min10.bAckup # <-------- CHANGE THIS, SAME AS `minio_users` secret_key
cipher_pass: 'pgBR.${pg_cluster}' # <-------- CHANGE THIS, you can use cluster name as part of password
type: s3 # minio is s3-compatible, so s3 is used
s3_endpoint: sss.pigsty # minio endpoint domain name, `sss.pigsty` by default
s3_region: us-east-1 # minio region, us-east-1 by default, useless for minio
s3_bucket: pgsql # minio bucket name, `pgsql` by default
s3_uri_style: path # use path style uri for minio rather than host style
path: /pgbackrest # minio backup path, default is `/pgbackrest`
storage_port: 9000 # minio port, 9000 by default
storage_ca_file: /etc/pki/ca.crt # minio ca file path, `/etc/pki/ca.crt` by default
block: y # Enable block incremental backup
bundle: y # bundle small files into a single file
bundle_limit: 20MiB # Limit for file bundles, 20MiB for object storage
bundle_size: 128MiB # Target size for file bundles, 128MiB for object storage
cipher_type: aes-256-cbc # enable AES encryption for remote backup repo
retention_full_type: time # retention full backup by time on minio repo
retention_full: 14 # keep full backup for last 14 days
#----------------------------------#
# Access Control
#----------------------------------#
# add passwordcheck extension to enforce strong password policy
pg_libs: '$libdir/passwordcheck, pg_stat_statements, auto_explain'
pg_extensions:
- passwordcheck, supautils, pgsodium, pg_vault, pg_session_jwt, anonymizer, pgsmcrypto, pgauditlogtofile, pgaudit #, pgaudit17, pgaudit16, pgaudit15, pgaudit14
- pg_auth_mon, credcheck, pgcryptokey, pg_jobmon, logerrors, login_hook, set_user, pgextwlist, pg_auditor, sslutils, noset #pg_tde #pg_snakeoil
pg_default_roles: # default roles and users in postgres cluster
- { name: dbrole_readonly ,login: false ,comment: role for global read-only access }
- { name: dbrole_offline ,login: false ,comment: role for restricted read-only access }
- { name: dbrole_readwrite ,login: false ,roles: [ dbrole_readonly ] ,comment: role for global read-write access }
- { name: dbrole_admin ,login: false ,roles: [ pg_monitor, dbrole_readwrite ] ,comment: role for object creation }
- { name: postgres ,superuser: true ,expire_in: 7300 ,comment: system superuser }
- { name: replicator ,replication: true ,expire_in: 7300 ,roles: [ pg_monitor, dbrole_readonly ] ,comment: system replicator }
- { name: dbuser_dba ,superuser: true ,expire_in: 7300 ,roles: [ dbrole_admin ] ,pgbouncer: true ,pool_mode: session, pool_connlimit: 16 , comment: pgsql admin user }
- { name: dbuser_monitor ,roles: [ pg_monitor ] ,expire_in: 7300 ,pgbouncer: true ,parameters: { log_min_duration_statement: 1000 } ,pool_mode: session ,pool_connlimit: 8 ,comment: pgsql monitor user }
pg_default_hba_rules: # postgres host-based auth rules by default, order by `order`
- { user: '${dbsu}' ,db: all ,addr: local ,auth: ident ,title: 'dbsu access via local os user ident' ,order: 100}
- { user: '${dbsu}' ,db: replication ,addr: local ,auth: ident ,title: 'dbsu replication from local os ident' ,order: 150}
- { user: '${repl}' ,db: replication ,addr: localhost ,auth: ssl ,title: 'replicator replication from localhost' ,order: 200}
- { user: '${repl}' ,db: replication ,addr: intra ,auth: ssl ,title: 'replicator replication from intranet' ,order: 250}
- { user: '${repl}' ,db: postgres ,addr: intra ,auth: ssl ,title: 'replicator postgres db from intranet' ,order: 300}
- { user: '${monitor}' ,db: all ,addr: localhost ,auth: pwd ,title: 'monitor from localhost with password' ,order: 350}
- { user: '${monitor}' ,db: all ,addr: infra ,auth: ssl ,title: 'monitor from infra host with password' ,order: 400}
- { user: '${admin}' ,db: all ,addr: infra ,auth: ssl ,title: 'admin @ infra nodes with pwd & ssl' ,order: 450}
- { user: '${admin}' ,db: all ,addr: world ,auth: cert ,title: 'admin @ everywhere with ssl & cert' ,order: 500}
- { user: '+dbrole_readonly',db: all ,addr: localhost ,auth: ssl ,title: 'pgbouncer read/write via local socket' ,order: 550}
- { user: '+dbrole_readonly',db: all ,addr: intra ,auth: ssl ,title: 'read/write biz user via password' ,order: 600}
- { user: '+dbrole_offline' ,db: all ,addr: intra ,auth: ssl ,title: 'allow etl offline tasks from intranet' ,order: 650}
pgb_default_hba_rules: # pgbouncer host-based authentication rules, order by `order`
- { user: '${dbsu}' ,db: pgbouncer ,addr: local ,auth: peer ,title: 'dbsu local admin access with os ident' ,order: 100}
- { user: 'all' ,db: all ,addr: localhost ,auth: pwd ,title: 'allow all user local access with pwd' ,order: 150}
- { user: '${monitor}' ,db: pgbouncer ,addr: intra ,auth: ssl ,title: 'monitor access via intranet with pwd' ,order: 200}
- { user: '${monitor}' ,db: all ,addr: world ,auth: deny ,title: 'reject all other monitor access addr' ,order: 250}
- { user: '${admin}' ,db: all ,addr: intra ,auth: ssl ,title: 'admin access via intranet with pwd' ,order: 300}
- { user: '${admin}' ,db: all ,addr: world ,auth: deny ,title: 'reject all other admin access addr' ,order: 350}
- { user: 'all' ,db: all ,addr: intra ,auth: ssl ,title: 'allow all user intra access with pwd' ,order: 400}
#----------------------------------#
# Repo, Node, Packages
#----------------------------------#
repo_remove: true # remove existing repo on admin node during repo bootstrap
node_repo_remove: true # remove existing node repo for node managed by pigsty
#node_selinux_mode: enforcing # set selinux mode: enforcing,permissive,disabled
node_firewall_mode: zone # firewall mode: zone (default), off (disable), none (skip & self-managed)
repo_extra_packages: [ pg18-main ] #,pg18-core ,pg18-time ,pg18-gis ,pg18-rag ,pg18-fts ,pg18-olap ,pg18-feat ,pg18-lang ,pg18-type ,pg18-util ,pg18-func ,pg18-admin ,pg18-stat ,pg18-sec ,pg18-fdw ,pg18-sim ,pg18-etl]
pg_version: 18 # default postgres version
#pg_extensions: [ pg18-time ,pg18-gis ,pg18-rag ,pg18-fts ,pg18-olap ,pg18-feat ,pg18-lang ,pg18-type ,pg18-util ,pg18-func ,pg18-admin ,pg18-stat ,pg18-sec ,pg18-fdw ,pg18-sim ,pg18-etl]
#----------------------------------------------#
# PASSWORD : https://pigsty.io/docs/setup/security/
#----------------------------------------------#
#grafana_admin_username: admin
grafana_admin_password: You.Have2Use-A_VeryStrongPassword
grafana_view_password: DBUser.Viewer
#pg_admin_username: dbuser_dba
pg_admin_password: PessWorb.Should8eStrong-eNough
#pg_monitor_username: dbuser_monitor
pg_monitor_password: MekeSuerYour.PassWordI5secured
#pg_replication_username: replicator
pg_replication_password: doNotUseThis-PasswordFor.AnythingElse
#patroni_username: postgres
patroni_password: don.t-forget-to-change-thEs3-password
#haproxy_admin_username: admin
haproxy_admin_password: GneratePasswordWith-pwgen-s-16-1
minio_secret_key: S3User.MinIO
etcd_root_password: Etcd.Root
...