Security and Compliance
Pigsty manages authentication, authorization, encryption, audit, backup, and recovery as code, with a clear path from the default baseline to production hardening.
The database is usually the most sensitive component in an information system: it stores the most valuable data, so attacks and failures can have the most serious consequences.
Database security is not a feature that can be enabled with one switch. It is the combined answer to a series of questions: Who can connect? What can they do after connecting? Can traffic be intercepted? Are operations recorded? Can damaged, lost, or deleted data be recovered?
Pigsty turns these answers into an out-of-the-box security baseline and manages it through declarative configuration:
HBA rules, roles and privileges, certificates, encryption, backups, and audit policies are declared as parameters in the inventory, then rendered and applied by idempotent playbooks.
This Security as Code approach is itself an important security practice. Policies can be versioned, reviewed, and traced, while one inventory provides a consistent baseline across many instances.
When an auditor asks who can access a database, you can start from a readable YAML declaration, then verify the generated HBA rules and database grants against the running system.
Security as Code
In traditional operations, security settings are often scattered across the environment: pg_hba.conf on one server, a GRANT statement executed manually by a DBA, or a firewall rule opened temporarily during an incident.
Over time, documentation and actual state can drift, making it difficult to determine which rule set each instance is using.
Pigsty takes a different approach: security policy is part of the cluster definition and lives alongside other cluster properties.
pg-meta:
hosts: { 10.10.10.10: { pg_seq: 1, pg_role: primary } }
vars:
pg_cluster: pg-meta
pg_users: # Who may log in: account, role, and expiration
- { name: dbuser_app ,password: '<unique-random-password>' ,roles: [dbrole_readwrite] ,expire_in: 365 }
pg_databases: # Databases and their isolation policy
- { name: app ,owner: dbuser_app ,revokeconn: true }
pg_hba_rules: # Who may connect, from where, and how
- { user: dbuser_app ,db: app ,addr: 10.1.0.0/16 ,auth: ssl ,order: 50 ,title: 'app access via ssl' }
Users, privileges, and HBA rules are described declaratively, and playbooks apply them idempotently to every cluster instance.
New instances inherit the same policy, and Git history records security configuration changes. Manual GRANT statements, runtime parameter changes, and edits to node files can still cause drift, so production environments should compare declared and actual state regularly.
Default Security Baseline
Reasonable defaults reduce omissions. The following capabilities are enabled in the default Pigsty configuration:
| Capability | Default Behavior | Related Parameter |
|---|
| Password hashing | New or updated PostgreSQL passwords use SCRAM-SHA-256 | pg_pwd_enc |
| Data checksums | Page checksums are enabled during cluster initialization to detect silent corruption | pg_checksum |
| Server-side TLS | PostgreSQL server certificates are installed and ssl is enabled, so TLS connections are accepted | — |
| Local CA | A self-signed CA is created automatically for managed component certificates | ca_create |
| etcd encryption and authentication | TLS for client and peer traffic, plus RBAC password authentication | etcd_root_password |
| MinIO HTTPS | Backup storage traffic uses HTTPS by default | minio_https |
| Nginx HTTPS | Web ingress listens on both ports 80 and 443 by default | nginx_sslmode |
| HBA rules | Layered access: local ident, intranet password authentication, and SSL required for public administrator access | pg_default_hba_rules |
| Roles and privileges | A four-tier role model and default privilege templates provide a least-privilege baseline | pg_default_roles |
| Backup and recovery | pgBackRest is enabled by default, with two full backups retained in the local repository | pgbackrest_enabled |
| Firewall | Zone mode trusts intranet CIDRs and exposes only required ports to public networks | node_firewall_mode |
| Restricted sudo | Sudo access for the database OS user is limited to the required command set | pg_dbsu_sudo |
Hardening with Trade-offs
The default configuration targets deployments on a trusted intranet. Some controls require explicit enablement because they impose performance or compatibility costs, or require decisions from the operator:
- Default configurations and examples contain publicly documented default passwords for quick starts and local testing. Before production deployment, use
./configure -g to randomize the credentials it recognizes, then check the pgBackRest encryption passphrase, MinIO users in ha/safe, and all custom values. - TLS is disabled by default for the Patroni REST API and PgBouncer (
patroni_ssl_enabled, pgbouncer_sslmode); enable it explicitly with the certificates already issued. - Password strength checks (
passwordcheck) and the audit extension (pgaudit) are disabled by default. Confirm package availability, then configure preloading and policy before use. - SELinux defaults to
permissive. Demo configurations also expose port 5432 through the firewall; remove that exception in production. - The local backup repository is not encrypted by default. The MinIO backup repository uses AES-256 encryption by default, but its default encryption passphrase must be changed.
The ha/safe hardening template combines TLS, certificate authentication, password checks, and backup encryption.
Together with the consistency-first CRIT parameter template, it provides a practical starting point. Public credentials, audit extensions, and the failure model still require explicit review.
See the Security Model for the complete upgrade path.
This Chapter
| Section | Question Answered |
|---|
| Security Model | Where is the root of trust? How many defensive layers exist? How should the baseline be hardened? |
| Authentication | Who can connect? How is identity proven? How are HBA rules declared and applied? |
| Access Control | What can a connected user do? How does least privilege become the default? |
| Encrypted Communication | How is traffic encrypted? Who issues, distributes, and rotates certificates? |
| Data Security | How is data kept intact, recoverable, confidential, and traceable? |
| Compliance | How do security capabilities map to MLPS and SOC 2 controls? |
Beyond the conceptual model, these pages provide operational security guidance:
1 - Security Model
Pigsty trust boundaries and defense in depth, with the admin node as a high-trust control plane and a path from the default baseline to production hardening.
Before examining individual security features, answer two more fundamental questions: Where is the root of trust? and How many defensive layers exist?
The first determines what deserves the strongest protection. The second determines what remains when one layer fails.
Trust Boundaries
Pigsty is an Ansible-based declarative deployment system. Like other control-plane systems, its admin node is the control plane and the node that requires the strongest protection.
| Role | Assets and Privileges |
|---|
| Admin node | The pigsty.yml inventory, which normally contains system and application credentials; the CA private key; SSH administration access to every node |
| INFRA nodes | Monitoring and alerts, DNS, Nginx ingress, and software repositories |
| Database nodes | Database instances, local dbsu, and restricted sudo |
| Clients | Database credentials or client certificates; access through service ports, HBA, and authentication |
These roles hold different capabilities; they do not form a simple linear hierarchy. Three assets are especially important:
- The
pigsty.yml inventory contains component passwords and credentials. Strictly control access to the admin node and to the configuration repository when Git is used. - The CA private key,
files/pki/ca/ca.key, is the trust anchor for the deployment. Anyone holding it can issue an arbitrary trusted certificate. The file uses mode 0600 inside a 0700 directory; keep an offline backup. - The administration user’s SSH private key lets the admin node manage every enrolled node with passwordless sudo. It is effectively root access to the managed fleet.
Pigsty’s security policy states this boundary explicitly: an attack that requires admin-node access, or possession of both pigsty.yml and the CA private key, is not treated as a product vulnerability.
These are high-trust control-plane assets by design and must be protected accordingly.
Seven Defensive Layers
Defense in depth does not ask one mechanism to solve every problem. It combines controls so that one failure does not remove all protection.
Pigsty’s security capabilities can be summarized as seven layers:
| # | Layer | Mechanisms | Details |
|---|
| 1 | Network boundary | Firewall zones, constrained listen addresses, centralized ingress | This page |
| 2 | Transport encryption | Local CA and TLS between components | Encrypted Communication |
| 3 | Authentication | HBA rules, SCRAM passwords, client certificates | Authentication |
| 4 | Access control | Role model, default privileges, database isolation | Access Control |
| 5 | Host security | SELinux, restricted sudo, dedicated OS users | This page |
| 6 | Data security | Checksums, backup and encryption, PITR, deletion safeguards | Data Security |
| 7 | Audit trail | DDL and connection logs, audit extensions, centralized logs | Data Security |
Layers 2, 3, 4, 6, and 7 have dedicated chapters. The following sections cover the network and host layers.
Network Boundaries
Pigsty enables a firewall during node provisioning (node_firewall_mode defaults to zone), using firewalld or ufw according to the operating system.
Intranet CIDRs (10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16, defined by node_firewall_intranet) enter the trusted zone.
Public networks can reach only ports declared in node_firewall_public_port, which defaults to 22 for SSH and 80/443 for web traffic.
The default demo inventory, pigsty.yml, also exposes port 5432 for local evaluation. Remove it in production. If direct database access is required, restrict sources to explicit CIDRs with security groups, host firewalls, and HBA.
PostgreSQL listens on all addresses by default (pg_listen: 0.0.0.0). The effective access boundary is the combination of listen addresses, firewall rules, and HBA. Stricter environments can constrain the listener:
pg_listen: '${ip},${vip},${lo}' # Host IP, cluster VIP, and loopback only
The default firewall does not expose Grafana, VictoriaMetrics, or other web infrastructure directly to public networks. External web access normally enters through the Nginx portal.
Database traffic enters through HAProxy service ports. Fewer entry points are easier to harden and audit.
Host Security
The central host-level rule is: each OS user receives only the privileges required for its job.
- The database superuser
postgres (pg_dbsu) has no password by default and can enter the database only through local ident authentication.
pg_dbsu_sudo defaults to limit, allowing passwordless systemctl operations for database services and log viewing rather than unrestricted root access. - The administration user (
node_admin_username, default dba) is used by operators and playbooks and receives passwordless sudo (nopass) by default.
Security-sensitive environments can set node_admin_sudo to all, which requires a sudo password, or limit, which restricts the command set. node_selinux_mode defaults SELinux to permissive: violations are logged but not blocked, providing a baseline before moving to enforcing.
Pigsty does not manage the SSH server configuration. Disabling password login, restricting remote root login, and similar operating-system hardening belong in your host security baseline.
Hardening Levels
Security does not have to jump to its final state in one step. Pigsty provides an upgrade path in which each level builds on the previous one:
Level 1: default baseline. Out-of-the-box controls include SCRAM passwords, data checksums, a local CA and component certificates, layered HBA, a four-tier role model, default backups, and firewall zones.
This level suits development, testing, and evaluation on a trusted intranet. Production still requires credential review, network-boundary review, and client verification.
Level 2: randomized credentials. Default passwords are documented publicly and must be changed in every network-exposed deployment. Add -g when generating configuration to randomize built-in parameters and example credentials recognized by the configuration wizard:
./configure -g # --generate: randomize recognized default credentials
This option does not replace the pgBackRest cipher_pass, every MinIO example credential in ha/safe, or user-defined values. See the Default Credentials Checklist for the complete scope.
Level 3: policy hardening with the ha/safe template. conf/ha/safe.yml combines several controls into a starting point for further customization:
- TLS and certificate authentication: the main TCP HBA rules use
ssl, public administrator access uses a client certificate, PgBouncer uses require, and the Patroni API uses HTTPS. Local ident and selected localhost password rules remain. - Password policy:
passwordcheck is preloaded explicitly, and built-in users declare expire_in. Example passwords in the template still require review and replacement. - Reduced attack surface: listen addresses are limited to
${ip},${vip},${lo}, and public connection-pool access by monitoring and administration accounts is denied explicitly. - Backup encryption: pgBackRest uses a MinIO repository with AES-256-CBC.
pgBR.${pg_cluster} is a predictable example value and must be replaced. - Security extensions:
passwordcheck, credcheck, pgaudit, pgsodium, anonymizer, and related extensions are installed. Installation does not preload, create, or configure an extension.
Level 4: database hardening with the crit.yml parameter template. The safe template selects the CRIT parameter template for consistency-first workloads. Compared with the general oltp template, it:
- forces data checksums regardless of
pg_checksum; - enables strict synchronous replication (
synchronous_mode_strict), blocking writes that require synchronous acknowledgment when no synchronous replica is available; - logs connection and disconnection events; PostgreSQL 18 also separates connection receipt, authentication, and authorization stages;
- configures watchdog as
automatic, which activates only when a usable device exists.
Strict synchronous mode targets preservation of acknowledged transactions, but still depends on synchronous_commit, synchronous replica state, and failover eligibility. Validate RPO with failure exercises on the target topology.
You can also select individual controls instead of adopting the complete template:
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 }
vars:
pg_cluster: pg-meta
pg_conf: crit.yml # Use the CRIT database parameter template
patroni_ssl_enabled: true # Enable HTTPS for the Patroni API
pgbouncer_sslmode: require # Require TLS for PgBouncer
pg_listen: '${ip},${vip},${lo}' # Constrain listen addresses
pg_libs: '$libdir/passwordcheck, pg_stat_statements, auto_explain' # Password strength checks
Next
2 - Authentication
Pigsty manages PostgreSQL and PgBouncer HBA rules declaratively, combining SCRAM passwords and client certificates to define who may connect and how identity is proven.
PostgreSQL uses pg_hba.conf for Host-Based Authentication: who may connect, from where, to which database, and how they must prove their identity.
The mechanism is powerful, but expensive to maintain manually across a cluster. Primary and replica instances may require different rules, and every instance stores its own configuration in the data directory.
Without a common declaration and refresh process, rules can drift between instances.
Pigsty applies the same declarative configuration model here: HBA rules are part of the inventory and are rendered and distributed consistently by playbooks.
HBA as Code
Cluster HBA policy combines two parameter groups: the global defaults in pg_default_hba_rules and cluster-specific additions in pg_hba_rules.
The PgBouncer connection pool has two independent counterparts: pgb_default_hba_rules and pgb_hba_rules.
A rule can use either of two forms. The recommended alias form keeps one semantic rule on one line:
pg_hba_rules:
- { user: dbuser_app ,db: app ,addr: 10.1.0.0/16 ,auth: ssl ,order: 50 ,title: 'app user access via ssl' }
The raw form supplies a literal pg_hba.conf line for cases the aliases cannot express.
In addition to user, address, database, and authentication method, each rule has two control fields:
order: render order. HBA uses first-match semantics, so order is priority. By convention, 0-99 is reserved for high-priority user rules, 100-999 for defaults, and rules without order come last.role: instance-role filter. common and default apply to every instance; primary, replica, offline, standby, and delayed apply only to matching instances.
A role: offline rule is also rendered on instances marked with pg_offline_query. The same declaration therefore produces the appropriate rules for each instance role without maintaining primary and replica files manually.
After editing the declaration, apply it with the wrapper script. The rules are rendered again and reloaded:
bin/pgsql-hba pg-meta # Render and apply HBA rules for pg-meta
pg_hba_rules appends rules; it does not automatically narrow broader defaults. To establish a stricter boundary, review pg_default_hba_rules as well, then inspect the generated pg_hba.conf on every instance.
Address and Authentication Aliases
The alias form gives common cases semantic names. Values in addr expand into concrete address blocks:
| Alias | Expands To | Meaning |
|---|
local | Unix socket | Local socket only |
localhost | Unix socket, 127.0.0.1/32, and ::1/128 | Local host |
admin | <admin_ip>/32 | Admin node |
infra | /32 address of each INFRA node | Infrastructure nodes |
cluster | /32 address of every cluster member | Cluster-internal traffic |
intra | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 | Intranet CIDRs, customizable with node_firewall_intranet |
world | 0.0.0.0/0 and ::/0 | Any address |
| CIDR | Unchanged | Custom network |
Values in auth select the authentication method and whether TLS is mandatory:
| Alias | Authentication Method | Notes |
|---|
deny | reject | Explicit rejection |
trust | trust | Unconditional access; use with care |
pwd | scram-sha-256 or md5 | Follows pg_pwd_enc; SCRAM by default |
sha | scram-sha-256 | Force SCRAM |
md5 | md5 | Compatibility for legacy clients |
ssl | hostssl with password authentication | Password authentication over mandatory TLS |
ssl-sha | hostssl with scram-sha-256 | Mandatory TLS and SCRAM |
cert | hostssl with cert | Client certificate authentication |
ident, os | ident (peer in PgBouncer) | OS user mapping |
peer | peer | Local OS user |
The user field supports four placeholders, replaced with actual user names during rendering: ${dbsu} (superuser), ${repl} (replication user), ${monitor} (monitoring user), and ${admin} (administration user).
A +role prefix matches all members of that role.
Do not confuse transport enforcement with server verification: auth: ssl requires TLS but does not require the client to verify the server identity. Security-sensitive clients should also use sslmode=verify-full with a trusted CA; see Encrypted Communication.
Default Rules Explained
Pigsty’s default HBA policy follows a simple rule: the farther the source, the stronger the requirement. These are the PostgreSQL defaults from the source configuration:
pg_default_hba_rules: # postgres default host-based authentication rules, 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: pwd ,title: 'replicator replication from localhost',order: 200}
- {user: '${repl}' ,db: replication ,addr: intra ,auth: pwd ,title: 'replicator replication from intranet' ,order: 250}
- {user: '${repl}' ,db: postgres ,addr: intra ,auth: pwd ,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: pwd ,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: ssl ,title: 'admin @ everywhere with ssl & pwd' ,order: 500}
- {user: '+dbrole_readonly',db: all ,addr: localhost ,auth: pwd ,title: 'pgbouncer read/write via local socket',order: 550}
- {user: '+dbrole_readonly',db: all ,addr: intra ,auth: pwd ,title: 'read/write biz user via password' ,order: 600}
- {user: '+dbrole_offline' ,db: all ,addr: intra ,auth: pwd ,title: 'allow etl offline tasks from intranet',order: 650}
Layer by layer:
- Local access is most trusted:
postgres can enter only through a local Unix socket with ident. No password is required, but remote login is impossible. This is why dbsu has no password by default. - The intranet comes next: replication and application accounts use SCRAM password authentication on the intranet. Remote monitoring and administration access primarily originates from INFRA nodes.
- Public sources are strictest: only the administrator may connect from any address by default, and the connection requires both a password and TLS.
PgBouncer defaults are more restrictive: public access for monitoring and administration accounts is explicitly denied, while application users are limited to localhost and intranet sources.
The default +dbrole_offline rule does not set role and therefore applies to every instance. To restrict offline users to pg_role: offline or instances with pg_offline_query: true, add role: offline explicitly to the corresponding HBA rule.
This default policy favors usability: application accounts can connect from the intranet with password authentication.
The ha/safe template changes the main TCP rules to ssl and requires administrators outside the intranet to present a client certificate (cert); local ident and selected localhost password rules remain.
Password Policy
Pigsty uses PostgreSQL’s recommended scram-sha-256 password storage by default (pg_pwd_enc). Downgrade to md5 only for legacy client compatibility.
Before executing ALTER USER ... PASSWORD, the password workflow temporarily disables statement logging (SET log_statement TO 'none') to keep passwords out of PostgreSQL logs.
Plaintext passwords still appear in the inventory, and rendered user SQL is written to /pg/tmp/pg-user-<name>.sql with mode 0640. The related Ansible tasks do not use no_log consistently. Restrict access to the admin node, configuration repository, and automation output, and avoid --diff on tasks containing credentials.
Password strength is not enforced by default. If required, preload passwordcheck or the more configurable credcheck:
pg_libs: '$libdir/passwordcheck, pg_stat_statements, auto_explain' # Reject weak passwords
The ha/safe template sets this pg_libs value explicitly. Selecting the CRIT parameter template alone does not load passwordcheck.
Declare account lifetime with expire_in (days after creation) or expire_at (absolute date), then combine it with the organization’s rotation process:
pg_users:
- { name: dbuser_app ,password: '<unique-random-password>' ,roles: [dbrole_readwrite] ,expire_in: 365 }
Certificate Authentication
Passwords can be phished, reused, or guessed. For privileged accounts such as administrators, use auth: cert in HBA to require client certificate authentication.
The client must present a certificate signed by the local CA whose CN matches the database user name. When the HBA rule accepts only cert, a leaked password alone cannot authenticate.
Issue client certificates with the built-in cert.yml playbook:
./cert.yml -e cn=dbuser_dba # Issue a 20-year client certificate by default
./cert.yml -e cn=dbuser_dba -e expire=365d # Or specify a shorter lifetime
The certificate and key are stored in files/pki/misc/<cn>.crt and files/pki/misc/<cn>.key. Deliver the private key through a controlled channel. The client should still use verify-full to authenticate the database server; see Encrypted Communication.
Connection Pool and Component APIs
The database is not the only authenticated entry point.
The PgBouncer connection pool uses an independent HBA policy and user list. pgbouncer_auth_query is disabled by default, so only users declared with pgbouncer: true are written to userlist.txt and can authenticate through the pool. Re-evaluate the login scope before enabling dynamic authentication queries.
The Patroni REST API carries high-availability control operations such as restart, switchover, and configuration reload. Write operations require HTTP Basic authentication (patroni_username and patroni_password) and are restricted by source-address allowlists.
When patroni_ssl_enabled is enabled, the API uses HTTPS throughout.
Credentials for Grafana, the HAProxy administration interface, MinIO, etcd, and other components are also declared in the inventory. See the Default Credentials Checklist for the full list and update guidance.
Next
3 - Access Control
Pigsty turns least privilege into reusable declarative cluster configuration through a built-in four-tier role model and default privilege templates.
Authentication answers “Who are you?” Authorization answers “What may you do?”
Privilege failures rarely result from a lack of mechanisms—PostgreSQL GRANT and REVOKE are sufficiently precise. The usual problem is the absence of conventions that are applied by default:
an application account is made the owner at launch, temporary superuser access is not revoked after troubleshooting, or grants are missed when new tables are created and cause failures in production.
Pigsty provides an out-of-the-box access control model as a starting point: four role tiers, default privileges, and database isolation.
It reduces per-database manual grants, but operators must still assign roles according to business boundaries and review effective privileges regularly.

