diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/defaults/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/defaults/main.yml new file mode 100644 index 0000000..ab44d75 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/defaults/main.yml @@ -0,0 +1,14 @@ +--- +postgresql_install_server: true +postgresql_install_client: true +postgresql_nft_filter_input: true +postgresql_nft_filter_output: false +postgresql_nft_allowed_ingress_list: ['127.0.0.1/32'] +postgresql_nft_allowed_egress_list: [] +postgresql_server_port: 5432 +postgresql_server_run_init_sql: false +postgresql_server_run_custom_sql: false +postgresql_server_custom_sql: "" +postgresql_server_bind_addresses: false +postgresql_server_accounts_list: {} +postgresql_server_encryption_scheme: scram-sha-256 diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/handlers/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/handlers/main.yml new file mode 100644 index 0000000..17797a2 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/handlers/main.yml @@ -0,0 +1,18 @@ +--- +- name: reload postgresql service + become: true + systemd_service: + name: postgresql.service + enabled: true + state: reloaded + +- name: restart postgresql service + become: true + ansible.builtin.systemd_service: + name: postgresql.service + enabled: true + state: restarted + +- name: load firewall rules + become: true + ansible.builtin.command: /usr/sbin/nft -f /etc/nftables.d/postgresql.nft diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/meta/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/meta/main.yml new file mode 100644 index 0000000..4cd9d86 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/meta/main.yml @@ -0,0 +1,20 @@ +--- +galaxy_info: + author: Florian L. + namespace: nullified + description: Install PostgreSQL and configure it, along with firewall rules + license: MIT + min_ansible_version: 2.15 + + # https://galaxy.ansible.com/api/v1/platforms/ + platforms: + - name: Debian + versions: + - bookworm + + galaxy_tags: + - database + - postgresql + - sql + +dependencies: [] diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/client.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/client.yml new file mode 100644 index 0000000..f1dee81 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/client.yml @@ -0,0 +1,9 @@ +--- +- name: install client packages + become: true + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + force_apt_get: true + pkg: + - postgresql-client diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/main.yml new file mode 100644 index 0000000..afa9ff1 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/main.yml @@ -0,0 +1,17 @@ +--- +- name: setup server + include_tasks: server.yml + when: postgresql_install_server is truthy + +- name: setup client + include_tasks: client.yml + when: postgresql_install_client is truthy + +- name: install firewall rules + become: true + template: + src: ../templates/nftables.d/postgresql.nft.j2 + dest: /etc/nftables.d/postgresql.nft + mode: '0600' + notify: + - 'postgresql : load firewall rules' diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/server.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/server.yml new file mode 100644 index 0000000..c55a7ab --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tasks/server.yml @@ -0,0 +1,132 @@ +--- +- name: install server packages + become: true + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + force_apt_get: true + pkg: + - postgresql + - python3-pexpect + +- name: gather information + become: true + block: + - name: pg_hba.conf path + ansible.builtin.shell: > + su {{ postgresql_default_user }} -c 'psql -t --csv -c "SHOW hba_file"' + register: hba_file_query + changed_when: false + failed_when: hba_file_query.rc != 0 or hba_file_query.stdout is falsy + - name: postgresql.conf path + ansible.builtin.shell: > + su {{ postgresql_default_user }} -c 'psql -t --csv -c "SHOW config_file"' + register: psql_conf_query + changed_when: false + failed_when: psql_conf_query.rc != 0 or psql_conf_query.stdout is falsy + - name: register facts + ansible.builtin.set_fact: + postgresql_hba_file: '{{ hba_file_query.stdout }}' + postgresql_conf_file: '{{ psql_conf_query.stdout }}' + +- name: update postgresql.conf values + become: true + block: + - name: update listen addresses + ansible.builtin.lineinfile: + path: '{{ postgresql_conf_file }}' + regexp: '^#?listen_addresses\b.+' + line: "listen_addresses = '{{ postgresql_server_bind_addresses|join(',') }}'" + state: present + when: postgresql_server_bind_addresses is truthy + notify: + - 'postgresql : restart postgresql service' + - name: update listen port + ansible.builtin.lineinfile: + path: '{{ postgresql_conf_file }}' + regexp: '^#?port\b.+' + line: 'port = {{ postgresql_server_port }}' + state: present + notify: + - 'postgresql : restart postgresql service' + - name: update default encryption + ansible.builtin.lineinfile: + path: '{{ postgresql_conf_file }}' + regexp: '^#?password_encryption\b.+' + line: "password_encryption = '{{ postgresql_server_encryption_scheme }}'" + state: present + notify: + - 'postgresql : restart postgresql service' + +- name: flush handlers + ansible.builtin.meta: flush_handlers + +- name: create databases + become: true + ansible.builtin.command: > + su {{ postgresql_default_user }} -c 'createdb{% if 'tablespace' in item %} -D "{{ item.tablespace }}"{% endif %}{% if 'encoding' in item %} -E "{{ item.encoding }}"{% endif %}{% if 'locale' in item %} -l "{{ item.locale }}"{% endif %}{% if 'owner' in item %} -O "{{ item.owner }}"{% endif %} "{{ item.name }}"' + loop: '{{ postgresql_server_databases_list }}' + loop_control: + label: '{{ item.name }}' + register: create_db_exec + failed_when: create_db_exec.rc != 0 and not " already exists" in create_db_exec.stderr + changed_when: not " already exists" in create_db_exec.stderr + +- name: create accesses + become: true + block: + - name: create roles + ansible.builtin.expect: + command: > + su {{ postgresql_default_user }} -c 'createuser --{{ 'no-' if item.get('nologin', False) is truthy }}login "{{ item.name }}" --pwprompt' + responses: + 'Enter password for new role: ': + - '{{ item.password }}' + 'Enter it again: ': + - '{{ item.password }}' + loop: '{{ postgresql_server_accounts_list }}' + loop_control: + label: '{{ item.name }}' + register: create_user_exec + failed_when: create_user_exec.rc != 0 and not " already exists" in create_user_exec.stdout + changed_when: not " already exists" in create_user_exec.stdout + no_log: true + - name: add HBA accesses + ansible.builtin.lineinfile: + path: '{{ postgresql_hba_file }}' + regexp: '^#?(?P{{ item.contype }}+)\s+(?P{{ item.databases }})\s+(?P{{ item.users }})\s+(?P{{ item.address }})\s+(?P{{ item.method }})$' + line: "{{ item.contype }}\t{{ item.databases | join(',') }}\t{{ item.users | join(',') }}\t{{ item.address }}\t{{ item.method }}" + group: '{{ postgresql_default_user }}' + owner: '{{ postgresql_default_user }}' + mode: '0600' + state: present + loop: '{{ postgresql_server_hba_conf_list }}' + loop_control: + label: '{{ item.contype }}:{{ item.method }}:: {{ item.users }}-{{ item.address }} @ {{ item.databases }}' + notify: + - 'postgresql : reload postgresql service' + +- name: run custom initialization queries + become: true + block: + - name: create temporary file + ansible.builtin.tempfile: + state: file + register: tmp_file + changed_when: false + - name: export initialization SQL file + ansible.builtin.template: + src: ../templates/postgresql_init.sql.j2 + dest: '{{ tmp_file.path }}' + mode: '0600' + force: true + owner: '{{ postgresql_default_user }}' + group: '{{ postgresql_default_user }}' + changed_when: false + - name: run initialization file + ansible.builtin.shell: "su {{ postgresql_default_user }} -c 'psql < {{ tmp_file.path }}'" + - name: cleanup + ansible.builtin.file: + path: '{{ tmp_file.path }}' + state: absent + when: postgresql_server_run_init_sql is truthy or postgresql_server_run_custom_sql is truthy diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/nftables.d/postgresql.nft.j2 b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/nftables.d/postgresql.nft.j2 new file mode 100644 index 0000000..b343938 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/nftables.d/postgresql.nft.j2 @@ -0,0 +1,26 @@ +{%- set allowed_ingress_list4 = postgresql_nft_allowed_ingress_list | ansible.utils.ipv4 -%} +{%- set allowed_ingress_list6 = postgresql_nft_allowed_ingress_list | ansible.utils.ipv6 -%} +{%- set allowed_egress_list4 = postgresql_nft_allowed_egress_list | ansible.utils.ipv4 | default([], true) -%} +{%- set allowed_egress_list6 = postgresql_nft_allowed_egress_list | ansible.utils.ipv6 | default([], true) -%} +table inet filter { +{% if postgresql_install_server %} + chain input { +{% if postgresql_nft_filter_input %} + {%+ if allowed_ingress_list4 %}ip saddr { {{ allowed_ingress_list4 | join(', ') }} } tcp dport {{ postgresql_server_port }} accept{% endif +%} + {%+ if allowed_ingress_list6 %}ip6 saddr { {{ allowed_ingress_list6 | join(', ') }} } tcp dport {{ postgresql_server_port }} accept{% endif +%} +{% else %} + tcp dport {{ postgresql_server_port }} accept +{% endif %} + } +{% endif %} +{% if postgresql_install_client %} + chain output { +{% if postgresql_nft_filter_output %} + {%+ if allowed_egress_list4 %}ip daddr { {{ allowed_egress_list4 | join(', ') }} } tcp sport {{ postgresql_server_port }} accept{% endif +%} + {%+ if allowed_egress_list6 %}ip daddr { {{ allowed_egress_list6 | join(', ') }} } tcp sport {{ postgresql_server_port }} accept{% endif +%} +{% else %} + tcp sport {{ postgresql_server_port }} accept +{% endif %} + } +{% endif %} +} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/postgresql_init.sql.j2 b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/postgresql_init.sql.j2 new file mode 100644 index 0000000..42782d4 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/templates/postgresql_init.sql.j2 @@ -0,0 +1,5 @@ +{% if postgresql_server_run_init_sql %} +{% endif %} +{% if postgresql_server_run_custom_sql %} +{{ postgresql_server_custom_sql }} +{% endif %} diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/inventory b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/inventory new file mode 100644 index 0000000..878877b --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/inventory @@ -0,0 +1,2 @@ +localhost + diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/test.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/test.yml new file mode 100644 index 0000000..6ed8d7e --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: localhost + remote_user: root + roles: + - postgresql diff --git a/collections/ansible_collections/nullified/infrastructure/roles/postgresql/vars/main.yml b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/vars/main.yml new file mode 100644 index 0000000..86c31b1 --- /dev/null +++ b/collections/ansible_collections/nullified/infrastructure/roles/postgresql/vars/main.yml @@ -0,0 +1,2 @@ +--- +postgresql_default_user: postgres diff --git a/inventory/host_vars/actinium/vars.yml b/inventory/host_vars/actinium/vars.yml index 9112aa1..d1e954b 100644 --- a/inventory/host_vars/actinium/vars.yml +++ b/inventory/host_vars/actinium/vars.yml @@ -11,9 +11,29 @@ k3s_cluster_role: server k3s_cluster_ip: "{{ vault_cluster_ip }}" mariadb_server_root_password: "{{ vault_mariadb_server_root_password }}" +mariadb_server_run_custom_sql: true mariadb_server_custom_sql: "{{ vault_mariadb_server_custom_sql }}" mariadb_server_bind_addresses: "{{ vault_mariadb_server_bind_addresses }}" +postgresql_server_run_custom_sql: false +postgresql_nft_allowed_ingress_list: ['127.0.0.1/32', '10.42.0.0/16'] +postgresql_server_custom_sql: "{{ vault_postgresql_server_custom_sql }}" +postgresql_server_bind_addresses: "{{ vault_postgresql_server_bind_addresses }}" +postgresql_server_databases_list: + - name: '{{ vault_invidious_pg_dbname }}' +postgresql_server_accounts_list: + - name: '{{ vault_invidious_pg_user }}' + db: '{{ vault_invidious_pg_dbname }}' + password: '{{ vault_invidious_pg_password }}' +postgresql_server_hba_conf_list: + - address: '10.42.0.0/16' + databases: + - invidious + contype: hostssl + method: scram-sha-256 + users: + - invidious + k3s_cluster_additional_helm_charts: - release_name: redis release_namespace: default @@ -22,6 +42,7 @@ k3s_cluster_additional_helm_charts: values: replica: replicaCount: 1 + k3s_cluster_additional_tf_resources: - name: Invoice Ninja git_repository: 'https://gitlab.0x2a.ninja/flowtech/oss/invoice-ninja.git' diff --git a/playbooks/internal.yml b/playbooks/internal.yml index cd8246f..3e0e324 100644 --- a/playbooks/internal.yml +++ b/playbooks/internal.yml @@ -69,6 +69,16 @@ tags: [mariadb] tags: [mariadb] +- name: setup postgresql servers + hosts: internal:&postgresql + tasks: + - name: include postgresql role + ansible.builtin.include_role: + name: nullified.infrastructure.postgresql + apply: + tags: [postgresql] + tags: [postgresql] + - name: setup workstations hosts: internal:&workstation tasks: