From 73bbab9a03eda600b2cb6f63ca879ad279e979cd Mon Sep 17 00:00:00 2001 From: klention Date: Sat, 21 Dec 2024 15:31:45 +0100 Subject: [PATCH] Enabled TLS communication encryption --- automation/add_pgnode.yml | 2 + automation/deploy_pgcluster.yml | 17 ++- .../roles/confd/templates/confd.toml.j2 | 4 +- automation/roles/etcd/tasks/main.yml | 15 ++- automation/roles/etcd/templates/etcd.conf.j2 | 21 +++- .../roles/patroni/templates/patroni.yml.j2 | 18 ++- .../roles/patroni/templates/pg_hba.conf.j2 | 8 +- automation/roles/pgbouncer/tasks/main.yml | 8 ++ .../roles/tls_certificate/copy/tasks/main.yml | 73 +++++++----- .../tls_certificate/generate/tasks/main.yml | 109 ++++++++++++++++++ .../roles/tls_certificate/tasks/main.yml | 30 ----- .../vip-manager/templates/vip-manager.yml.j2 | 16 +-- automation/vars/Debian.yml | 1 - automation/vars/RedHat.yml | 1 - automation/vars/main.yml | 24 ++-- 15 files changed, 249 insertions(+), 98 deletions(-) create mode 100644 automation/roles/tls_certificate/generate/tasks/main.yml delete mode 100644 automation/roles/tls_certificate/tasks/main.yml diff --git a/automation/add_pgnode.yml b/automation/add_pgnode.yml index 9dd7c15f1..a1ceae9f8 100644 --- a/automation/add_pgnode.yml +++ b/automation/add_pgnode.yml @@ -236,6 +236,8 @@ when: pg_probackup_install|bool - role: tls_certificate/copy + vars: + copy_for: "pg" when: tls_cert_generate|bool - role: pgbouncer diff --git a/automation/deploy_pgcluster.yml b/automation/deploy_pgcluster.yml index cf2aed8b8..f4a592db8 100644 --- a/automation/deploy_pgcluster.yml +++ b/automation/deploy_pgcluster.yml @@ -83,6 +83,15 @@ - pgbackrest_auto_conf | default(true) | bool # to be able to disable auto backup settings tags: always + - name: Make sure that the python3-cryptography package is present + ansible.builtin.package: + name: python3-cryptography + state: present + register: pack_status + until: pack_status is success + delay: 5 + retries: 3 + roles: # (optional) if 'ssh_public_keys' is defined - role: authorized-keys @@ -94,6 +103,11 @@ timescale_minimal_pg_version: 12 # if enable_timescale is defined tags: always + - role: hostname + + - role: tls_certificate/generate + when: tls_cert_generate|bool + tasks: - name: Clean dnf cache ansible.builtin.command: dnf clean all @@ -356,9 +370,6 @@ - role: cron - - role: tls_certificate - when: tls_cert_generate|bool - - role: pgbouncer when: pgbouncer_install|bool diff --git a/automation/roles/confd/templates/confd.toml.j2 b/automation/roles/confd/templates/confd.toml.j2 index e27d66f9b..d9ee0f0da 100644 --- a/automation/roles/confd/templates/confd.toml.j2 +++ b/automation/roles/confd/templates/confd.toml.j2 @@ -4,12 +4,12 @@ watch = true nodes = [ {% if not dcs_exists|bool and dcs_type == 'etcd' %} {% for host in groups['etcd_cluster'] %} - "http://{{ hostvars[host]['inventory_hostname'] }}:2379", + "{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ hostvars[host]['inventory_hostname'] }}:2379", {% endfor %} {% endif %} {% if dcs_exists|bool and dcs_type == 'etcd' %} {% for etcd_hosts in patroni_etcd_hosts %} - "{{ patroni_etcd_protocol | default('http', true) }}://{{etcd_hosts.host}}:{{etcd_hosts.port}}", + "{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{etcd_hosts.host}}:{{etcd_hosts.port}}", {% endfor %} {% endif %} ] diff --git a/automation/roles/etcd/tasks/main.yml b/automation/roles/etcd/tasks/main.yml index c18b35941..c20e58a09 100644 --- a/automation/roles/etcd/tasks/main.yml +++ b/automation/roles/etcd/tasks/main.yml @@ -86,6 +86,14 @@ state: directory tags: etcd, etcd_conf +- name: Fetch etcd TLS certificate, key and CA from the master node + ansible.builtin.include_role: + name: ../roles/tls_certificate/copy + vars: + copy_for: "etcd" + when: tls_cert_generate|bool + tags: etcd, etcd_conf + - name: Create etcd data directory ansible.builtin.file: path: "{{ etcd_data_dir }}" @@ -128,7 +136,12 @@ - name: Wait until the etcd cluster is healthy ansible.builtin.command: > /usr/local/bin/etcdctl endpoint health - --endpoints=http://{{ inventory_hostname }}:2379 + --endpoints={% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ inventory_hostname }}:2379 + {% if tls_cert_generate | bool %} + --cacert=/etc/etcd/ca.crt + --cert=/etc/etcd/server.crt + --key=/etc/etcd/server.key + {% endif %} environment: ETCDCTL_API: "3" register: etcd_health_result diff --git a/automation/roles/etcd/templates/etcd.conf.j2 b/automation/roles/etcd/templates/etcd.conf.j2 index 0aa568c4a..de1aea647 100644 --- a/automation/roles/etcd/templates/etcd.conf.j2 +++ b/automation/roles/etcd/templates/etcd.conf.j2 @@ -1,13 +1,24 @@ ETCD_NAME="{{ ansible_hostname }}" -ETCD_LISTEN_CLIENT_URLS="http://{{ inventory_hostname }}:2379,http://127.0.0.1:2379" -ETCD_ADVERTISE_CLIENT_URLS="http://{{ inventory_hostname }}:2379" -ETCD_LISTEN_PEER_URLS="http://{{ inventory_hostname }}:2380" -ETCD_INITIAL_ADVERTISE_PEER_URLS="http://{{ inventory_hostname }}:2380" +ETCD_LISTEN_CLIENT_URLS="{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ inventory_hostname }}:2379,{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://127.0.0.1:2379" +ETCD_ADVERTISE_CLIENT_URLS="{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ inventory_hostname }}:2379" +ETCD_LISTEN_PEER_URLS="{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ inventory_hostname }}:2380" +ETCD_INITIAL_ADVERTISE_PEER_URLS="{% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ inventory_hostname }}:2380" ETCD_INITIAL_CLUSTER_TOKEN="{{ etcd_cluster_name }}" -ETCD_INITIAL_CLUSTER="{% for host in groups['etcd_cluster'] %}{{ hostvars[host]['ansible_hostname'] }}=http://{{ hostvars[host]['inventory_hostname'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}" +ETCD_INITIAL_CLUSTER="{% for host in groups['etcd_cluster'] %}{{ hostvars[host]['ansible_hostname'] }}={% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ hostvars[host]['inventory_hostname'] }}:2380{% if not loop.last %},{% endif %}{% endfor %}" ETCD_INITIAL_CLUSTER_STATE="new" ETCD_DATA_DIR="{{ etcd_data_dir }}" ETCD_ELECTION_TIMEOUT="5000" ETCD_HEARTBEAT_INTERVAL="1000" ETCD_INITIAL_ELECTION_TICK_ADVANCE="false" ETCD_AUTO_COMPACTION_RETENTION="1" +{% if tls_cert_generate | bool %} +ETCD_CERT_FILE="{{ tls_etcd_cert_path }}" +ETCD_KEY_FILE="{{ tls_etcd_privatekey_path }}" +ETCD_TRUSTED_CA_FILE="{{ tls_etcd_ca_cert_path }}" +ETCD_PEER_CERT_FILE="{{ tls_etcd_cert_path }}" +ETCD_PEER_KEY_FILE="{{ tls_etcd_privatekey_path }}" +ETCD_PEER_TRUSTED_CA_FILE="{{ tls_etcd_ca_cert_path }}" +ETCD_PEER_CLIENT_CERT_AUTH="true" +ETCD_CLIENT_CERT_AUTH="true" +ETCD_TLS_MIN_VERSION="TLS1.2" +{% endif %} diff --git a/automation/roles/patroni/templates/patroni.yml.j2 b/automation/roles/patroni/templates/patroni.yml.j2 index 011165ac3..241c5ed5a 100644 --- a/automation/roles/patroni/templates/patroni.yml.j2 +++ b/automation/roles/patroni/templates/patroni.yml.j2 @@ -38,20 +38,32 @@ restapi: {% if not dcs_exists|bool and dcs_type == 'etcd' %} etcd3: hosts: {% for host in groups['etcd_cluster'] %}{{ hostvars[host]['inventory_hostname'] }}:2379{% if not loop.last %},{% endif %}{% endfor %} + + {% if tls_cert_generate | bool %} + protocol: https + cacert: {{ tls_ca_cert_path | default('/etc/tls/ca.crt') }} + cert: {{ tls_cert_path | default('/etc/tls/server.crt') }} + key: {{ tls_privatekey_path | default('/etc/tls/server.key') }} + {% endif %} {% endif %} + {% if dcs_exists|bool and dcs_type == 'etcd' %} etcd3: hosts: {% for etcd_hosts in patroni_etcd_hosts %}{{etcd_hosts.host}}:{{etcd_hosts.port}}{% if not loop.last %},{% endif %}{% endfor %} + {% if tls_cert_generate | bool %} + protocol: https + cacert: {{ tls_ca_cert_path | default('/etc/tls/ca.crt') }} + cert: {{ tls_cert_path | default('/etc/tls/server.crt') }} + key: {{ tls_privatekey_path | default('/etc/tls/server.key') }} + {% endif %} + {% if patroni_etcd_username | default('') | length > 0 %} username: {{ patroni_etcd_username | default('') }} {% endif %} {% if patroni_etcd_password | default('') | length > 0 %} password: {{ patroni_etcd_password }} {% endif %} - {% if patroni_etcd_protocol | default('') | length > 0 %} - protocol: {{ patroni_etcd_protocol }} - {% endif %} {% endif %} {% if dcs_type == 'consul' %} diff --git a/automation/roles/patroni/templates/pg_hba.conf.j2 b/automation/roles/patroni/templates/pg_hba.conf.j2 index f3be1883a..8c1adaa03 100644 --- a/automation/roles/patroni/templates/pg_hba.conf.j2 +++ b/automation/roles/patroni/templates/pg_hba.conf.j2 @@ -85,14 +85,14 @@ # TYPE DATABASE USER ADDRESS METHOD {% for client in postgresql_pg_hba %} - {{ client.type.ljust(10) |default('host') }}{{ client.database.ljust(25) |default('all') }}{{ client.user.ljust(25) |default('all') }}{{ client.address.ljust(25) |default('') }}{{ client.method |default('md5') }} {{ client.options |default(None) }} + {{ client.type.ljust(10) |default('{% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %}') }}{{ client.database.ljust(25) |default('all') }}{{ client.user.ljust(25) |default('all') }}{{ client.address.ljust(25) |default('') }}{{ client.method |default('md5') }} {{ client.options |default(None) }} {% endfor %} {% for patroni in groups['postgres_cluster'] %} - host all all {{ hostvars[patroni]['inventory_hostname'] }}/32 {{ postgresql_password_encryption_algorithm }} + {% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %} all all {{ hostvars[patroni]['inventory_hostname'] }}/32 {{ postgresql_password_encryption_algorithm }} {% endfor %} # Allow replication connections from localhost, by a user with the # replication privilege. - host replication {{ patroni_replication_username }} localhost trust + {% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %} replication {{ patroni_replication_username }} localhost trust {% for host in groups['postgres_cluster'] %} - host replication {{ patroni_replication_username }} {{ hostvars[host]['inventory_hostname'] }}/32 {{ postgresql_password_encryption_algorithm }} + {% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %} replication {{ patroni_replication_username }} {{ hostvars[host]['inventory_hostname'] }}/32 {{ postgresql_password_encryption_algorithm }} {% endfor %} diff --git a/automation/roles/pgbouncer/tasks/main.yml b/automation/roles/pgbouncer/tasks/main.yml index cd2fb08fe..ba4402e9f 100644 --- a/automation/roles/pgbouncer/tasks/main.yml +++ b/automation/roles/pgbouncer/tasks/main.yml @@ -36,6 +36,14 @@ mode: "0750" tags: pgbouncer_conf, pgbouncer +- name: Fetch PostgreSQL TLS certificate, key and CA from the master node + ansible.builtin.include_role: + name: ../roles/tls_certificate/copy + vars: + copy_for: "pg" + when: tls_cert_generate|bool + tags: pgbouncer_conf, pgbouncer + - name: Ensure log directory "{{ pgbouncer_log_dir }}" exist ansible.builtin.file: path: "{{ pgbouncer_log_dir }}" diff --git a/automation/roles/tls_certificate/copy/tasks/main.yml b/automation/roles/tls_certificate/copy/tasks/main.yml index a0038ecc4..fe8c05448 100644 --- a/automation/roles/tls_certificate/copy/tasks/main.yml +++ b/automation/roles/tls_certificate/copy/tasks/main.yml @@ -1,42 +1,53 @@ --- -# for add_pgnode.yml - -- name: Ensure TLS directories exist - ansible.builtin.file: - path: "{{ item | dirname }}" - state: directory - owner: "{{ tls_owner | default('postgres') }}" - group: "{{ tls_owner | default('postgres') }}" - mode: "0750" - loop: - - "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" - - "{{ tls_cert_path | default('/etc/tls/server.crt') }}" - -- name: Fetch TLS certificate and key from master +- name: Fetch TLS certificate, key and CA from the master node into memory run_once: true - ansible.builtin.fetch: + ansible.builtin.slurp: src: "{{ item }}" - dest: "files/tls/" - validate_checksum: true - flat: true delegate_to: "{{ groups.master[0] }}" + register: tls_files loop: - - "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" - - "{{ tls_cert_path | default('/etc/tls/server.crt') }}" + - "/etc/tls/server.key" + - "/etc/tls/server.crt" + - "/etc/tls/ca.crt" -- name: Copy TLS certificate and key to replica +- name: Copy etcd TLS certificate, key and CA to all nodes from memory ansible.builtin.copy: - src: "files/tls/{{ item.path | basename }}" + content: "{{ tls_files.results[item.index].content | b64decode }}" dest: "{{ item.path }}" - owner: "{{ tls_owner | default('postgres') }}" - group: "{{ tls_owner | default('postgres') }}" + owner: "etcd" + group: "etcd" mode: "{{ item.mode }}" loop: - - { path: "{{ tls_privatekey_path | default('/etc/tls/server.key') }}", mode: "{{ tls_privatekey_mode | default('0400') }}" } - - { path: "{{ tls_cert_path | default('/etc/tls/server.crt') }}", mode: "{{ tls_cert_mode | default('0644') }}" } + - { index: 0, path: "{{ tls_etcd_privatekey_path | default('/etc/etcd/server.key') }}", mode: "0400" } + - { index: 1, path: "{{ tls_etcd_cert_path | default('/etc/etcd/server.crt') }}", mode: "0644" } + - { index: 2, path: "{{ tls_etcd_ca_cert_path | default('/etc/etcd/ca.crt') }}", mode: "0644" } + when: copy_for == 'etcd' + +- block: + - name: Create directory {{ tls_privatekey_path | dirname }} + ansible.builtin.file: + dest: "{{ tls_privatekey_path | dirname }}" + state: directory + owner: "{{ tls_owner }}" + group: "{{ tls_owner }}" + mode: "0755" + + - name: Copy PostgreSQL TLS certificate, key and CA to all nodes + ansible.builtin.copy: + content: "{{ tls_files.results[item.index].content | b64decode }}" + dest: "{{ item.path }}" + owner: "{{ tls_owner }}" + group: "{{ tls_owner }}" + mode: "{{ item.mode }}" + loop: + - { index: 0, path: "{{ tls_privatekey_path | default('/etc/tls/server.key') }}", mode: "0400" } + - { index: 1, path: "{{ tls_cert_path | default('/etc/tls/server.crt') }}", mode: "0644" } + - { index: 2, path: "{{ tls_ca_cert_path | default('/etc/tls/ca.crt') }}", mode: "0644" } -- name: Delete TLS certificate and key from the ansible controller - ansible.builtin.file: - path: "files/tls/" - state: absent - delegate_to: localhost + - name: Delete TLS certificate and key from the ansible controller + ansible.builtin.file: + path: "files/tls/" + state: absent + delegate_to: localhost + run_once: true + when: copy_for == 'pg' diff --git a/automation/roles/tls_certificate/generate/tasks/main.yml b/automation/roles/tls_certificate/generate/tasks/main.yml new file mode 100644 index 000000000..4c99cb60d --- /dev/null +++ b/automation/roles/tls_certificate/generate/tasks/main.yml @@ -0,0 +1,109 @@ +--- +- name: "Clean up existing certificates" + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" + - "{{ tls_cert_path | default('/etc/tls/server.crt') }}" + - "{{ tls_ca_cert_path | default('/etc/tls/ca.crt') }}" + - "{{ tls_ca_privatekey_path | default('/etc/tls/ca.key') }}" + - "{{ tls_etcd_cert_path | default('/etc/etcd/server.crt') }}" + - "{{ tls_etcd_ca_cert_path | default('/etc/etcd/ca.crt') }}" + - "{{ tls_etcd_privatekey_path | default('/etc/etcd/server.key') }}" + - "/etc/tls" + +- ansible.builtin.set_fact: + all_san_entries: [] + +- name: "Gather host-specific network information" + ansible.builtin.set_fact: + san_entry: >- + DNS:{{ ansible_hostname }},DNS:{{ ansible_fqdn }},IP:{{ ansible_default_ipv4.address }} + +- block: + - name: "Aggregate all subjectAltName entries" + ansible.builtin.set_fact: + all_san_entries: "{{ all_san_entries + [hostvars[item].san_entry] }}" + with_items: "{{ ansible_play_hosts }}" + + - name: "Join subjectAltName entries into a single string" + ansible.builtin.set_fact: + subject_alt_name: "{{ all_san_entries | join(',') + ',DNS:localhost,IP:127.0.0.1' }}" + when: ansible_play_hosts | length > 1 + + - name: "Display Certificate subjectAltName future value" + ansible.builtin.debug: + var: subject_alt_name + +######## Generate CA ######## + - name: "Ensure TLS directory exist" + ansible.builtin.file: + path: "/etc/tls" + state: directory + mode: "0700" + + - name: "Generate CA private key" + community.crypto.openssl_privatekey: + path: "/etc/tls/ca.key" + size: "{{ tls_privatekey_size | default(4096) }}" + type: "{{ tls_privatekey_type | default('RSA') }}" + + - name: "Create CSR for CA certificate" + community.crypto.openssl_csr_pipe: + privatekey_path: "/etc/tls/ca.key" + common_name: PostgreSQL CA + use_common_name_for_san: false + basic_constraints: + - 'CA:TRUE' + basic_constraints_critical: true + key_usage: + - keyCertSign + key_usage_critical: true + register: ca_csr + + - name: "Create self-signed CA certificate from CSR" + community.crypto.x509_certificate: + path: "/etc/tls/ca.crt" + csr_content: "{{ ca_csr.csr }}" + privatekey_path: "/etc/tls/ca.key" + provider: "{{ tls_cert_provider | default('selfsigned') }}" + entrust_not_after: "+{{ tls_cert_valid_days | default(3650) }}d" + +######## Generate Server cert/key ######## + - name: "Create server private key" + community.crypto.openssl_privatekey: + path: "/etc/tls/server.key" + size: "{{ tls_privatekey_size | default(4096) }}" + type: "{{ tls_privatekey_type | default('RSA') }}" + + - name: "Create server CSR" + community.crypto.openssl_csr_pipe: + privatekey_path: "/etc/tls/server.key" + common_name: postgresql.cluster + key_usage: + - digitalSignature + - keyEncipherment + - dataEncipherment + extended_key_usage: + - clientAuth + - serverAuth + subject_alt_name: "{{ subject_alt_name }}" + register: csr + + - name: "Sign server certificate with the CA" + community.crypto.x509_certificate_pipe: + csr_content: "{{ csr.csr }}" + provider: ownca + ownca_path: "/etc/tls/ca.crt" + ownca_privatekey_path: "/etc/tls/ca.key" + ownca_not_after: +3650d + ownca_not_before: "-1d" + register: certificate + + - name: "Write server certificate" + ansible.builtin.copy: + dest: "/etc/tls/server.crt" + content: "{{ certificate.certificate }}" + delegate_to: "{{ groups.master[0] }}" + run_once: true diff --git a/automation/roles/tls_certificate/tasks/main.yml b/automation/roles/tls_certificate/tasks/main.yml deleted file mode 100644 index 07ee54dd1..000000000 --- a/automation/roles/tls_certificate/tasks/main.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -- name: Ensure TLS directories exist - ansible.builtin.file: - path: "{{ item | dirname }}" - state: directory - owner: "{{ tls_owner | default('postgres') }}" - group: "{{ tls_owner | default('postgres') }}" - mode: "0750" - loop: - - "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" - - "{{ tls_cert_path | default('/etc/tls/server.crt') }}" - -- name: "Generate private TLS key {{ tls_privatekey_path | default('/etc/tls/server.key') }}" - community.crypto.openssl_privatekey: - path: "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" - owner: "{{ tls_owner | default('postgres') }}" - group: "{{ tls_owner | default('postgres') }}" - mode: "{{ tls_privatekey_mode | default('0400') }}" - size: "{{ tls_privatekey_size | default(4096) }}" - type: "{{ tls_privatekey_type | default('RSA') }}" - -- name: "Generate self-signed TLS certificate {{ tls_cert_path | default('/etc/tls/server.crt') }}" - community.crypto.x509_certificate: - path: "{{ tls_cert_path | default('/etc/tls/server.crt') }}" - privatekey_path: "{{ tls_privatekey_path | default('/etc/tls/server.key') }}" - owner: "{{ tls_owner | default('postgres') }}" - group: "{{ tls_owner | default('postgres') }}" - mode: "{{ tls_cert_mode | default('0644') }}" - provider: "{{ tls_cert_provider | default('selfsigned') }}" - entrust_not_after: "+{{ tls_cert_valid_days | default(3650) }}d" diff --git a/automation/roles/vip-manager/templates/vip-manager.yml.j2 b/automation/roles/vip-manager/templates/vip-manager.yml.j2 index ded0bd395..4222d79e8 100644 --- a/automation/roles/vip-manager/templates/vip-manager.yml.j2 +++ b/automation/roles/vip-manager/templates/vip-manager.yml.j2 @@ -27,12 +27,12 @@ dcs-type: {{ vip_manager_dcs_type | default(dcs_type) }} # etcd, consul or patro {% if not dcs_exists | bool %} dcs-endpoints: {% for host in groups['etcd_cluster'] %} - - http://{{ hostvars[host]['inventory_hostname'] }}:2379 + - {% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ hostvars[host]['inventory_hostname'] }}:2379 {% endfor %} {% else %} dcs-endpoints: {% for etcd_hosts in patroni_etcd_hosts %} - - {{ patroni_etcd_protocol | default('http', true) }}://{{ etcd_hosts.host }}:{{ etcd_hosts.port }} + - {% if tls_cert_generate | bool %}https{% else %}http{% endif %}://{{ etcd_hosts.host }}:{{ etcd_hosts.port }} {% endfor %} {% endif %} {% endif %} @@ -64,16 +64,16 @@ etcd-user: {{ patroni_etcd_username | default("") }} {% if patroni_etcd_password | default("") | length > 0 %} etcd-password: {{ patroni_etcd_password | default("") }} {% endif %} -{% if patroni_etcd_ca_file | default("") | length > 0 %} +{% if tls_cert_generate | bool %} # when etcd-ca-file is specified, TLS connections to the etcd endpoints will be used. -etcd-ca-file: {{ patroni_etcd_ca_file | default("") }} +etcd-ca-file: {{ tls_ca_cert_path | default('/etc/tls/ca.crt') }} {% endif %} -{% if patroni_etcd_cert_file | default("") | length > 0 %} +{% if tls_cert_generate | bool %} # when etcd-cert-file and etcd-key-file are specified, we will authenticate at the etcd endpoints using this certificate and key. -etcd-cert-file: {{ patroni_etcd_cert_file | default("") }} +etcd-cert-file: {{ tls_cert_path | default('/etc/tls/server.crt') }} {% endif %} -{% if patroni_etcd_key_file | default("") | length > 0 %} -etcd-key-file: {{ patroni_etcd_key_file | default("") }} +{% if tls_cert_generate | bool %} +etcd-key-file: {{ tls_privatekey_path | default('/etc/tls/server.key') }} {% endif %} {% endif %} diff --git a/automation/vars/Debian.yml b/automation/vars/Debian.yml index 4f1ea629b..5e2d99900 100644 --- a/automation/vars/Debian.yml +++ b/automation/vars/Debian.yml @@ -40,7 +40,6 @@ system_packages: - python3-psycopg2 - python3-setuptools - python3-pip - - python3-cryptography - curl - less - sudo diff --git a/automation/vars/RedHat.yml b/automation/vars/RedHat.yml index 86fb6be98..46e158bcc 100644 --- a/automation/vars/RedHat.yml +++ b/automation/vars/RedHat.yml @@ -60,7 +60,6 @@ system_packages: - python{{ python_version }}-setuptools - python{{ python_version }}-pip - python{{ python_version }}-urllib3 - - python3-cryptography - less - sudo - vim diff --git a/automation/vars/main.yml b/automation/vars/main.yml index e8fa0c4fc..bb187268a 100644 --- a/automation/vars/main.yml +++ b/automation/vars/main.yml @@ -104,7 +104,7 @@ patroni_etcd_hosts: [] # list of servers of an existing etcd cluster patroni_etcd_namespace: "service" # (optional) etcd namespace (prefix) patroni_etcd_username: "" # (optional) username for etcd authentication patroni_etcd_password: "" # (optional) password for etcd authentication -patroni_etcd_protocol: "" # (optional) http or https, if not specified http is used +patroni_etcd_protocol: "https" # (optional) http or https, if not specified http is used # more options you can specify in the roles/patroni/templates/patroni.yml.j2 # https://patroni.readthedocs.io/en/latest/yaml_configuration.html#etcd @@ -174,12 +174,16 @@ consul_services: # - { http: "http://{{ inventory_hostname }}:{{ patroni_restapi_port }}/async?lag={{ patroni_maximum_lag_on_replica }}", interval: "2s" } # - { args: ["systemctl", "status", "pgbouncer"], interval: "5s" } -# TLS certificate (for PostgreSQL & PgBouncer) +# TLS certificate (for PostgreSQL, PgBouncer and etcd) tls_cert_generate: true tls_cert_valid_days: 3650 tls_cert_path: "{{ postgresql_home_dir }}/tls/server.crt" tls_privatekey_path: "{{ postgresql_home_dir }}/tls/server.key" +tls_ca_cert_path: "{{ postgresql_home_dir }}/tls/ca.crt" tls_owner: "postgres" +tls_etcd_cert_path: "/etc/etcd/server.crt" +tls_etcd_ca_cert_path: "/etc/etcd/ca.crt" +tls_etcd_privatekey_path: "/etc/etcd/server.key" # PostgreSQL variables postgresql_version: 17 @@ -241,9 +245,11 @@ postgresql_parameters: - { option: "max_connections", value: "1000" } - { option: "superuser_reserved_connections", value: "5" } - { option: "password_encryption", value: "{{ postgresql_password_encryption_algorithm }}" } - - { option: "ssl", value: "on"} - - { option: "ssl_cert_file", value: "{{ tls_cert_path }}"} - - { option: "ssl_key_file", value: "{{ tls_privatekey_path }}"} + - { option: "ssl", value: "{% if tls_cert_generate | bool %}on{% else %}off{% endif %}"} + - { option: "ssl_prefer_server_ciphers", value: "{% if tls_cert_generate | bool %}on{% else %}off{% endif %}"} + - { option: "ssl_cert_file", value: "{{ tls_cert_path | default('') }}"} + - { option: "ssl_key_file", value: "{{ tls_privatekey_path | default('') }}"} + - { option: "ssl_ca_file", value: "{{ tls_ca_cert_path | default('') }}"} - { option: "ssl_min_protocol_version", value: "TLSv1.2"} - { option: "max_locks_per_transaction", value: "512" } - { option: "max_prepared_transactions", value: "0" } @@ -338,9 +344,9 @@ postgresql_pg_hba: - { type: "local", database: "all", user: "all", address: "", method: "{{ postgresql_password_encryption_algorithm }}" } - { type: "host", database: "all", user: "all", address: "127.0.0.1/32", method: "{{ postgresql_password_encryption_algorithm }}" } - { type: "host", database: "all", user: "all", address: "::1/128", method: "{{ postgresql_password_encryption_algorithm }}" } - - { type: "host", database: "all", user: "all", address: "0.0.0.0/0", method: "{{ postgresql_password_encryption_algorithm }}" } -# - { type: "host", database: "mydatabase", user: "mydb-user", address: "192.168.0.0/24", method: "{{ postgresql_password_encryption_algorithm }}" } -# - { type: "host", database: "all", user: "all", address: "192.168.0.0/24", method: "ident", options: "map=main" } # use pg_ident + - { type: "{% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %}", database: "all", user: "all", address: "0.0.0.0/0", method: "{{ postgresql_password_encryption_algorithm }}" } +# - { type: "{% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %}", database: "mydatabase", user: "mydb-user", address: "192.168.0.0/24", method: "{{ postgresql_password_encryption_algorithm }}" } +# - { type: "{% if tls_cert_generate | bool %}hostssl{% else %}host{% endif %}", database: "all", user: "all", address: "192.168.0.0/24", method: "ident", options: "map=main" } # use pg_ident # list of lines that Patroni will use to generate pg_ident.conf postgresql_pg_ident: [] @@ -387,7 +393,7 @@ pgbouncer_server_tls_protocols: "secure" pgbouncer_server_tls_ciphers: "secure" pgbouncer_server_tls_cert_file: "{{ tls_cert_path }}" pgbouncer_server_tls_key_file: "{{ tls_privatekey_path }}" -pgbouncer_server_tls_ca_file: "" +pgbouncer_server_tls_ca_file: "{{ tls_ca_cert_path }}" pgbouncer_pools: - { name: "postgres", dbname: "postgres", pool_parameters: "" }