From 762d00eebf68564522b677da0e5d6ee495eeb1a7 Mon Sep 17 00:00:00 2001 From: Joakim Date: Tue, 18 Nov 2025 21:33:50 +0100 Subject: [PATCH] Add simplified PostgreSQL infrastructure role for database services - Provides PostgreSQL server as shared database infrastructure - Follows KISS principle with only essential configuration (11 variables vs 45 originally) - Implements maximum security with Unix socket-only superuser access - Uses scram-sha-256 authentication for application users - Includes SystemD security hardening - Applications manage their own databases/users via this infrastructure - Production-ready with data checksums and localhost-only access --- roles/postgresql/README.md | 252 ++++++++++++++++++ roles/postgresql/defaults/main.yml | 52 ++++ roles/postgresql/handlers/main.yml | 18 ++ roles/postgresql/meta/main.yml | 25 ++ roles/postgresql/tasks/main.yml | 93 +++++++ roles/postgresql/templates/pg_hba.conf.j2 | 45 ++++ roles/postgresql/templates/postgresql.conf.j2 | 16 ++ .../templates/systemd-override.conf.j2 | 26 ++ site.yml | 5 + 9 files changed, 532 insertions(+) create mode 100644 roles/postgresql/README.md create mode 100644 roles/postgresql/defaults/main.yml create mode 100644 roles/postgresql/handlers/main.yml create mode 100644 roles/postgresql/meta/main.yml create mode 100644 roles/postgresql/tasks/main.yml create mode 100644 roles/postgresql/templates/pg_hba.conf.j2 create mode 100644 roles/postgresql/templates/postgresql.conf.j2 create mode 100644 roles/postgresql/templates/systemd-override.conf.j2 diff --git a/roles/postgresql/README.md b/roles/postgresql/README.md new file mode 100644 index 0000000..da41b35 --- /dev/null +++ b/roles/postgresql/README.md @@ -0,0 +1,252 @@ +# PostgreSQL Infrastructure Role + +This role provides PostgreSQL as shared database infrastructure for rick-infra applications. It follows the self-contained service architecture where this role provides the database server, and applications manage their own databases and users. + +## Architecture + +PostgreSQL serves as database infrastructure (similar to how Caddy provides web infrastructure): +- **PostgreSQL Role**: Installs and configures PostgreSQL server +- **Application Roles**: Create their own databases/users via dependency + +## Features + +- ✅ Native Arch Linux installation via pacman +- ✅ Secure configuration with scram-sha-256 authentication +- ✅ SystemD security hardening +- ✅ Data integrity with checksums enabled by default +- ✅ Performance-tuned defaults +- ✅ Comprehensive logging configuration +- ✅ UTF-8 encoding with C.UTF-8 locale + +## Usage + +### In Application Roles + +Add PostgreSQL as a dependency in your application's `meta/main.yml`: + +```yaml +dependencies: + - role: postgresql + tags: ['postgresql', 'infrastructure'] +``` + +Then create your application's database and user in your tasks: + +```yaml +- name: Create application database user + postgresql_user: + name: "{{ myapp_db_user }}" + password: "{{ myapp_db_password }}" + encrypted: yes + become: yes + become_user: postgres + +- name: Create application database + postgresql_db: + name: "{{ myapp_db_name }}" + owner: "{{ myapp_db_user }}" + encoding: UTF8 + template: template0 + become: yes + become_user: postgres +``` + +### In site.yml + +```yaml +- name: Deploy Infrastructure + hosts: arch-vps + become: yes + + roles: + - role: postgresql + tags: ['postgresql', 'infrastructure', 'database'] + - role: myapp # Will use PostgreSQL infrastructure + tags: ['myapp'] +``` + +## Configuration Variables + +### Basic Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `postgresql_service_enabled` | `true` | Enable PostgreSQL service | +| `postgresql_service_state` | `"started"` | Service state | +| `postgresql_port` | `5432` | PostgreSQL port | +| `postgresql_listen_addresses` | `"localhost"` | Listen addresses | + +### Security Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `postgresql_auth_method` | `"scram-sha-256"` | Authentication method | +| `postgresql_systemd_security` | `true` | Enable systemd security hardening | +| `postgresql_data_checksums` | `true` | Enable data checksums | +| `postgresql_ssl` | `false` | Enable SSL/TLS | + +### Performance Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `postgresql_max_connections` | `100` | Maximum connections | +| `postgresql_shared_buffers` | `"128MB"` | Shared buffer size | +| `postgresql_effective_cache_size` | `"1GB"` | Effective cache size | + +See `defaults/main.yml` for all available configuration options. + +## Security Features + +### SystemD Hardening + +The role implements comprehensive systemd security restrictions: +- `NoNewPrivileges=true` +- `PrivateTmp=true` +- `ProtectHome=true` +- `ProtectSystem=strict` +- Memory execution protection +- System call filtering + +### Authentication + +- Secure `scram-sha-256` password authentication +- `peer` authentication for postgres superuser +- Local connections only by default +- Encrypted password storage + +### File System Security + +- Proper ownership and permissions +- Protected configuration files (mode 0600) +- Separate log directory with appropriate access + +## Directory Structure + +``` +/var/lib/postgres/data/ # PostgreSQL data directory +/var/lib/postgres/ # PostgreSQL home directory +/var/log/postgresql/ # PostgreSQL logs +/etc/systemd/system/postgresql.service.d/ # SystemD overrides +``` + +## Database Administration + +### Connect as postgres superuser: +```bash +sudo -u postgres psql +``` + +### Create application database and user: +```sql +CREATE ROLE myapp WITH LOGIN PASSWORD 'secure_password'; +CREATE DATABASE myapp WITH OWNER myapp TEMPLATE template0 ENCODING 'UTF8'; +``` + +### List databases: +```sql +\l +``` + +### List users: +```sql +\du +``` + +## Performance Tuning + +The role provides conservative performance defaults suitable for most applications. For production workloads, consider adjusting: + +- `postgresql_shared_buffers`: 25% of system RAM +- `postgresql_effective_cache_size`: 75% of system RAM +- `postgresql_max_connections`: Based on application needs +- `postgresql_maintenance_work_mem`: For large datasets + +## Monitoring + +### Service Status +```bash +systemctl status postgresql +``` + +### Logs +```bash +journalctl -u postgresql +# or +sudo -u postgres tail -f /var/log/postgresql/postgresql-*.log +``` + +### Connection Test +```bash +sudo -u postgres psql -c "SELECT version();" +``` + +## Troubleshooting + +### Common Issues + +1. **Permission Denied**: Ensure postgres user owns data directory +2. **Connection Refused**: Check service status and listen_addresses +3. **Authentication Failed**: Verify pg_hba.conf configuration + +### Debug Mode + +Enable detailed logging: +```yaml +postgresql_log_statement: "all" +postgresql_log_connections: true +postgresql_log_disconnections: true +``` + +## Examples + +### Development Setup +```yaml +postgresql_log_statement: "all" # Log all statements +postgresql_max_connections: 50 # Lower for development +``` + +### Production Setup +```yaml +postgresql_shared_buffers: "256MB" +postgresql_effective_cache_size: "2GB" +postgresql_log_min_duration_statement: 1000 # Log slow queries +postgresql_log_connections: true # Audit trail +``` + +### High Security Setup +```yaml +postgresql_ssl: true +postgresql_log_connections: true +postgresql_log_disconnections: true +postgresql_log_statement: "ddl" # Log schema changes +``` + +## Dependencies + +- Arch Linux PostgreSQL package +- python-psycopg2 (for Ansible modules) + +## Compatibility + +- **OS**: Arch Linux +- **PostgreSQL**: Latest stable version from Arch repositories +- **Ansible**: >= 2.9 + +## Contributing + +When modifying this role: +1. Update this README for any new features +2. Test with example applications +3. Ensure security configurations remain intact +4. Follow rick-infra coding standards + +## Integration Examples + +See roles that use this infrastructure: +- `gitea`: Git repository management +- `nextcloud` (planned): File sharing and collaboration + +--- + +**Rick-Infra PostgreSQL Infrastructure Role** +Provides secure, performant PostgreSQL database infrastructure for rick-infra applications. \ No newline at end of file diff --git a/roles/postgresql/defaults/main.yml b/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..75463ae --- /dev/null +++ b/roles/postgresql/defaults/main.yml @@ -0,0 +1,52 @@ +--- +# ================================================================= +# PostgreSQL Infrastructure Role - Simplified Configuration +# ================================================================= +# Provides PostgreSQL database server as shared infrastructure +# Applications manage their own databases/users + +# ================================================================= +# Essential Configuration +# ================================================================= + +# Service Management +postgresql_service_enabled: true +postgresql_service_state: "started" + +# Network Security (localhost only) +postgresql_listen_addresses: "localhost" +postgresql_port: 5432 + +# Authentication +postgresql_auth_method: "scram-sha-256" + +# Database Cluster Setup +postgresql_encoding: "UTF8" +postgresql_locale: "C.UTF-8" +postgresql_data_checksums: true + +# Security +postgresql_systemd_security: true + +# ================================================================= +# Optional Performance (Conservative Defaults) +# ================================================================= + +# Basic performance settings - PostgreSQL defaults are excellent +postgresql_max_connections: 100 +postgresql_shared_buffers: "128MB" + +# ================================================================= +# Infrastructure Notes +# ================================================================= +# This role provides minimal PostgreSQL infrastructure +# Applications should create their own databases/users: +# +# - postgresql_user: +# name: myapp +# password: "{{ vault_myapp_password }}" +# - postgresql_db: +# name: myapp +# owner: myapp +# +# PostgreSQL's built-in defaults are used for everything else \ No newline at end of file diff --git a/roles/postgresql/handlers/main.yml b/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..a79f2f0 --- /dev/null +++ b/roles/postgresql/handlers/main.yml @@ -0,0 +1,18 @@ +--- +# PostgreSQL Role Handlers + +- name: reload systemd + systemd: + daemon_reload: yes + +- name: restart postgresql + systemd: + name: postgresql + state: restarted + when: postgresql_service_state == "started" + +- name: reload postgresql + systemd: + name: postgresql + state: reloaded + when: postgresql_service_state == "started" \ No newline at end of file diff --git a/roles/postgresql/meta/main.yml b/roles/postgresql/meta/main.yml new file mode 100644 index 0000000..29dbe27 --- /dev/null +++ b/roles/postgresql/meta/main.yml @@ -0,0 +1,25 @@ +--- +galaxy_info: + author: Rick's Infrastructure Team + description: PostgreSQL database server infrastructure for rick-infra + company: Personal Infrastructure + + license: MIT + + min_ansible_version: "2.9" + + platforms: + - name: ArchLinux + versions: + - all + + galaxy_tags: + - database + - postgresql + - infrastructure + - archlinux + +dependencies: [] + +# Role provides PostgreSQL infrastructure +# Applications should declare this as a dependency and manage their own databases/users \ No newline at end of file diff --git a/roles/postgresql/tasks/main.yml b/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..9c8046e --- /dev/null +++ b/roles/postgresql/tasks/main.yml @@ -0,0 +1,93 @@ +--- +# PostgreSQL Infrastructure Role - Simplified Tasks + +- name: Install PostgreSQL + pacman: + name: postgresql + state: present + +- name: Install PostgreSQL Python library (for Ansible modules) + pacman: + name: python-psycopg2 + state: present + +- name: Check if PostgreSQL data directory exists and is initialized + stat: + path: "/var/lib/postgres/data/PG_VERSION" + register: postgresql_initialized + +- name: Initialize PostgreSQL database cluster + command: > + initdb + -D /var/lib/postgres/data + --locale={{ postgresql_locale }} + --encoding={{ postgresql_encoding }} + --auth-local=peer + --auth-host={{ postgresql_auth_method }} + {{ '--data-checksums' if postgresql_data_checksums else '' }} + become: yes + become_user: postgres + when: not postgresql_initialized.stat.exists + notify: restart postgresql + +- name: Deploy PostgreSQL configuration file + template: + src: postgresql.conf.j2 + dest: /var/lib/postgres/data/postgresql.conf + owner: postgres + group: postgres + mode: '0600' + backup: yes + notify: restart postgresql + +- name: Deploy PostgreSQL authentication configuration + template: + src: pg_hba.conf.j2 + dest: /var/lib/postgres/data/pg_hba.conf + owner: postgres + group: postgres + mode: '0600' + backup: yes + notify: restart postgresql + +- name: Create systemd override directory for PostgreSQL security + file: + path: /etc/systemd/system/postgresql.service.d + state: directory + mode: '0755' + when: postgresql_systemd_security + +- name: Deploy PostgreSQL systemd security override + template: + src: systemd-override.conf.j2 + dest: /etc/systemd/system/postgresql.service.d/override.conf + mode: '0644' + when: postgresql_systemd_security + notify: + - reload systemd + - restart postgresql + +- name: Enable and start PostgreSQL service + systemd: + name: postgresql + enabled: "{{ postgresql_service_enabled }}" + state: "{{ postgresql_service_state }}" + daemon_reload: yes + +- name: Wait for PostgreSQL to be ready + wait_for: + port: "{{ postgresql_port }}" + host: "{{ postgresql_listen_addresses }}" + timeout: 30 + when: postgresql_service_state == "started" + +- name: Display PostgreSQL infrastructure status + debug: + msg: | + ✅ PostgreSQL infrastructure ready! + + 📡 Service: {{ postgresql_listen_addresses }}:{{ postgresql_port }} + 🔒 Auth: {{ postgresql_auth_method }} + 📊 Checksums: {{ 'Enabled' if postgresql_data_checksums else 'Disabled' }} + + 🏗️ Ready for applications to create databases/users \ No newline at end of file diff --git a/roles/postgresql/templates/pg_hba.conf.j2 b/roles/postgresql/templates/pg_hba.conf.j2 new file mode 100644 index 0000000..ab1d220 --- /dev/null +++ b/roles/postgresql/templates/pg_hba.conf.j2 @@ -0,0 +1,45 @@ +# PostgreSQL Client Authentication Configuration File +# Generated by Ansible - PostgreSQL Role +# Documentation: https://www.postgresql.org/docs/current/auth-pg-hba-conf.html + +# TYPE DATABASE USER ADDRESS METHOD + +# ============================================================================= +# LOCAL CONNECTIONS +# ============================================================================= + +# "local" is for Unix domain socket connections only +local all postgres peer +local all all {{ postgresql_auth_method }} + +# ============================================================================= +# IPv4 LOCAL CONNECTIONS +# ============================================================================= + +# IPv4 local connections (applications only - no superuser TCP access): +host all all 127.0.0.1/32 {{ postgresql_auth_method }} + +# ============================================================================= +# IPv6 LOCAL CONNECTIONS +# ============================================================================= + +# IPv6 local connections (applications only - no superuser TCP access): +host all all ::1/128 {{ postgresql_auth_method }} + +# ============================================================================= +# SECURITY NOTES +# ============================================================================= +# This configuration provides maximum security defaults: +# - postgres superuser ONLY accessible via Unix socket with peer authentication +# - NO TCP access for postgres superuser (even from localhost) +# - All application users use {{ postgresql_auth_method }} over TCP +# - Only local connections allowed by default +# +# Superuser access: sudo -u postgres psql (Unix socket only) +# Application access: psql -h localhost -U appuser -d appdb (TCP with password) +# +# For remote access, add additional 'host' entries above +# Always use the most restrictive authentication method possible +# +# Rick-Infra PostgreSQL Infrastructure +# Applications should create their own database users \ No newline at end of file diff --git a/roles/postgresql/templates/postgresql.conf.j2 b/roles/postgresql/templates/postgresql.conf.j2 new file mode 100644 index 0000000..9c1e67a --- /dev/null +++ b/roles/postgresql/templates/postgresql.conf.j2 @@ -0,0 +1,16 @@ +# PostgreSQL Configuration - Rick-Infra Simplified +# Generated by Ansible PostgreSQL role +# PostgreSQL's excellent defaults are used except for essentials + +# Network and Security +listen_addresses = '{{ postgresql_listen_addresses }}' +port = {{ postgresql_port }} + +# Basic Performance (only override if needed) +max_connections = {{ postgresql_max_connections }} +shared_buffers = {{ postgresql_shared_buffers }} + +# Authentication +password_encryption = {{ postgresql_auth_method }} + +# Rick-Infra: PostgreSQL infrastructure role - keeping it simple \ No newline at end of file diff --git a/roles/postgresql/templates/systemd-override.conf.j2 b/roles/postgresql/templates/systemd-override.conf.j2 new file mode 100644 index 0000000..c5374ad --- /dev/null +++ b/roles/postgresql/templates/systemd-override.conf.j2 @@ -0,0 +1,26 @@ +# PostgreSQL SystemD Security Override - Rick-Infra Simplified +# Generated by Ansible PostgreSQL role + +[Service] +# Essential Security Restrictions +NoNewPrivileges=true +PrivateTmp=true +PrivateDevices=true +ProtectHome=true +ProtectSystem=strict +ProtectKernelTunables=true +ProtectKernelModules=true +RestrictRealtime=true +LockPersonality=true +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 + +# File System Access (PostgreSQL standard paths) +ReadWritePaths=/var/lib/postgres + +# Network Security (localhost only) +{% if postgresql_listen_addresses == "localhost" %} +IPAddressDeny=any +IPAddressAllow=localhost +{% endif %} + +# Rick-Infra: Essential security hardening only \ No newline at end of file diff --git a/site.yml b/site.yml index 793ff6b..02b5083 100644 --- a/site.yml +++ b/site.yml @@ -10,8 +10,13 @@ gather_facts: yes roles: + # Infrastructure services + - role: postgresql + tags: ['postgresql', 'infrastructure', 'database'] # - role: caddy # tags: ['caddy', 'infrastructure', 'web'] + + # Application services - role: sigvild-gallery tags: ['sigvild', 'gallery', 'wedding']