Role System
Pigsty creates four business roles by default. They cannot log in and are used as privilege groups:
| Role | Attribute | Inherits | Purpose |
|---|
dbrole_readonly | NOLOGIN | — | Global read-only access |
dbrole_readwrite | NOLOGIN | dbrole_readonly | Global DML access; the default choice for application accounts |
dbrole_admin | NOLOGIN | dbrole_readwrite, pg_monitor | Object creation and DDL for administration and release workflows |
dbrole_offline | NOLOGIN | — | Independent read-only role that can be restricted to offline instances through HBA |
Pigsty also creates four system users, each with a specific responsibility:
| User | Attribute | Purpose |
|---|
postgres | SUPERUSER | Database superuser; no password and local ident login only |
replicator | REPLICATION | Streaming replication and backup, with pg_monitor and read-only privileges |
dbuser_dba | SUPERUSER | Routine administration user that inherits dbrole_admin |
dbuser_monitor | — | Monitoring user with only pg_monitor and read-only privileges |
Application accounts join role groups through the roles field and inherit their privileges:
pg_users:
- { name: dbuser_app ,password: '...' ,roles: [dbrole_readwrite] } # Regular application account
- { name: dbuser_report ,password: '...' ,roles: [dbrole_readonly] } # Read-only reporting account
- { name: dbuser_etl ,password: '...' ,roles: [dbrole_offline] } # Offline ETL account
The role system is itself declarative (pg_default_roles) and can be customized.
This parameter is a complete list. Preserve all required system users and default roles when changing it, and check references from HBA rules, default privileges, and scripts at the same time.
Default Privileges
Roles answer “Who receives a privilege?” The other half of the problem is: How do newly created objects receive the correct privileges automatically?
PostgreSQL provides ALTER DEFAULT PRIVILEGES. Pigsty declares these rules through pg_default_privileges:
pg_default_privileges: # Apply these privileges to new objects created by managed identities
- GRANT USAGE ON SCHEMAS TO dbrole_readonly
- GRANT SELECT ON TABLES TO dbrole_readonly
- GRANT SELECT ON SEQUENCES TO dbrole_readonly
- GRANT EXECUTE ON FUNCTIONS TO dbrole_readonly
- GRANT USAGE ON SCHEMAS TO dbrole_offline
- GRANT SELECT ON TABLES TO dbrole_offline
- GRANT SELECT ON SEQUENCES TO dbrole_offline
- GRANT EXECUTE ON FUNCTIONS TO dbrole_offline
- GRANT INSERT ON TABLES TO dbrole_readwrite
- GRANT UPDATE ON TABLES TO dbrole_readwrite
- GRANT DELETE ON TABLES TO dbrole_readwrite
- GRANT USAGE ON SEQUENCES TO dbrole_readwrite
- GRANT UPDATE ON SEQUENCES TO dbrole_readwrite
- GRANT TRUNCATE ON TABLES TO dbrole_admin
- GRANT REFERENCES ON TABLES TO dbrole_admin
- GRANT TRIGGER ON TABLES TO dbrole_admin
- GRANT CREATE ON SCHEMAS TO dbrole_admin
The read-only role receives query and function execution privileges, the read-write role adds DML, and the administrator role adds the supporting privileges required for object management.
Ownership Convention
Default privileges have an often-missed prerequisite: they apply only to objects created by identities for which those defaults were configured. Pigsty configures default privileges for:
- the database OS user
pg_dbsu, which defaults to postgres; - the administration user
pg_admin_username, which defaults to dbuser_dba; dbrole_admin;- each database owner declared in
pg_databases.
Application DDL should normally run as the declared database owner. Platform administration and release workflows can use dbuser_dba or first execute SET ROLE dbrole_admin. Objects created directly by other users do not enter this default privilege model unless ALTER DEFAULT PRIVILEGES is also configured for those users.
This is PostgreSQL behavior, not a Pigsty limitation: default privileges follow the object creator; they do not automatically propagate from the database or the session login name.
Database Isolation
PostgreSQL grants CONNECT on databases to PUBLIC by default. If HBA also permits a connection, a login role may enter a database it does not own. This default is particularly important to tighten when several applications share a cluster.
Set revokeconn in a database definition to revoke public connection access:
pg_databases:
- { name: app_a ,owner: dbuser_a ,revokeconn: true }
- { name: app_b ,owner: dbuser_b ,revokeconn: true }
When enabled, CONNECT is revoked from PUBLIC and granted explicitly to the replication, monitoring, and administration users and to the database owner.
The owner receives GRANT OPTION and can decide who else may connect. Without additional grants or inherited roles, the app_a account cannot connect to app_b.
Cluster initialization also revokes CREATE from PUBLIC on the database and the public schema:
REVOKE CREATE ON DATABASE app FROM PUBLIC;
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
Ordinary users can no longer create objects freely in public databases or schemas, reducing risks from unsafe search_path settings and object shadowing.
PostgreSQL 15 tightened the default CREATE privilege on the public schema; Pigsty applies the same boundary consistently across all supported major versions.
Offline Role and Instance Isolation
dbrole_offline provides an independent set of read-only privileges for ETL, reporting, and ad hoc queries. The role controls object privileges only; it does not automatically restrict which instance a user may connect to.
In the current default HBA rules, the intranet rule for +dbrole_offline does not set role and therefore applies to every instance. To restrict it to a dedicated pg_role: offline instance, or to a regular replica marked with pg_offline_query: true, modify that rule in the complete pg_default_hba_rules list:
pg_default_hba_rules:
# Copy and retain all other default rules; change only the offline-role rule
- { user: '+dbrole_offline', db: all, addr: intra, auth: pwd, role: offline, order: 650,
title: 'allow offline users on offline instances' }
Defining pg_default_hba_rules replaces the entire default list; the example rule cannot be used alone. Expensive queries are limited to offline instances only when HBA filters by instance role and the user does not inherit another role allowed by broader rules. Resource isolation should also use a dedicated service endpoint, connection limits, and query resource controls.
Beyond the Database
Least privilege also applies at the host level:
- The
postgres superuser has no password and can log in only through local ident. Its sudo access defaults to a restricted set of database service and log commands (pg_dbsu_sudo: limit). - The monitoring user
dbuser_monitor holds pg_monitor, the read-only role, and privileges on the dedicated monitor schema; it cannot write business tables by default. - The replication user
replicator receives only the directory function privileges required for backup and recovery instead of broad superuser access.
Next
4 - Encrypted Communication
Pigsty provides a self-signed CA that issues certificates and distributes trust for managed components, creating a unified TLS foundation.
TLS can provide three separate protections: transport encryption, server authentication, and client authentication. Each must be configured independently. Enabling server-side TLS does not mean the client verifies the server identity, nor does it mean the server requires a client certificate.
The main operational cost of TLS is not the encryption algorithm but certificate issuance, distribution, trust, and rotation. Without centralized management, internal services often encrypt traffic while skipping certificate verification—or remain on plaintext connections.
Pigsty brings PKI under declarative management. During deployment it creates a local self-signed CA, issues certificates for managed components, and distributes trust so TLS is ready for use after installation.
Local CA
During the first deployment, Pigsty checks for a CA on the admin node and creates one when required:
| File | Description | Permissions |
|---|
files/pki/ca/ca.key | CA private key and root of trust for the deployment; protect it carefully | 0600, with directory mode 0700 |
files/pki/ca/ca.crt | CA root certificate; safe to distribute | 0644 |
ca_create controls CA behavior. Existing files are reused unchanged for idempotency; otherwise a new CA is created.
If set to false and the CA files are missing, deployment fails instead of silently creating a new trust root.ca_cn sets the CA certificate CN, which defaults to pigsty-ca. The key is RSA 4096.- The root CA is valid for 100 years, while component certificates default to 20 years (
cert_validity: 7300d).
The browser-facing Nginx certificate is an exception and currently defaults to 397 days.
Long default lifetimes reduce the initial maintenance burden for private infrastructure; they do not remove the need for production rotation. Organizations with an established certificate policy should shorten lifetimes and monitor expiration.
Trust Distribution
Issuing a certificate is only half of PKI. Every node must trust it. When a node is managed, Pigsty distributes the CA certificate to /etc/pki/ca.crt and links it into the operating system trust store:
- EL family (RHEL, Rocky, Alma): link under
/etc/pki/ca-trust/source/anchors/ and run update-ca-trust - Debian and Ubuntu: link under
/usr/local/share/ca-certificates/ and run update-ca-certificates
Clients that use the OS trust store, such as curl, can then verify certificates signed by the Pigsty CA.
The CA certificate is also published as ca.crt at the site root of the Nginx portal for browsers and external clients.
PostgreSQL libpq clients require special attention: by default they look for ~/.postgresql/root.crt and use sslmode=prefer, so they do not directly use the operating system trust store to verify the server identity.
Server Identity Verification
Security-sensitive PostgreSQL clients should use sslmode=verify-full and specify the Pigsty CA:
psql "host=pg-meta dbname=postgres user=dbuser_dba sslmode=verify-full sslrootcert=/etc/pki/ca.crt"
verify-full validates both the certificate chain and the connection host name. The DNS name or IP address used by the client must therefore appear in the server certificate SAN. External clients must install ca.crt or specify it with sslrootcert.
Certificate Matrix
The local CA issues certificates for the following components and places them under one trust chain:
| Component | Certificate Identity (CN) | Deployment Path | Encryption State |
|---|
| PostgreSQL | <cluster>-<sequence> | /pg/cert/server.{crt,key} | Server-side SSL enabled by default; HBA determines whether it is mandatory |
| PgBouncer | Reuses the PostgreSQL certificate | /pg/cert/ | TLS disabled by default (pgbouncer_sslmode) |
| Patroni | Reuses the PostgreSQL certificate | /pg/cert/ | API HTTPS disabled by default (patroni_ssl_enabled) |
| etcd | <instance-name> | /etc/etcd/server.{crt,key} | TLS for client and peer traffic |
| MinIO | <node-name> | ~minio/.minio/certs/ | HTTPS enabled by default (minio_https) |
| Nginx | pigsty, with portal domains in SAN | /etc/nginx/conf.d/cert/ | HTTPS enabled by default (nginx_sslmode) |
| INFRA node | <node-name> | /etc/pki/infra.{crt,key} | Available to infrastructure components |
The encryption-state column reflects deliberate defaults:
- Enabled at deployment: PostgreSQL accepts SSL connections; etcd uses TLS for client and peer traffic.
- Encrypted by default: MinIO backup traffic and Nginx web traffic use HTTPS.
- Disabled by default, available on demand: TLS for the Patroni REST API and PgBouncer is disabled by default, but certificates are already present. Enable it through the corresponding parameters; both are enabled in the
ha/safe template.
Keep three states distinct: server-side SSL support does not force clients to use SSL, and neither state proves that the client verifies the server identity.
HBA rules enforce encryption with auth: ssl or cert. Client sslmode and trust settings control server verification. The default rules require TLS only for administrator connections from arbitrary sources. The safe template changes the main TCP rules to ssl or cert while retaining local ident and selected localhost password rules.
Client Certificates
The built-in cert.yml playbook issues client certificates. The certificate CN represents the database user name for HBA cert authentication:
./cert.yml -e cn=dbuser_dba # Issue a 20-year client certificate by default
./cert.yml -e cn=dbuser_dba -e expire=365d # Or specify a shorter lifetime
Results are stored in files/pki/misc/<cn>.key and files/pki/misc/<cn>.crt. Deliver private keys through a controlled channel and make them readable only by the corresponding user. The client certificate lets the server authenticate the client; the client must still use verify-full to authenticate the database server.
Using an Enterprise CA
If the organization already operates a PKI, Pigsty can issue certificates from that CA, or from an intermediate signed by the enterprise root. Place the certificate and private key at the expected paths; playbooks do not regenerate a CA when one already exists:
files/pki/ca/ca.key # CA or intermediate CA private key
files/pki/ca/ca.crt # Corresponding CA certificate
Also set ca_create: false. Deployment will then fail explicitly if the files are missing instead of creating an unexpected self-signed CA and breaking the existing trust chain.
Key Protection and Rotation
- The CA private key exists only on the admin node. Together with
pigsty.yml, it is one of the highest-trust assets in the deployment; see Trust Boundaries. Keep an offline backup. - If the CA private key is compromised, establish a new trust root and reissue every component and client certificate. Plan an overlap period in which both old and new CAs are trusted to avoid interrupting all connections at once.
- Component certificate sources are stored under
files/pki/<component>/ on the admin node; node certificates are deployment copies. Deleting only a node copy restores the same certificate rather than issuing a new one. To rotate, update or remove the corresponding source on the admin node, rerun the relevant playbook, then reload or roll the component as required.
Next
5 - Data Security
Protect PostgreSQL data integrity, recoverability, confidentiality, and traceability with checksums, backup and PITR, encryption, and audit logs.
Network boundaries, authentication, and access control reduce the likelihood of an incident. When hardware fails, credentials leak, or an operator makes a mistake, data-layer controls must limit the impact and support recovery.
Data security answers four questions: Is the data intact? Can it be recovered? If copied, does it remain confidential? Can you determine what happened?
Integrity
Bad disk sectors, memory bit flips, and storage firmware defects can cause silent data corruption: the data is damaged without an immediate error.
Pigsty enables page checksums by default (pg_checksum: true).
The cluster is initialized with data-checksums, so PostgreSQL calculates a checksum when writing a page and verifies it when reading.
Page checksums primarily detect corruption in storage media, the I/O path, or pages after they were written. They do not detect every memory error, logical error, or incorrect application write, and they do not replace backups.
The CRIT parameter template goes further: checksums are mandatory regardless of the parameter, and strict synchronous replication (synchronous_mode_strict) blocks writes that require synchronous acknowledgment when no synchronous replica is available.
This mode targets preservation of acknowledged transactions, but it still assumes clients have not reduced synchronous_commit, a synchronous replica participates in the commit, and failover selects only a node containing the required WAL. Validate RPO through failure exercises on the target topology.
Recoverability
Replicas primarily handle node failures; backups handle accidental deletion, logical errors, cluster corruption, and broader disasters.
High availability can shorten an interruption after primary failure, but replication also copies an accidental deletion to every replica. Backups are therefore indispensable.
Pigsty enables pgBackRest by default (pgbackrest_enabled).
Base backups plus continuous WAL archiving provide Point-in-Time Recovery (PITR), allowing recovery to a target time within the retained backup and WAL window.
Select the backup repository with pgbackrest_method:
| Repository | Location | Default Retention | Encryption |
|---|
local (default) | Local /pg/backup directory | Latest 2 full backups | None |
minio | MinIO or S3-compatible object storage | 14 days | AES-256-CBC |
Two additional controls reduce damage from accidental deletion:
- Delayed replica: declare a
pg_delay: 1h replica for a critical cluster. Before an erroneous operation is replayed, pause replication and extract the required data. A delayed replica eventually catches up and does not replace a backup. - Removal safeguards: when
pg_safeguard or etcd_safeguard is enabled, the corresponding removal playbook refuses to run, reducing the risk of accidental cluster removal.
Having a backup is not the same as being able to restore. Recovery exercises should be routine; see Backup and Recovery for mechanisms and procedures.
Confidentiality
Protect data at rest at three layers:
Backup encryption. The MinIO repository uses AES-256-CBC by default, but its default encryption passphrase (pgBackRest) is public and must be changed in production.
The ha/safe template derives an example passphrase from the cluster name:
pgbackrest_repo:
minio:
cipher_type: aes-256-cbc
cipher_pass: 'pgBR.${pg_cluster}' # Example only; replace before deployment
pgBR.${pg_cluster} is predictable, and configure -g does not replace it. Use a unique random passphrase in production and store it separately from the backup. Losing the passphrase makes the backup unrecoverable.
The local backup repository is not encrypted by default. Encryption reduces disclosure if backup files or media are copied separately, but offers limited protection when the key and backup remain on the same host.
Transport encryption. Backup uploads to MinIO use HTTPS. PostgreSQL client and replication traffic can require SSL through HBA. Clients should also verify the server certificate; see Encrypted Communication.
Encryption at rest. Upstream PostgreSQL currently has no general built-in transparent data encryption (TDE). Pigsty provides two practical options:
use the pg_tde extension with Percona Distribution for PostgreSQL for table-level transparent encryption (see the pgtde configuration template);
or use security extensions such as pgsodium, pgcrypto, and anonymizer for column-level encryption and masking. The safe template installs this extension category.
Full-disk encryption such as LUKS or dm-crypt protects against stolen media at the operating-system layer and complements database-level controls.
Audit and Traceability
After an incident, you must be able to answer who did what and when. Pigsty provides layered logging:
Default baseline: all DDL is logged (log_statement: ddl), and statements taking longer than 100 ms are logged (log_min_duration_statement: 100).
PostgreSQL 18 and later also record connection authorization events.
CRIT template: connection and disconnection events are recorded with log_connections and log_disconnections. PostgreSQL 18 can distinguish connection receipt, authentication, and authorization stages.
pgaudit extension: for fine-grained statement auditing such as object reads and writes or role-based audit classes, install pgaudit and add it to pg_libs for preloading.
The safe template installs the extension, but loading and audit policy must be declared explicitly.
When INFRA logging is enabled and Vector is configured, PostgreSQL logs are sent to VictoriaLogs for centralized storage. The default retention is 15 days and can be adjusted for compliance.
Logs and metrics support search, alerts, and incident reconstruction, but incident classification, response, and evidence preservation still require an operational process.
Next
6 - Compliance
Compliance combines configuration, process, and evidence. This page covers launch hardening, MLPS and SOC 2 control mappings, supply-chain integrity, and vulnerability response.
Compliance is not a product you can buy. It is a state that must be demonstrated continuously through three elements:
- Configuration: whether security controls are enabled. Pigsty directly provides this part.
- Process: access approval, change management, recovery exercises, and related procedures. The organization must establish these.
- Evidence: records showing that configuration and process remain effective. Pigsty’s inventory, runtime logs, and monitoring system can provide part of this evidence.
This page begins with a pre-launch hardening checklist and then maps Pigsty security capabilities to common compliance frameworks.
The mappings support architecture and gap analysis; they are not an MLPS assessment conclusion, a SOC 2 audit opinion, or legal advice.
Default Credentials Checklist
Pigsty default credentials are public in the documentation and source code. They are intended only for demonstrations and local development. Change every applicable default before any production or network-exposed deployment goes live:
| Scope | Example Default | configure -g |
|---|
| Grafana administrator and viewer | pigsty, DBUser.Viewer | Yes |
| HAProxy administration interface | pigsty | Yes |
| PostgreSQL administration, monitoring, and replication users | DBUser.DBA, DBUser.Monitor, DBUser.Replicator | Yes |
| Patroni REST API | Patroni.API | Yes |
| etcd root | Etcd.Root | Yes |
| MinIO root | S3User.MinIO | Yes |
| MinIO backup and example application users | S3User.Backup, S3User.Meta, S3User.Data | Yes |
| Example database users | DBUser.Meta, DBUser.Supa, Vibe.Coding | Yes |
| pgBackRest encryption passphrase | cipher_pass: pgBackRest | No |
MinIO users and pgBR.${pg_cluster} in ha/safe | Template example values | No |
| User-defined credentials | Custom values | No |
Use -g while generating configuration to randomize built-in parameters and example strings recognized by the configuration wizard:
./configure -g # Generate the inventory and randomize recognized default credentials
The wizard prints generated passwords to the terminal, so protect terminal history and automation logs as sensitive data. After generation, inspect the configuration and replace pgBackRest cipher_pass, MinIO example values in ha/safe that were not covered, and all custom credentials.
Launch Hardening Checklist
Before deployment:
After deployment:
Periodically:
Compliance Evidence
Declarative configuration provides a stable starting point for audit evidence. Retain runtime state as well to show that the configuration was applied and remains effective.
| Evidence | Source |
|---|
| Security baseline and change history | The pigsty.yml inventory and Git history |
| Access-control matrix | pg_default_roles, pg_users, and pg_hba_rules declarations |
| Effective authentication policy | Rendered pg_hba.conf on each instance, compared with declarations to detect drift |
| Effective users and privileges | PostgreSQL catalogs, database ACLs, \du+, and \ddp+ |
| Operation and connection logs | PostgreSQL DDL, slow-query, and connection logs retained in VictoriaLogs |
| Backup records | pgBackRest information and monitoring dashboards |
| Security incidents and alerts | Monitoring alert history |
| Certificate inventory | files/pki/ and deployed component certificates |
MLPS Level 3 Mapping
The following maps database-related Pigsty capabilities to controls in the “secure computing environment” section of GB/T 22239-2019 Level 3:
| Control | Pigsty Capability | Additional Requirement |
|---|
| Unique identity | Independent accounts and SCRAM-SHA-256 password storage | Real-name account management process |
| Password complexity and rotation | passwordcheck, credcheck, and expire_in | Enable extensions and establish a rotation process |
| Login failure handling | Can be implemented with credcheck and related extensions | Enable and configure as required |
| Access control and least privilege | Four-tier roles, default privileges, and database isolation | Privilege approval workflow |
| Security audit | DDL, connection, and slow-query logs; pgaudit; centralized retention | CRIT or manual connection logging; required retention period |
| Communication confidentiality | Local CA and TLS; HBA-enforced ssl or cert | Enforce TLS, client verify-full, and certificate rotation |
| Data integrity | Page checksums by default and strict synchronous replication with CRIT | Storage protection, defined failure model, and exercises |
| Data confidentiality | AES-encrypted backup plus TDE and column-encryption options | Enable as required |
| Backup and recovery | pgBackRest, PITR, and remote MinIO repository | Recovery exercise process |
| Residual information protection | — | Media destruction and erasure process |
MLPS also covers physical security, communication networks, and management systems beyond the scope of a database distribution.
Pigsty can support database-related technical controls in a secure computing environment; facilities, network devices, and governance must be addressed in the overall system.
SOC 2 Mapping
Database-related controls in the SOC 2 Trust Services Criteria (TSC) include:
| Criterion | Pigsty Capability | Additional Requirement |
|---|
| CC6.1 Logical access security | HBA, RBAC, default privileges, and database isolation | Privilege design, approval, and periodic review |
| CC6.2 User registration and authorization | Declarative users, roles, and expiration | Joiner, mover, leaver, and identity-verification process |
| CC6.3 Access changes and revocation | pg_users, role changes, REVOKE, and expiration | Tickets, approval evidence, and timely revocation |
| CC6.6 External boundary threats | Firewalls, listen addresses, HBA, and restricted management ingress | Network architecture, boundary devices, and continuous validation |
| CC6.7 Information transmission and movement | TLS, client verification, and backup encryption | Policies for exports, media, and third-party transfer |
| CC7.2 System monitoring | Victoria observability stack with extensive metrics and alerts | Alert-response process |
| CC7.3 Incident traceability | Centralized logs and audit extensions | Log-review process |
| A1.2 Availability and recovery | High Availability and PITR | Exercise records and RTO/RPO objectives |
Supply Chain and Vulnerability Response
Compliance reviews increasingly cover the software supply chain. Pigsty provides the following distribution and response controls:
Package integrity: RPM and DEB packages in the Pigsty repositories (repo.pigsty.io and repo.pigsty.cc) are GPG-signed.
The public-key fingerprint is 9592 A7BC 7A68 2E73 3337 6E09 E793 5D8D B9BD 8B20 (B9BD8B20) and can be verified before trust is established. Repository definitions written during deployment and the local repository on the INFRA node do not enforce signature verification for every package by default; review package-manager repository trust and signature settings in production.
Vulnerability response: report security issues privately through GitHub private vulnerability reporting or email, as documented in SECURITY.md.
The project targets acknowledgment within three business days and an initial assessment within seven days.
Version support: security fixes ship with the latest stable release. Staying current is the standard way to receive them. Users who must remain on a version for longer can obtain extended support through subscription services.
Next