diff --git a/.config/ansible-lint.yml b/.config/ansible-lint.yml index 53bf04bd7..48ce1be01 100644 --- a/.config/ansible-lint.yml +++ b/.config/ansible-lint.yml @@ -10,10 +10,12 @@ exclude_paths: - molecule/ssh_hardening_bsd/waivers_freebsd13.yaml - molecule/ssh_hardening_bsd/waivers_freebsd14.yaml - molecule/ssh_hardening_bsd/waivers_openbsd7.yaml + - molecule/postgres_hardening/geerlingguy_postgresql_vars.yml mock_roles: - geerlingguy.git - nginxinc.nginx + - geerlingguy.postgresql skip_list: - var-naming[no-role-prefix] diff --git a/.github/labeler.yml b/.github/labeler.yml index 05a1f76cd..7f5773a1f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -28,3 +28,10 @@ nginx_hardening: - roles/nginx_hardening/** - molecule/nginx_hardening/** - .github/workflows/nginx_hardening.yml + +postgres_hardening: + - changed-files: + - any-glob-to-any-file: + - "roles/postgres_hardening/**" + - "molecule/postgres_hardening/**" + - ".github/workflows/postgres_hardening.yml" diff --git a/.github/workflows/postgres_hardening.yml b/.github/workflows/postgres_hardening.yml new file mode 100644 index 000000000..30e491b4d --- /dev/null +++ b/.github/workflows/postgres_hardening.yml @@ -0,0 +1,90 @@ +--- +name: "devsec.postgres_hardening" +on: # yamllint disable-line rule:truthy + workflow_dispatch: + push: + branches: [master] + paths: + - "roles/postgres_hardening/**" + - "molecule/postgres_hardening/**" + - ".github/workflows/postgres_hardening.yml" + - "requirements.txt" + pull_request: + branches: [master] + paths: + - "roles/postgres_hardening/**" + - "molecule/postgres_hardening/**" + - ".github/workflows/postgres_hardening.yml" + - "requirements.txt" + schedule: + - cron: "0 6 * * 1" + +concurrency: + group: >- + ${{ github.workflow }}-${{ + github.event.pull_request.number || github.sha + }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + env: + PY_COLORS: 1 + ANSIBLE_FORCE_COLOR: 1 + strategy: + fail-fast: false + matrix: + molecule_distro: + # - centos7 + # - centosstream8 + # - centosstream9 + # - rocky8 + # - rocky9 + # - ubuntu1804 + - ubuntu2004 + - ubuntu2204 + # - debian10 + # - debian11 + # - debian12 + # - amazon2023 + # - arch # needs to be fixed + # - opensuse_tumbleweed # needs to be fixed + # - fedora # no support from geerlingguy role + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + path: ansible_collections/devsec/hardening + submodules: true + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install dependencies + run: | + sudo apt install git + python -m pip install --no-cache-dir --upgrade pip + pip install -r requirements.txt + working-directory: ansible_collections/devsec/hardening + + # Molecule has problems detecting the proper location for installing roles + # https://github.com/ansible/molecule/issues/3806 + # we do not set a custom role path, but the automatically determined install path used is not compatible with the location molecule expects the role + # see CI logs of this action "INFO Set ANSIBLE_ROLES_PATH" should not be present, since we do not set a custom path + # we have to find a proper way to configure this + - name: Temporary fix for roles + run: | + mkdir -p /home/runner/.ansible + ln -s /home/runner/work/ansible-collection-hardening/ansible-collection-hardening/ansible_collections/devsec/hardening/roles \ + /home/runner/.ansible/roles + + - name: Test with molecule + run: | + molecule --version + molecule test -s postgres_hardening + env: + MOLECULE_DISTRO: ${{ matrix.molecule_distro }} + working-directory: ansible_collections/devsec/hardening diff --git a/molecule/postgres_hardening/converge.yml b/molecule/postgres_hardening/converge.yml new file mode 100644 index 000000000..7ae261c11 --- /dev/null +++ b/molecule/postgres_hardening/converge.yml @@ -0,0 +1,12 @@ +--- +- name: Wrapper playbook for kitchen testing "ansible-postgres-hardening" with custom settings + become: true + hosts: all + environment: + http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" + https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" + no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" + tasks: + - name: Start Hardening + ansible.builtin.include_role: + name: devsec.hardening.postgres_hardening diff --git a/molecule/postgres_hardening/geerlingguy_postgresql_vars.yml b/molecule/postgres_hardening/geerlingguy_postgresql_vars.yml new file mode 100644 index 000000000..a88dddc51 --- /dev/null +++ b/molecule/postgres_hardening/geerlingguy_postgresql_vars.yml @@ -0,0 +1,6 @@ +postgresql_databases: + - name: example_db +postgresql_users: + - name: postgres + password: iloverandompasswordsbutthiswilldo +postgresql_auth_method: scram-sha-256 \ No newline at end of file diff --git a/molecule/postgres_hardening/molecule.yml b/molecule/postgres_hardening/molecule.yml new file mode 100644 index 000000000..668fa84c7 --- /dev/null +++ b/molecule/postgres_hardening/molecule.yml @@ -0,0 +1,64 @@ +--- +dependency: + name: galaxy + options: + role-file: molecule/postgres_hardening/requirements.yml +driver: + name: docker +platforms: + - name: instance + image: "rndmh3ro/docker-${MOLECULE_DISTRO}-ansible:latest" + command: ${MOLECULE_DOCKER_COMMAND:-/lib/systemd/systemd} + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:rw + privileged: true + cgroupns_mode: host + pre_build_image: true +provisioner: + name: ansible + options: + diff: true + config_options: + defaults: + interpreter_python: auto_silent + callbacks_enabled: profile_tasks, timer, yaml + inventory: + host_vars: + # https://molecule.readthedocs.io/en/latest/examples.html#docker-with-non-privileged-user + # setting for the platform instance named 'instance' + instance: + ansible_user: ansible +verifier: + name: ansible + +scenario: + create_sequence: + - dependency + - create + - prepare + check_sequence: + - dependency + - destroy + - create + - prepare + - converge + - check + - destroy + converge_sequence: + - dependency + - create + - prepare + - converge + destroy_sequence: + - destroy + test_sequence: + - dependency + - destroy + - syntax + - create + - prepare + - check + - converge + - idempotence + - verify + - destroy diff --git a/molecule/postgres_hardening/prepare.yml b/molecule/postgres_hardening/prepare.yml new file mode 100644 index 000000000..fc9e8e65c --- /dev/null +++ b/molecule/postgres_hardening/prepare.yml @@ -0,0 +1,20 @@ +--- +- name: Prepare playbook for kitchen testing "ansible-postgres-hardening" with custom settings + become: true + hosts: all + environment: + http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" + https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" + no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" + vars_files: + - geerlingguy_postgresql_vars.yml + tasks: + - name: Install required packages + ansible.builtin.package: + name: "python3-apt" + update_cache: true + ignore_errors: true # noqa ignore-errors + + - name: Installing PostgreSQL + ansible.builtin.include_role: + name: geerlingguy.postgresql diff --git a/molecule/postgres_hardening/requirements.yml b/molecule/postgres_hardening/requirements.yml new file mode 100644 index 000000000..f9f654283 --- /dev/null +++ b/molecule/postgres_hardening/requirements.yml @@ -0,0 +1,6 @@ +--- +collections: + - community.postgresql + +roles: + - name: geerlingguy.postgresql diff --git a/molecule/postgres_hardening/verify.yml b/molecule/postgres_hardening/verify.yml new file mode 100644 index 000000000..1fa1b51e8 --- /dev/null +++ b/molecule/postgres_hardening/verify.yml @@ -0,0 +1,36 @@ +--- +- name: Verify + hosts: all + become: true + environment: + http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" + https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" + no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" + +- name: Verify + hosts: localhost + environment: + http_proxy: "{{ lookup('env', 'http_proxy') | default(omit) }}" + https_proxy: "{{ lookup('env', 'https_proxy') | default(omit) }}" + no_proxy: "{{ lookup('env', 'no_proxy') | default(omit) }}" + tasks: + - name: Execute cinc-auditor tests + ansible.builtin.command: > + docker run + --volume /run/docker.sock:/run/docker.sock + docker.io/cincproject/auditor exec + -t docker://instance + --no-show-progress --no-color + --no-distinct-exit https://github.com/dev-sec/postgres-baseline/archive/refs/heads/master.zip + register: test_results + changed_when: false + ignore_errors: true + + - name: Display details about the cinc-auditor results + ansible.builtin.debug: + msg: "{{ test_results.stdout_lines }}" + + - name: Fail when tests fail + ansible.builtin.fail: + msg: "Inspec failed to validate" + when: test_results.rc != 0 diff --git a/roles/.gitignore b/roles/.gitignore new file mode 100644 index 000000000..74ccee7c9 --- /dev/null +++ b/roles/.gitignore @@ -0,0 +1 @@ +geerlingguy.postgresql/ \ No newline at end of file diff --git a/roles/os_hardening/README.md b/roles/os_hardening/README.md index 27835b8d7..51989252d 100644 --- a/roles/os_hardening/README.md +++ b/roles/os_hardening/README.md @@ -145,7 +145,6 @@ This role is mostly based on guides by: ## Supported Operating Systems - - EL - 8, 9 - Ubuntu diff --git a/roles/postgres_hardening/CHANGELOG.md b/roles/postgres_hardening/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/roles/postgres_hardening/README.md b/roles/postgres_hardening/README.md new file mode 100644 index 000000000..4a0abada4 --- /dev/null +++ b/roles/postgres_hardening/README.md @@ -0,0 +1,18 @@ +# devsec.postgres_hardening + +[![devsec.postgres_hardening](https://github.com/dev-sec/ansible-collection-hardening/actions/workflows/postgres_hardening.yml/badge.svg)](https://github.com/dev-sec/ansible-collection-hardening/actions/workflows/postgres_hardening.yml) + +## Description + +This role provides secure postgres configuration. It is intended to be compliant with the [DevSec Postgres Baseline](https://github.com/dev-sec/postgres-baseline). + + +**NOTE: This role does not work with postgres 1.0.15 or older! Please use the latest version from the official postgres repositories!** + + + +## Supported Operating Systems [For Now] +- Ubuntu + - bionic, focal, jammy + +## Role Variables diff --git a/roles/postgres_hardening/defaults/main.yml b/roles/postgres_hardening/defaults/main.yml new file mode 100644 index 000000000..7a7ae0cb6 --- /dev/null +++ b/roles/postgres_hardening/defaults/main.yml @@ -0,0 +1,27 @@ +--- +# switcher to enable/disable role +postgres_hardening_enabled: true + +postgres_daemon_enabled: true + +postgres_hardening_restart_postgres: true + +# Postgres user/group +postgres_user: postgres +postgres_group: postgres + +# Password Authentication +password_encryption: scram-sha-256 + +# SSL +ssl_enabled: "on" +ssl_ciphers: ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH + +# Logging +logging_collector: "on" +log_connections: "on" +log_disconnections: "on" +log_duration: "on" +log_hostname: "on" +log_directory: pg_log +log_line_prefix: "%t %u %d %h" diff --git a/roles/postgres_hardening/handlers/main.yml b/roles/postgres_hardening/handlers/main.yml new file mode 100644 index 000000000..104d536c9 --- /dev/null +++ b/roles/postgres_hardening/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: Restart postgres + ansible.builtin.service: + name: "{{ postgres_daemon }}" + state: restarted + when: postgres_hardening_restart_postgres | bool diff --git a/roles/postgres_hardening/tasks/hardening.yml b/roles/postgres_hardening/tasks/hardening.yml new file mode 100644 index 000000000..7b36e0d24 --- /dev/null +++ b/roles/postgres_hardening/tasks/hardening.yml @@ -0,0 +1,142 @@ +--- +- name: Fetch OS dependent variables + ansible.builtin.include_vars: + file: "{{ item }}" + name: os_vars + with_first_found: + - files: + - "{{ ansible_facts.distribution }}_{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.distribution }}.yml" + - "{{ ansible_facts.os_family }}_{{ ansible_facts.distribution_major_version }}.yml" + - "{{ ansible_facts.os_family }}.yml" + skip: true + tags: always + +# we only override variables with our default if they have not been specified already. +# by default the lookup functions finds all varnames containing the string, therefore +# we add ^ and $ to denote start and end of string, so this returns only exact matches. +- name: Set OS dependent variables, if not already defined by user # noqa var-naming + ansible.builtin.set_fact: + "{{ item.key }}": "{{ item.value }}" + when: not lookup('varnames', '^' + item.key + '$') + with_dict: "{{ os_vars }}" + tags: always + +################################# +# Check Compatibility ########### +################################# + +- name: Only compatible OS versions + ansible.builtin.fail: + msg: "Only Ubuntu/Debian are supported" + when: ansible_facts.os_family not in ["Ubuntu"] + +################################# +# POSTGRES-01 ################### +################################# +- name: Check postgres service status + ansible.builtin.service: + name: "{{ postgres_daemon }}" + state: started + +################################# +# POSTGRES-02 ################### +################################# +- name: Get postgres version + ansible.builtin.command: psql -V + register: postgres_version_raw + changed_when: false + check_mode: false + + +- name: Parse postgres-version + ansible.builtin.set_fact: + postgres_version: "{{ postgres_version_raw.stdout | regex_search('psql\\s\\(PostgreSQL\\)\\s(12|13|14|15|16).*', '\\1') | first }}" + + +- name: Only compatible postgres versions allowed + ansible.builtin.fail: + msg: "Postgres Version is not secure or supported!" + when: not postgres_version or 'RC' in postgres_version_raw or 'DEVEL' in postgres_version_raw or 'BETA' in postgres_version_raw + +################################# +# POSTGRES-10 ################### +################################# +- name: Manage permissions on /etc/postgresql//main + ansible.builtin.file: + path: "/etc/postgresql/{{ postgres_version }}/main" + state: directory + owner: "{{ postgres_user }}" + group: "{{ postgres_group }}" + mode: "0700" + +- name: Manage permissions on /etc/postgresql//main/postgresql.conf + ansible.builtin.file: + path: "/etc/postgresql/{{ postgres_version }}/main/postgresql.conf" + state: file + owner: "{{ postgres_user }}" + group: "{{ postgres_group }}" + mode: "0640" + +################################# +# POSTGRES-07/11/12/16 ########## +################################# +- name: Secure postgresql.conf Configuration + ansible.builtin.lineinfile: + path: "/etc/postgresql/{{ postgres_version }}/main/postgresql.conf" + line: "{{ item.line }}" + regexp: "{{ item.regexp }}" + state: present + with_items: + - line: "password_encryption = {{ password_encryption }}" + regexp: "#?password_encryption\\s?=" + - line: "ssl = {{ ssl_enabled }}" + regexp: "#?ssl\\s?=" + - line: "ssl_ciphers = '{{ ssl_ciphers }}'" + regexp: "#?ssl_ciphers\\s?=" + - line: "logging_collector = {{ logging_collector }}" + regexp: "#?logging_collector\\s?=" + - line: "log_connections = {{ log_connections }}" + regexp: "#?log_connections\\s?=" + - line: "log_disconnections = {{ log_disconnections }}" + regexp: "#?log_disconnections\\s?=" + - line: "log_duration = {{ log_duration }}" + regexp: "#?log_duration\\s?=" + - line: "log_hostname = {{ log_hostname }}" + regexp: "#?log_hostname\\s?=" + - line: "log_directory = '{{ log_directory }}'" + regexp: "#?log_directory\\s?=" + - line: "log_line_prefix = '{{ log_line_prefix }}'" + regexp: "#?log_line_prefix\\s?=" + notify: Restart postgres + +################################# +# POSTGRES-13/14/15 ############# +################################# +- name: Secure pg_hba.conf Configuration + ansible.builtin.template: + src: templates/pg_hba.conf + dest: /etc/postgresql/{{ postgres_version }}/main/pg_hba.conf + owner: "{{ postgres_user }}" + group: "{{ postgres_group }}" + mode: u=rw,g=,o= + notify: Restart postgres + +################################# +# POSTGRES-20 ################### +################################# +- name: Manage permissions on /var/lib/postgresql//main + ansible.builtin.file: + path: "/var/lib/postgresql/{{ postgres_version }}/main" + state: directory + owner: "{{ postgres_user }}" + group: "{{ postgres_group }}" + mode: u=rwx,g=,o= + +- name: Manage permissions on /var/log/postgresql + ansible.builtin.file: + path: /var/log/postgresql + state: directory + owner: "{{ postgres_user }}" + group: "{{ postgres_group }}" + mode: u=rwx,g=,o= diff --git a/roles/postgres_hardening/tasks/main.yml b/roles/postgres_hardening/tasks/main.yml new file mode 100644 index 000000000..0ca3c41ea --- /dev/null +++ b/roles/postgres_hardening/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- name: Include hardening tasks + ansible.builtin.include_tasks: hardening.yml + args: + apply: + become: true + when: postgres_hardening_enabled | bool diff --git a/roles/postgres_hardening/templates/pg_hba.conf b/roles/postgres_hardening/templates/pg_hba.conf new file mode 100644 index 000000000..61f853bfc --- /dev/null +++ b/roles/postgres_hardening/templates/pg_hba.conf @@ -0,0 +1,5 @@ +local all postgres peer +local all all peer +hostssl all all 127.0.0.1/32 scram-sha-256 +hostssl all all ::1/128 scram-sha-256 +local replication all peer \ No newline at end of file diff --git a/roles/postgres_hardening/vars/Ubuntu.yml b/roles/postgres_hardening/vars/Ubuntu.yml new file mode 100644 index 000000000..d2f1aec46 --- /dev/null +++ b/roles/postgres_hardening/vars/Ubuntu.yml @@ -0,0 +1,2 @@ +--- +postgres_daemon: postgresql diff --git a/roles/ssh_hardening/README.md b/roles/ssh_hardening/README.md index 420edacb3..05d0ea51a 100644 --- a/roles/ssh_hardening/README.md +++ b/roles/ssh_hardening/README.md @@ -46,7 +46,6 @@ For more information, see [this issue](https://github.com/dev-sec/ansible-collec ## Supported Operating Systems - - EL - 8, 9 - Ubuntu