diff --git a/.ansible-lint b/.ansible-lint new file mode 100644 index 0000000..b31f3f6 --- /dev/null +++ b/.ansible-lint @@ -0,0 +1,4 @@ +--- +profile: production # min, basic, moderate, safety, shared, production +exclude_paths: + - .github/workflows/galaxy.yml diff --git a/.github/workflows/molecule.yml b/.github/workflows/molecule.yml index ef42adf..01e0c77 100644 --- a/.github/workflows/molecule.yml +++ b/.github/workflows/molecule.yml @@ -37,12 +37,14 @@ jobs: steps: - name: checkout uses: actions/checkout@v4 - with: - path: "${{ github.repository }}" - name: Run ansible lint - uses: ansible/ansible-lint-action@v6.17.0 + uses: ansible/ansible-lint@main with: - path: "." + args: "" + setup_python: "true" + working_directory: "" + requirements_file: "./molecule/shared/tools/requirements.yml" + test: name: Scenario "${{ matrix.scenario }}", pg-${{ matrix.postgresql_version }} on ${{ matrix.config.image }}:${{ matrix.config.tag }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 80e2fc9..65bc816 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,7 +46,7 @@ git clone ansible-role-postgresql cd ansible-role-postgresql python3 -m venv env # Create a virtual environnement source env/bin/activate # Activate the environnement -pip3 install ansible ansible-lint molecule[docker] # Install the python packages in the virutal environnement +pip3 install ansible ansible-lint molecule-plugins[docker] # Install the python packages in the virutal environnement ``` After setting up the environnement you can use molecule to test the role as you wish. If you are not very familiar with molecule, checkout the following table for some basic commands you can run against this role. diff --git a/README.md b/README.md index 8efd5b9..ae301f8 100644 --- a/README.md +++ b/README.md @@ -181,10 +181,16 @@ postgresql_hba_raw: | postgresql_config_change_allow_restart: true # Controls running tasks handling: configuration postgresql_configure: true +# Enable SSL +postgresql_ca_enabled: true +# Certificate file subject used during generation +postgresql_ssl_cert_subj: /C=FR/ST=FR ``` _Notes:_ +SSL configuration (introduced in `v3.0.0`) is enabled by default. The associated key and cert files are only regenerated if they are missing on the remote host. + By default, this role restarts the PostgreSQL service during subsequent configuration changes after the initial engine installation, ensuring all changes are applied immediately. However, this behavior can cause potential service outages. To prevent automatic restarts, you can set the variable `postgresql_config_change_allow_restart` (introduced in `v2.1.0`) to `false`. Starting with (`v3.0.0`), the default value of this variable will change to `false`, meaning the role will avoid restarting PostgreSQL by default. If you rely on the current behavior, you will need to explicitly set this variable to true in your configuration. @@ -603,6 +609,10 @@ postgresql_tempfile_dest_path: /etc/tmpfiles.d/postgresql-common.conf postgresql_tempfile_mode: '0644' postgresql_tempfile_owner: root postgresql_tempfile_group: root +# SSL cert file path, can be absolute or relative (to data dir) +# postgresql_ssl_cert_file: (default is os specific. vars/.yml) +# SSL key file path, can be absolute or relative (to data dir) +# postgresql_ssl_key_file: (default is os specific. vars/.yml) # Controls running tasks handling: cluster initialization postgresql_initialize: true diff --git a/defaults/main.yml b/defaults/main.yml index 55e8fbc..e559d22 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -39,8 +39,17 @@ postgresql_unix_socket_directories: - /run/postgresql # Permissions for the PostgreSQL unix sockets (default is distro dependant) postgresql_unix_socket_directories_mode: '' -# Allow service restart for configuration changes that require it -postgresql_config_change_allow_restart: "{{ (postgresql_restarted_state | d('restarted')) == 'restarted' }}" +# Allow service restart for conf changes that require it (default changed to false in v3.0.0+) +postgresql_config_change_allow_restart: false +# SSL related variables +# Enable SSL +postgresql_ca_enabled: true +# SSL cert file path, can be absolute or relative +# postgresql_ssl_cert_file: (default is os specific. vars/.yml) +# SSL key file path, can be absolute or relative +# postgresql_ssl_key_file: (default is os specific. vars/.yml) +# Certificate file subject used during generation +postgresql_ssl_cert_subj: /C=FR/ST=FR # Global configuration options that will be set in postgresql.conf. @@ -61,6 +70,13 @@ postgresql_global_config_options: value: "{{ _postgresql_config_path }}/pg_hba.conf" - option: max_connections value: "{{ postgresql_max_connections }}" + - option: ssl + value: "{{ 'on' if postgresql_ca_enabled else 'off' }}" + - option: ssl_cert_file + value: '{{ postgresql_ssl_cert_file }}' + - option: ssl_key_file + value: '{{ postgresql_ssl_key_file }}' + # Extra configuration options that are always inserted inside postgresql.conf postgresql_global_config_options_extra: [] # Actual postgresql log directory @@ -370,7 +386,8 @@ postgresql_uninstall_1: false postgresql_uninstall_2: false -# Tells the role that the PostgreSQL instance is managed by Patroni therefore automatically disabling some features initialization,auto tuning,regular configuration, actual replication configuration +# Tells the role that the PostgreSQL instance is managed by Patroni therefore automatically +# disabling some features initialization,auto tuning,regular configuration, actual replication configuration postgresql_is_patroni: false # When combined with postgresql_install:true, this essentially skips all remaining tasks after packages installation postgresql_only_install: false diff --git a/molecule/all_features/verify.yml b/molecule/all_features/verify.yml index 58a17a2..3a4e791 100644 --- a/molecule/all_features/verify.yml +++ b/molecule/all_features/verify.yml @@ -6,5 +6,5 @@ - name: Include default verification ansible.builtin.include_tasks: tasks/verify_default.yml - - name: Include default verification + - name: Include vacuum verification ansible.builtin.include_tasks: tasks/verify_vacuum.yml diff --git a/molecule/cluster/verify.yml b/molecule/cluster/verify.yml index b1be088..06d001f 100644 --- a/molecule/cluster/verify.yml +++ b/molecule/cluster/verify.yml @@ -17,6 +17,7 @@ ansible.builtin.command: psql -q -A -t -c "SELECT datname FROM pg_database" register: databases changed_when: false + become: true become_user: postgres vars: ansible_ssh_pipelining: true @@ -37,15 +38,16 @@ vars: ansible_ssh_pipelining: true - - name: Retrieve databases on replica nodes + - name: Retrieve databases on replica nodes to check deletion ansible.builtin.command: psql -q -A -t -c "SELECT datname FROM pg_database" register: databases changed_when: false + become: true become_user: postgres vars: ansible_ssh_pipelining: true - - name: Assert db created on primary is also present on the replicas + - name: Assert db not created on primary is also present on the replicas ansible.builtin.assert: that: '"db2" not in databases.stdout_lines' diff --git a/molecule/shared/tasks/verify_default.yml b/molecule/shared/tasks/verify_default.yml index 19de726..1b65862 100644 --- a/molecule/shared/tasks/verify_default.yml +++ b/molecule/shared/tasks/verify_default.yml @@ -56,3 +56,16 @@ msg: "Timeout waiting for 5432 to respond" register: port_check ignore_errors: true + +- name: Retrieve ssl option value + ansible.builtin.command: psql -q -A -t -c "show ssl" + register: ssl_res + changed_when: false + become: true + become_user: postgres + vars: + ansible_ssh_pipelining: true + +- name: Assert ssl is on by default + ansible.builtin.assert: + that: '"on" == (ssl_res.stdout | trim)' diff --git a/molecule/shared/tasks/verify_vacuum.yml b/molecule/shared/tasks/verify_vacuum.yml index c27f193..8321a22 100644 --- a/molecule/shared/tasks/verify_vacuum.yml +++ b/molecule/shared/tasks/verify_vacuum.yml @@ -1,10 +1,12 @@ --- - name: Check if vacuum script works ansible.builtin.command: /var/scripts/pgsql_vacuumDB.sh vacuumanalyze + changed_when: true become: true become_user: postgres - name: Check min last vacuum date ansible.builtin.command: psql db1 -c "select min(last_vacuum) from pg_stat_user_tables;" + changed_when: false become: true become_user: postgres diff --git a/molecule/shared/vars/main_all_features.yml b/molecule/shared/vars/main_all_features.yml index fcac9e6..dbbbf19 100644 --- a/molecule/shared/vars/main_all_features.yml +++ b/molecule/shared/vars/main_all_features.yml @@ -10,7 +10,7 @@ postgresql_global_config_options_extra: - option: log_statement value: all - option: logging_collector - value: on + value: 'on' postgresql_hba_entries_extra: [] # - {contype: local, databases: all, users: postgres, method: peer} @@ -36,20 +36,20 @@ postgresql_users: postgresql_memberships: # Ensure the role 'user1' belongs to group 'group1' - groups: - - group1 + - group1 target_roles: - - user1 + - user1 state: present # Ensure the role 'user2' does not belong to the group 'group2' - groups: - - group2 + - group2 target_roles: - - user2 + - user2 state: absent # Ensure the role 'jdoe' does not belong to any group - groups: [] target_roles: - - jdoe + - jdoe state: exact postgresql_tablespaces: diff --git a/molecule/vacuum/verify.yml b/molecule/vacuum/verify.yml index 58a17a2..3a4e791 100644 --- a/molecule/vacuum/verify.yml +++ b/molecule/vacuum/verify.yml @@ -6,5 +6,5 @@ - name: Include default verification ansible.builtin.include_tasks: tasks/verify_default.yml - - name: Include default verification + - name: Include vacuum verification ansible.builtin.include_tasks: tasks/verify_vacuum.yml diff --git a/tasks/configure.yml b/tasks/configure.yml index c833c98..d9cecc1 100644 --- a/tasks/configure.yml +++ b/tasks/configure.yml @@ -47,6 +47,47 @@ mode: "{{ postgresql_tempfile_mode }}" when: postgresql_persist_permissions +- name: Configure SSL + when: postgresql_ca_enabled + become: true + block: + - name: Stat CA private key + ansible.builtin.stat: + path: "{{ _postgresql_ssl_key_file_path }}" + register: _postgresql_ssl_key_file_res + + - name: Create CA private key + changed_when: true + when: not _postgresql_ssl_key_file_res.stat.exists + ansible.builtin.command: "{{ _postgresql_ssl_key_gen_cmd }}" + args: + creates: "{{ _postgresql_ssl_key_file_path }}" + + - name: Stat CA cert + ansible.builtin.stat: + path: "{{ _postgresql_ssl_cert_file_path }}" + register: _postgresql_ssl_cert_file_res + + - name: Create CA cert + changed_when: true + when: not _postgresql_ssl_key_file_res.stat.exists or not _postgresql_ssl_cert_file_res.stat.exists + ansible.builtin.command: "{{ _postgresql_ssl_cert_gen_cmd }}" + + - name: Set owner on key and cert files + when: item.when + ansible.builtin.file: + path: "{{ item.path }}" + owner: "{{ postgresql_user }}" + group: "{{ postgresql_group }}" + mode: "{{ item.mode }}" + loop: + - path: "{{ _postgresql_ssl_key_file_path }}" + mode: '0400' + when: _postgresql_ssl_key_file | dirname == '' + - path: "{{ _postgresql_ssl_cert_file_path }}" + mode: '0666' + when: _postgresql_ssl_cert_file | dirname == '' + - name: Set postgresql service enabled and started state ansible.builtin.service: name: "{{ _postgresql_daemon }}" diff --git a/tasks/redhat/install.yml b/tasks/redhat/install.yml index 1aa4a71..0d2b533 100644 --- a/tasks/redhat/install.yml +++ b/tasks/redhat/install.yml @@ -1,5 +1,5 @@ --- -- name: Install postgresql rpm repostiory +- name: Install postgresql rpm repository ansible.builtin.dnf: name: "{{ _postgresql_repo_rpm_url }}" update_cache: true diff --git a/tasks/redhat/uninstall.yml b/tasks/redhat/uninstall.yml index ce8cfd0..f6e5302 100644 --- a/tasks/redhat/uninstall.yml +++ b/tasks/redhat/uninstall.yml @@ -4,7 +4,7 @@ name: "{{ _postgresql_packages | select('match', 'postgresql') }}" state: absent -- name: Uninstall postgresql rpm repostiory +- name: Uninstall postgresql rpm repository ansible.builtin.dnf: name: "{{ _postgresql_repo_rpm_url }}" update_cache: true diff --git a/vars/amazon-2.yml b/vars/amazon-2.yml index 2bad836..70a8382 100644 --- a/vars/amazon-2.yml +++ b/vars/amazon-2.yml @@ -9,3 +9,6 @@ _postgresql_packages: - postgresql-server - postgresql-contrib - postgresql-libs +# SSL files default path +postgresql_ssl_cert_file: server.crt +postgresql_ssl_key_file: server.key diff --git a/vars/debian-family.yml b/vars/debian-family.yml index 5fa145b..23b117d 100644 --- a/vars/debian-family.yml +++ b/vars/debian-family.yml @@ -12,5 +12,9 @@ _postgresql_packages: - libpq-dev - nano - pg-activity - # removed as it depends on the latest version of postgresql package, so it entails installing this package also install the latest postgresql server package and because + # removed as it depends on the latest version of postgresql package, so it entails + # installing this package also install the latest postgresql server package and because # - postgresql-contrib +# SSL files default path +postgresql_ssl_cert_file: /etc/ssl/certs/ssl-cert-snakeoil.pem +postgresql_ssl_key_file: /etc/ssl/private/ssl-cert-snakeoil.key diff --git a/vars/fedora-35.yml b/vars/fedora-35.yml index 71aca8a..d67efaa 100644 --- a/vars/fedora-35.yml +++ b/vars/fedora-35.yml @@ -11,3 +11,6 @@ _postgresql_packages: _postgresql_unix_socket_directories_mode: "{{ postgresql_unix_socket_directories_mode | d('0755', true) }}" # Fedora 32 containers only have python3 by default postgresql_python_library: python3-psycopg2 +# SSL files default path +postgresql_ssl_cert_file: server.crt +postgresql_ssl_key_file: server.key diff --git a/vars/fedora.yml b/vars/fedora.yml index c2ff1be..59a9ea6 100644 --- a/vars/fedora.yml +++ b/vars/fedora.yml @@ -16,3 +16,6 @@ _postgresql_packages: - pg_activity _postgresql_unix_socket_directories_mode: "{{ postgresql_unix_socket_directories_mode | d('0755', true) }}" _postgresql_service_path: "{{ postgresql_service_path | d('/usr/lib/systemd/system/postgresql-' ~ postgresql_version ~ '.service', true) }}" +# SSL files default path +postgresql_ssl_cert_file: server.crt +postgresql_ssl_key_file: server.key diff --git a/vars/main.yml b/vars/main.yml index f1a85dd..4631980 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -31,9 +31,18 @@ _postgresql_service_state: started _postgresql_apt_mirror_url: http://apt.postgresql.org/pub/repos/apt _postgresql_apt_repo_template_path: templates/etc/apt/sources.list.d/pgdb.list.j2 -_postgresql_repo_rpm_url: "https://download.postgresql.org/pub/repos/yum/reporpms/{{ (ansible_distro == 'fedora') | ternary('F', 'EL') }}-{{ ansible_distribution_major_version }}-x86_64/pgdg-{{ (ansible_distro == 'fedora') | ternary('fedora', 'redhat') }}-repo-latest.noarch.rpm" +_postgresql_repo_rpm_url: "https://download.postgresql.org/pub/repos/yum/reporpms/{{ (ansible_distro == 'fedora') | ternary('F', 'EL') }}-{{ ansible_distribution_major_version }}-x86_64/pgdg-{{ (ansible_distro == 'fedora') | ternary('fedora', 'redhat') }}-repo-latest.noarch.rpm" # noqa: yaml[line-length] _postgresql_unix_socket_directories_mode: "{{ postgresql_unix_socket_directories_mode | d('2775', true) }}" _postgresql_service_path: "{{ postgresql_service_path | d('', true) }}" _postgresql_virtualenv_path: "{{ _postgresql_user_home_dir | d('~', true) }}/.venv.claranet.postgresql" _postgresql_ansible_python_interpreter: "{{ _postgresql_virtualenv_path }}/bin/python" _postgresql_pythonized_path: "{{ ansible_env.PATH }}:{{ _postgresql_bin_path }}:{{ _postgresql_virtualenv_path }}/bin" +# SSL key/cert generation +_postgresql_ssl_key_file_path: "{{ postgresql_ssl_key_file if (postgresql_ssl_key_file | dirname != '') + else _postgresql_data_dir ~ '/' ~ postgresql_ssl_key_file }}" +_postgresql_ssl_cert_file_path: "{{ postgresql_ssl_cert_file if (postgresql_ssl_cert_file | dirname != '') +else _postgresql_data_dir ~ '/' ~ postgresql_ssl_cert_file }}" +_postgresql_ssl_key_gen_cmd: openssl genrsa -out "{{ _postgresql_ssl_key_file_path }}" 4096 +_postgresql_ssl_cert_gen_cmd: > + openssl req -new -x509 -days 36500 -subj "{{ postgresql_ssl_cert_subj }}" + -key "{{ _postgresql_ssl_key_file_path }}" -outform PEM -out "{{ _postgresql_ssl_cert_file_path }}" diff --git a/vars/redhat-family.yml b/vars/redhat-family.yml index f19571b..799bd9d 100644 --- a/vars/redhat-family.yml +++ b/vars/redhat-family.yml @@ -17,3 +17,6 @@ _postgresql_packages: - pg_activity _postgresql_unix_socket_directories_mode: "{{ postgresql_unix_socket_directories_mode | d('0755', true) }}" _postgresql_service_path: "{{ postgresql_service_path | d('/usr/lib/systemd/system/postgresql-' ~ postgresql_version ~ '.service', true) }}" +# SSL files default path +postgresql_ssl_cert_file: server.crt +postgresql_ssl_key_file: server.key