From 0c88f2f816d8475b6683ec9a3a5b25e8e1bcbe39 Mon Sep 17 00:00:00 2001 From: Sam Crauwels Date: Tue, 10 Mar 2026 14:12:06 +0100 Subject: [PATCH 1/5] Fix all ansible-lint warnings at production profile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renamed ~47 registered variables across all roles to use the proper role prefix (e.g. _es_cgroup_memory → _elasticsearch_cgroup_memory, _validate_cert_stat → _elasticstack_validate_cert_stat). Fixed Jinja2 spacing in package name templates across beats, elasticsearch, kibana and logstash. Added pipefail to the shell pipe in wait_for_instance.yml. Suppressed intentional cross-role variable warnings for elasticstack_password and negative test cases in the cert_info module test. Partial fix for #27 --- molecule/plugins/converge.yml | 4 +- roles/beats/tasks/beat-install.yml | 2 +- roles/beats/tasks/restart_and_verify_beat.yml | 8 +- .../elasticsearch/handlers/restart_kibana.yml | 10 +-- .../tasks/elasticsearch-security.yml | 44 ++++----- roles/elasticsearch/tasks/main.yml | 50 +++++------ .../elasticsearch/tasks/wait_for_instance.yml | 20 +++-- .../tasks/certs/cert_expiry_warn.yml | 18 ++-- .../tasks/certs/cert_validate.yml | 90 +++++++++---------- roles/elasticstack/tasks/packages.yml | 4 +- roles/kibana/tasks/kibana-security.yml | 6 +- roles/kibana/tasks/main.yml | 2 +- roles/logstash/tasks/main.yml | 2 +- roles/repos/tasks/debian.yml | 8 +- roles/repos/tasks/redhat.yml | 12 +-- 15 files changed, 141 insertions(+), 139 deletions(-) diff --git a/molecule/plugins/converge.yml b/molecule/plugins/converge.yml index fa562569..1a9d8e60 100644 --- a/molecule/plugins/converge.yml +++ b/molecule/plugins/converge.yml @@ -15,7 +15,7 @@ - name: Debug ansible.builtin.debug: msg: "{{ test }}" - - name: Test required parameters (missing path) + - name: Test required parameters (missing path) # noqa: args[module] oddly.elasticstack.cert_info: passphrase: PleaseChangeMe failed_when: false @@ -37,6 +37,6 @@ oddly.elasticstack.cert_info: path: files/es-ca/elastic-stack-ca.p12 failed_when: false - - name: Test no parameters + - name: Test no parameters # noqa: args[module] oddly.elasticstack.cert_info: failed_when: false diff --git a/roles/beats/tasks/beat-install.yml b/roles/beats/tasks/beat-install.yml index 0e5314c4..b208e34b 100644 --- a/roles/beats/tasks/beat-install.yml +++ b/roles/beats/tasks/beat-install.yml @@ -12,7 +12,7 @@ _beat_name + ((elasticstack_versionseparator + elasticstack_version | - string ) if (elasticstack_version is defined and elasticstack_version | length > 0) else '') | + string) if (elasticstack_version is defined and elasticstack_version | length > 0) else '') | replace(' ', '') }} - name: "Install rpm full-stack package for {{ _beat_name }}" diff --git a/roles/beats/tasks/restart_and_verify_beat.yml b/roles/beats/tasks/restart_and_verify_beat.yml index 073cb7a5..87e3f8f2 100644 --- a/roles/beats/tasks/restart_and_verify_beat.yml +++ b/roles/beats/tasks/restart_and_verify_beat.yml @@ -10,8 +10,8 @@ - name: "Verify beat service is running — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.systemd: name: "{{ _beat_service_name }}" - register: _beat_service_state - until: _beat_service_state.status.ActiveState == 'active' + register: _beats_service_state + until: _beats_service_state.status.ActiveState == 'active' retries: 5 delay: 3 @@ -19,7 +19,7 @@ - name: "Get recent journal output — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.command: cmd: "journalctl -u {{ _beat_service_name }} --no-pager -n 50" - register: _beat_journal + register: _beats_journal changed_when: false - name: "Fail with startup diagnostics — {{ _beat_service_name }}" # noqa: name[template] @@ -28,4 +28,4 @@ {{ _beat_service_name }} failed to start. Recent log output: - {{ _beat_journal.stdout }} + {{ _beats_journal.stdout }} diff --git a/roles/elasticsearch/handlers/restart_kibana.yml b/roles/elasticsearch/handlers/restart_kibana.yml index e168027b..588738d2 100644 --- a/roles/elasticsearch/handlers/restart_kibana.yml +++ b/roles/elasticsearch/handlers/restart_kibana.yml @@ -26,18 +26,18 @@ fi exit 1 delegate_to: "{{ item }}" - register: _kibana_handler_wait - until: _kibana_handler_wait.rc == 0 + register: _elasticsearch_kibana_handler_wait + until: _elasticsearch_kibana_handler_wait.rc == 0 retries: 60 delay: 5 changed_when: false - failed_when: _kibana_handler_wait.rc == 2 + failed_when: _elasticsearch_kibana_handler_wait.rc == 2 rescue: - name: Get recent Kibana journal output ansible.builtin.command: cmd: journalctl -u kibana --no-pager -n 50 - register: _kibana_handler_journal + register: _elasticsearch_kibana_handler_journal delegate_to: "{{ item }}" changed_when: false @@ -47,4 +47,4 @@ Kibana failed to start after restart by elasticsearch handler. Recent log output: - {{ _kibana_handler_journal.stdout }} + {{ _elasticsearch_kibana_handler_journal.stdout }} diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index 523b8ccf..f8551ac4 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -261,7 +261,7 @@ cmd: >- awk '/-----BEGIN CERTIFICATE-----/{n++} n>1' /etc/elasticsearch/certs/{{ inventory_hostname }}-transport.crt - register: _extracted_ca_chain + register: _elasticsearch_extracted_ca_chain changed_when: false when: - elasticsearch_tls_ca_certificate | length == 0 @@ -270,7 +270,7 @@ - name: Write extracted CA chain ansible.builtin.copy: - content: "{{ _extracted_ca_chain.stdout }}\n" + content: "{{ _elasticsearch_extracted_ca_chain.stdout }}\n" dest: /etc/elasticsearch/certs/ca.crt owner: root group: elasticsearch @@ -296,16 +296,16 @@ excludes: - "{{ inventory_hostname }}-transport.p12" - "{{ inventory_hostname }}-http.p12" - register: _stale_p12_files + register: _elasticsearch_stale_p12_files - name: Remove stale auto-generated P12 files ansible.builtin.file: path: "{{ item.path }}" state: absent - loop: "{{ _stale_p12_files.files }}" + loop: "{{ _elasticsearch_stale_p12_files.files }}" loop_control: label: "{{ item.path | basename }}" - when: _stale_p12_files.files | length > 0 + when: _elasticsearch_stale_p12_files.files | length > 0 # -- Keystore setup for external certs -- @@ -361,7 +361,7 @@ loop: - encryption_key - savedobjects_encryption_key - register: _kibana_enckeys_before_backup + register: _elasticsearch_kibana_enckeys_before_backup when: - inventory_hostname == elasticstack_ca_host - "'renew_ca' in ansible_run_tags or elasticstack_ca_will_expire_soon | bool" @@ -374,7 +374,7 @@ dest: "/tmp/.kibana_preserved_{{ item.item }}" remote_src: true mode: "0600" - loop: "{{ _kibana_enckeys_before_backup.results | default([]) }}" + loop: "{{ _elasticsearch_kibana_enckeys_before_backup.results | default([]) }}" when: - inventory_hostname == elasticstack_ca_host - "'renew_ca' in ansible_run_tags or elasticstack_ca_will_expire_soon | bool" @@ -680,7 +680,7 @@ ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -b -s - register: _elastic_reset_pw + register: _elasticsearch_elastic_reset_pw changed_when: true no_log: "{{ elasticstack_no_log }}" @@ -688,11 +688,11 @@ ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}" user: elastic - password: "{{ _elastic_reset_pw.stdout }}" + password: "{{ _elasticsearch_elastic_reset_pw.stdout }}" validate_certs: "{{ elasticsearch_validate_api_certs }}" force_basic_auth: true - register: _elastic_recovery_check - until: (_elastic_recovery_check.json | default({})).cluster_name is defined + register: _elasticsearch_elastic_recovery_check + until: (_elasticsearch_elastic_recovery_check.json | default({})).cluster_name is defined retries: 5 delay: 5 no_log: "{{ elasticstack_no_log }}" @@ -702,7 +702,7 @@ dest: "{{ elasticstack_initial_passwords }}" content: | Changed password for user elastic - PASSWORD elastic = {{ _elastic_reset_pw.stdout }} + PASSWORD elastic = {{ _elasticsearch_elastic_reset_pw.stdout }} owner: root group: root mode: "0600" @@ -762,7 +762,7 @@ # actually been applied to ES yet. Clear it so the API check below # is safely skipped (the bootstrap-password check already confirmed # the cluster is reachable). - - name: Clear premature user-defined password on fresh install + - name: Clear premature user-defined password on fresh install # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "" @@ -792,7 +792,7 @@ # If the auto-generated password returned 401, the user-defined # password was already applied in a previous run. Switch to it. - - name: Switch to user-defined password after prior change + - name: Switch to user-defined password after prior change # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "{{ elasticsearch_elastic_password }}" @@ -894,8 +894,8 @@ args: executable: /bin/bash creates: "{{ elasticstack_initial_passwords }}" - register: _setup_passwords_result - until: _setup_passwords_result.rc | default(1) == 0 + register: _elasticsearch_setup_passwords_result + until: _elasticsearch_setup_passwords_result.rc | default(1) == 0 retries: 10 delay: 15 when: @@ -911,7 +911,7 @@ - name: Check if passwords file already exists ansible.builtin.stat: path: "{{ elasticstack_initial_passwords }}" - register: _pw_file_check + register: _elasticsearch_pw_file_check - name: Reset built-in user passwords ansible.builtin.command: > @@ -923,16 +923,16 @@ - logstash_system - beats_system - remote_monitoring_user - register: _reset_password_results + register: _elasticsearch_reset_password_results changed_when: true no_log: "{{ elasticstack_no_log }}" - when: not _pw_file_check.stat.exists + when: not _elasticsearch_pw_file_check.stat.exists - name: Write passwords file ansible.builtin.copy: dest: "{{ elasticstack_initial_passwords }}" content: | - {% for result in _reset_password_results.results %} + {% for result in _elasticsearch_reset_password_results.results %} Changed password for user {{ result.item }} PASSWORD {{ result.item }} = {{ result.stdout }} {% endfor %} @@ -940,7 +940,7 @@ group: root mode: "0600" no_log: "{{ elasticstack_no_log }}" - when: not _pw_file_check.stat.exists + when: not _elasticsearch_pw_file_check.stat.exists - name: Set permissions on passwords file ansible.builtin.file: @@ -977,7 +977,7 @@ status_code: 200 no_log: "{{ elasticstack_no_log }}" - - name: Set elastic password fact to user-defined value + - name: Set elastic password fact to user-defined value # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "{{ elasticsearch_elastic_password }}" diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index d4b1f539..8cc16335 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -34,7 +34,7 @@ - name: Detect cgroup memory limit ansible.builtin.slurp: src: /sys/fs/cgroup/memory.max - register: _es_cgroup_memory + register: _elasticsearch_cgroup_memory failed_when: false changed_when: false @@ -45,15 +45,15 @@ # (detected by comparing with the default formula based on memtotal_mb). - name: Recalculate heap from cgroup memory limit vars: - _es_cgroup_bytes: "{{ (_es_cgroup_memory.content | b64decode | trim) | int }}" + _es_cgroup_bytes: "{{ (_elasticsearch_cgroup_memory.content | b64decode | trim) | int }}" _es_cgroup_mb: "{{ (_es_cgroup_bytes | int) // 1048576 }}" _es_default_heap: "{{ [[(ansible_facts.memtotal_mb // 1024) // 2, 30] | min, 1] | max }}" ansible.builtin.set_fact: elasticsearch_heap: >- {{ [[(_es_cgroup_mb | int) // 1024 // 2, 30] | min, 1] | max }} when: - - _es_cgroup_memory.content is defined - - (_es_cgroup_memory.content | b64decode | trim) != 'max' + - _elasticsearch_cgroup_memory.content is defined + - (_elasticsearch_cgroup_memory.content | b64decode | trim) != 'max' - _es_cgroup_bytes | int < (ansible_facts.memtotal_mb | int) * 1048576 - elasticsearch_heap | int == _es_default_heap | int @@ -144,8 +144,8 @@ - name: Install openssl if security is activated ansible.builtin.package: name: openssl - register: _openssl_install - until: _openssl_install is success + register: _elasticsearch_openssl_install + until: _elasticsearch_openssl_install is success retries: 3 delay: 10 when: elasticsearch_security | bool @@ -176,7 +176,7 @@ 'elasticsearch' + ((elasticstack_versionseparator + elasticstack_version | - string ) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | + string) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | replace(' ', '') }} @@ -285,7 +285,7 @@ - name: Detect existing external CA for template rendering ansible.builtin.stat: path: /etc/elasticsearch/certs/ca.crt - register: _existing_ca_cert + register: _elasticsearch_existing_ca_cert when: elasticsearch_cert_source == 'external' - name: Set external CA availability for initial template render @@ -293,7 +293,7 @@ _elasticsearch_external_has_ca: true when: - elasticsearch_cert_source == 'external' - - (_existing_ca_cert.stat.exists | default(false)) or + - (_elasticsearch_existing_ca_cert.stat.exists | default(false)) or (elasticsearch_tls_ca_certificate | default('') | length > 0) or (elasticsearch_tls_ca_certificate_content | default('') | length > 0) @@ -453,24 +453,24 @@ - name: Check keystore exists (no-security) ansible.builtin.stat: path: /etc/elasticsearch/elasticsearch.keystore - register: _es_keystore_nosec + register: _elasticsearch_keystore_nosec - name: List keystore entries (no-security) ansible.builtin.command: /usr/share/elasticsearch/bin/elasticsearch-keystore list changed_when: false - register: _es_keystore_entries_nosec - when: _es_keystore_nosec.stat.exists + register: _elasticsearch_keystore_entries_nosec + when: _elasticsearch_keystore_nosec.stat.exists - name: Remove orphaned SSL keystore entries ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove {{ item }} - loop: "{{ _es_keystore_entries_nosec.stdout_lines | default([]) | select('match', 'xpack\\.security\\.') | list }}" + loop: "{{ _elasticsearch_keystore_entries_nosec.stdout_lines | default([]) | select('match', 'xpack\\.security\\.') | list }}" loop_control: label: "{{ item }}" changed_when: true when: - - _es_keystore_nosec.stat.exists - - _es_keystore_entries_nosec.stdout_lines is defined + - _elasticsearch_keystore_nosec.stat.exists + - _elasticsearch_keystore_entries_nosec.stdout_lines is defined - name: Start Elasticsearch ansible.builtin.service: @@ -531,8 +531,8 @@ ansible.builtin.debug: msg: >- Using {{ elasticsearch_heap | int * 1024 }} of - {% if _es_cgroup_memory.content is defined and (_es_cgroup_memory.content | b64decode | trim) != 'max' %} - {{ (_es_cgroup_memory.content | b64decode | trim) | int // 1048576 }} MB (cgroup limit) + {% if _elasticsearch_cgroup_memory.content is defined and (_elasticsearch_cgroup_memory.content | b64decode | trim) != 'max' %} + {{ (_elasticsearch_cgroup_memory.content | b64decode | trim) | int // 1048576 }} MB (cgroup limit) {% else %} {{ ansible_facts.memtotal_mb }} MB {% endif %} @@ -555,14 +555,14 @@ - name: Build effective cluster settings ansible.builtin.set_fact: - _es_effective_cluster_settings: >- + _elasticsearch_effective_cluster_settings: >- {{ (elasticsearch_logsdb | bool) | ternary({'cluster.logsdb.enabled': 'true'}, {}) | combine(elasticsearch_cluster_settings | default({})) }} - name: Apply persistent cluster settings # noqa: run-once[task] when: - - _es_effective_cluster_settings | length > 0 + - _elasticsearch_effective_cluster_settings | length > 0 - elasticsearch_security | bool | ternary(elasticstack_password is defined and (elasticstack_password.stdout | default('') | length > 0), true) - not ansible_check_mode run_once: true @@ -577,17 +577,17 @@ force_basic_auth: "{{ elasticsearch_security | bool }}" validate_certs: "{{ elasticsearch_validate_api_certs }}" return_content: true - register: _es_current_cluster_settings + register: _elasticsearch_current_cluster_settings no_log: "{{ elasticstack_no_log }}" - name: Check if settings already match ansible.builtin.set_fact: - _es_cluster_settings_changed: "{{ _needs_update | trim }}" + _elasticsearch_cluster_settings_changed: "{{ _needs_update | trim }}" vars: - _current: "{{ _es_current_cluster_settings.json.persistent }}" + _current: "{{ _elasticsearch_current_cluster_settings.json.persistent }}" _needs_update: >- {% set ns = namespace(changed=false) %} - {% for key, value in _es_effective_cluster_settings.items() %} + {% for key, value in _elasticsearch_effective_cluster_settings.items() %} {% if _current.get(key) is none or _current[key] | string != value | string %} {% set ns.changed = true %} {% endif %} @@ -600,12 +600,12 @@ method: PUT body_format: json body: - persistent: "{{ _es_effective_cluster_settings }}" + persistent: "{{ _elasticsearch_effective_cluster_settings }}" user: "{{ 'elastic' if elasticsearch_security | bool else omit }}" password: "{{ elasticstack_password.stdout if elasticsearch_security | bool else omit }}" force_basic_auth: "{{ elasticsearch_security | bool }}" validate_certs: "{{ elasticsearch_validate_api_certs }}" status_code: 200 no_log: "{{ elasticstack_no_log }}" - when: _es_cluster_settings_changed | bool + when: _elasticsearch_cluster_settings_changed | bool changed_when: true diff --git a/roles/elasticsearch/tasks/wait_for_instance.yml b/roles/elasticsearch/tasks/wait_for_instance.yml index 2a5e38ed..2daa3e73 100644 --- a/roles/elasticsearch/tasks/wait_for_instance.yml +++ b/roles/elasticsearch/tasks/wait_for_instance.yml @@ -10,6 +10,7 @@ - name: Wait for Elasticsearch port with service health check ansible.builtin.shell: cmd: | + set -o pipefail if ! systemctl is-active --quiet elasticsearch; then exit 2 fi @@ -17,18 +18,19 @@ exit 0 fi exit 1 - register: _es_wait_result - until: _es_wait_result.rc == 0 + executable: /bin/bash + register: _elasticsearch_wait_result + until: _elasticsearch_wait_result.rc == 0 retries: 120 delay: 5 changed_when: false - failed_when: _es_wait_result.rc == 2 + failed_when: _elasticsearch_wait_result.rc == 2 rescue: - name: Get recent Elasticsearch journal output ansible.builtin.command: cmd: journalctl -u elasticsearch --no-pager -n 50 - register: _es_wait_journal + register: _elasticsearch_wait_journal changed_when: false - name: Fail with Elasticsearch diagnostics (service crashed) @@ -37,15 +39,15 @@ Elasticsearch service died while waiting for port {{ elasticstack_elasticsearch_http_port }}. Recent log output: - {{ _es_wait_journal.stdout }} - when: _es_wait_result.rc | default(0) == 2 + {{ _elasticsearch_wait_journal.stdout }} + when: _elasticsearch_wait_result.rc | default(0) == 2 - name: Fail with Elasticsearch diagnostics (port timeout) ansible.builtin.fail: msg: | Elasticsearch port {{ elasticstack_elasticsearch_http_port }} did not become available within 600s. - Service state: {{ _es_wait_result.stdout | default('unknown') }} + Service state: {{ _elasticsearch_wait_result.stdout | default('unknown') }} Recent log output: - {{ _es_wait_journal.stdout }} - when: _es_wait_result.rc | default(0) != 2 + {{ _elasticsearch_wait_journal.stdout }} + when: _elasticsearch_wait_result.rc | default(0) != 2 diff --git a/roles/elasticstack/tasks/certs/cert_expiry_warn.yml b/roles/elasticstack/tasks/certs/cert_expiry_warn.yml index 5dd88908..070d0f2c 100644 --- a/roles/elasticstack/tasks/certs/cert_expiry_warn.yml +++ b/roles/elasticstack/tasks/certs/cert_expiry_warn.yml @@ -14,38 +14,38 @@ - name: "Check certificate file exists — {{ _warn_service_name }}" ansible.builtin.stat: path: "{{ _warn_cert_path }}" - register: _warn_cert_file + register: _elasticstack_warn_cert_file - name: "Check certificate expiry — {{ _warn_service_name }}" - when: _warn_cert_file.stat.exists + when: _elasticstack_warn_cert_file.stat.exists block: - name: "Read certificate info — {{ _warn_service_name }}" oddly.elasticstack.cert_info: path: "{{ _warn_cert_path }}" passphrase: "{{ _warn_cert_pass | default(omit, true) }}" format: "{{ _warn_cert_format | default('p12') }}" - register: _warn_cert_result + register: _elasticstack_warn_cert_result ignore_errors: true # noqa: ignore-errors - name: "Calculate days until certificate expires — {{ _warn_service_name }}" ansible.builtin.set_fact: - _warn_days_remaining: >- - {{ ((((_warn_cert_result.not_valid_after | regex_replace('[+-]\d{2}:\d{2}$', '')) + _elasticstack_warn_days_remaining: >- + {{ ((((_elasticstack_warn_cert_result.not_valid_after | regex_replace('[+-]\d{2}:\d{2}$', '')) | to_datetime('%Y-%m-%d %H:%M:%S')) - (ansible_facts.date_time.date | to_datetime('%Y-%m-%d'))).days) }} - when: _warn_cert_result is succeeded + when: _elasticstack_warn_cert_result is succeeded - name: "Certificate expires soon — {{ _warn_service_name }}" ansible.builtin.debug: msg: >- Certificate {{ _warn_cert_path }} on {{ inventory_hostname }} - expires in {{ _warn_days_remaining }} days. + expires in {{ _elasticstack_warn_days_remaining }} days. {% if (_warn_cert_source | default('elasticsearch_ca')) == 'external' %} Replace the certificate files and re-run the playbook. {% else %} Run with --tags renew_{{ _warn_service_name | lower | replace(' ', '_') }}_cert to renew. {% endif %} when: - - _warn_cert_result is succeeded - - _warn_days_remaining | int <= _warn_buffer_days | int + - _elasticstack_warn_cert_result is succeeded + - _elasticstack_warn_days_remaining | int <= _warn_buffer_days | int changed_when: true diff --git a/roles/elasticstack/tasks/certs/cert_validate.yml b/roles/elasticstack/tasks/certs/cert_validate.yml index 43be0ea9..abe08f3a 100644 --- a/roles/elasticstack/tasks/certs/cert_validate.yml +++ b/roles/elasticstack/tasks/certs/cert_validate.yml @@ -16,7 +16,7 @@ - name: "Check certificate file exists — {{ _validate_service }}" ansible.builtin.stat: path: "{{ _validate_cert_path }}" - register: _validate_cert_stat + register: _elasticstack_validate_cert_stat delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false @@ -25,14 +25,14 @@ msg: >- Certificate file not found: {{ _validate_cert_path }} (looked on {{ 'managed node' if (_validate_remote_src | bool) else 'Ansible controller' }}) - when: not _validate_cert_stat.stat.exists + when: not _elasticstack_validate_cert_stat.stat.exists # --- Format detection --- - name: "Probe for PEM format — {{ _validate_service }}" ansible.builtin.command: cmd: openssl x509 -in {{ _validate_cert_path }} -noout - register: _validate_pem_probe + register: _elasticstack_validate_pem_probe failed_when: false changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" @@ -43,13 +43,13 @@ cmd: >- openssl pkcs12 -in {{ _validate_cert_path }} -noout {{ ('-passin pass:' ~ _validate_pass) if (_validate_pass | default('', true) | length > 0) else '-passin pass:' }} - register: _validate_p12_probe + register: _elasticstack_validate_p12_probe failed_when: false changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false no_log: true - when: _validate_pem_probe.rc != 0 + when: _elasticstack_validate_pem_probe.rc != 0 - name: "Fail if format unrecognised — {{ _validate_service }}" ansible.builtin.fail: @@ -57,41 +57,41 @@ Unsupported certificate format for {{ _validate_service }}. File {{ _validate_cert_path }} is neither valid PEM nor PKCS12. when: - - _validate_pem_probe.rc != 0 - - (_validate_p12_probe.rc | default(1)) != 0 + - _elasticstack_validate_pem_probe.rc != 0 + - (_elasticstack_validate_p12_probe.rc | default(1)) != 0 - name: "Set detected format fact — {{ _validate_service }}" ansible.builtin.set_fact: - "{{ _validate_format_fact }}": "{{ 'pem' if _validate_pem_probe.rc == 0 else 'p12' }}" + "{{ _validate_format_fact }}": "{{ 'pem' if _elasticstack_validate_pem_probe.rc == 0 else 'p12' }}" # --- PEM key auto-detection --- - name: "Derive key path from certificate path — {{ _validate_service }}" when: - (_validate_key_path | default('', true)) | length == 0 - - _validate_pem_probe.rc == 0 + - _elasticstack_validate_pem_probe.rc == 0 block: - name: "Compute derived key path — {{ _validate_service }}" ansible.builtin.set_fact: - _validate_derived_key: "{{ _validate_cert_path | regex_replace('\\.(crt|pem|cert)$', '.key') }}" + _elasticstack_validate_derived_key: "{{ _validate_cert_path | regex_replace('\\.(crt|pem|cert)$', '.key') }}" - name: "Check derived key exists — {{ _validate_service }}" ansible.builtin.stat: - path: "{{ _validate_derived_key }}" - register: _validate_derived_key_stat + path: "{{ _elasticstack_validate_derived_key }}" + register: _elasticstack_validate_derived_key_stat delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - name: "Fail if derived key not found — {{ _validate_service }}" ansible.builtin.fail: msg: >- - Could not find key file {{ _validate_derived_key }} (derived from cert path). + Could not find key file {{ _elasticstack_validate_derived_key }} (derived from cert path). Set the corresponding *_tls_key variable explicitly. - when: not _validate_derived_key_stat.stat.exists + when: not _elasticstack_validate_derived_key_stat.stat.exists - name: "Set resolved key fact — {{ _validate_service }}" ansible.builtin.set_fact: - "{{ _validate_key_fact }}": "{{ _validate_derived_key }}" + "{{ _validate_key_fact }}": "{{ _elasticstack_validate_derived_key }}" - name: "Set explicit key fact — {{ _validate_service }}" ansible.builtin.set_fact: @@ -103,41 +103,41 @@ ansible.builtin.set_fact: "{{ _validate_key_fact }}": "" when: - - _validate_pem_probe.rc != 0 + - _elasticstack_validate_pem_probe.rc != 0 # --- PEM CA chain extraction --- - name: "Check for CA chain in PEM bundle — {{ _validate_service }}" - when: _validate_pem_probe.rc == 0 + when: _elasticstack_validate_pem_probe.rc == 0 block: - name: "Count PEM blocks in certificate file — {{ _validate_service }}" ansible.builtin.command: cmd: grep -c 'BEGIN CERTIFICATE' {{ _validate_cert_path }} - register: _validate_pem_count + register: _elasticstack_validate_pem_count changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - name: "Set CA extracted fact — {{ _validate_service }}" ansible.builtin.set_fact: - "{{ _validate_ca_extracted_fact }}": "{{ (_validate_pem_count.stdout | int) > 1 }}" + "{{ _validate_ca_extracted_fact }}": "{{ (_elasticstack_validate_pem_count.stdout | int) > 1 }}" - name: "Set CA extracted fact for P12 — {{ _validate_service }}" ansible.builtin.set_fact: "{{ _validate_ca_extracted_fact }}": false - when: _validate_pem_probe.rc != 0 + when: _elasticstack_validate_pem_probe.rc != 0 # --- Certificate expiry check --- - name: "Check certificate has not expired — {{ _validate_service }}" ansible.builtin.command: cmd: openssl x509 -in {{ _validate_cert_path }} -noout -checkend 0 - register: _validate_expiry_check + register: _elasticstack_validate_expiry_check failed_when: false changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - when: _validate_pem_probe.rc == 0 + when: _elasticstack_validate_pem_probe.rc == 0 - name: "Fail if certificate already expired — {{ _validate_service }}" ansible.builtin.fail: @@ -145,21 +145,21 @@ {{ _validate_service }} certificate {{ _validate_cert_path }} has already expired. Replace it with a valid certificate and re-run the playbook. when: - - _validate_pem_probe.rc == 0 - - _validate_expiry_check.rc != 0 + - _elasticstack_validate_pem_probe.rc == 0 + - _elasticstack_validate_expiry_check.rc != 0 # --- PEM key-cert match verification --- - name: "Verify key matches certificate — {{ _validate_service }}" when: - - _validate_pem_probe.rc == 0 + - _elasticstack_validate_pem_probe.rc == 0 - lookup('vars', _validate_key_fact) | length > 0 block: - name: "Get certificate modulus — {{ _validate_service }}" ansible.builtin.shell: cmd: set -o pipefail && openssl x509 -in {{ _validate_cert_path }} -noout -modulus 2>/dev/null | openssl md5 executable: /bin/bash - register: _validate_cert_modulus + register: _elasticstack_validate_cert_modulus changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false @@ -172,7 +172,7 @@ {{ ('-passin pass:' ~ _validate_pass) if (_validate_pass | default('', true) | length > 0) else '' }} 2>/dev/null | openssl md5 executable: /bin/bash - register: _validate_key_modulus + register: _elasticstack_validate_key_modulus changed_when: false failed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" @@ -188,23 +188,23 @@ {{ ('-passin pass:' ~ _validate_pass) if (_validate_pass | default('', true) | length > 0) else '' }} 2>/dev/null | openssl md5 executable: /bin/bash - register: _validate_ec_key_fp + register: _elasticstack_validate_ec_key_fp changed_when: false failed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false no_log: true - when: _validate_key_modulus.rc != 0 + when: _elasticstack_validate_key_modulus.rc != 0 - name: "Get EC cert public key fingerprint — {{ _validate_service }}" ansible.builtin.shell: cmd: set -o pipefail && openssl x509 -in {{ _validate_cert_path }} -noout -pubkey 2>/dev/null | openssl md5 executable: /bin/bash - register: _validate_ec_cert_fp + register: _elasticstack_validate_ec_cert_fp changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - when: _validate_key_modulus.rc != 0 + when: _elasticstack_validate_key_modulus.rc != 0 - name: "Fail if RSA key does not match certificate — {{ _validate_service }}" ansible.builtin.fail: @@ -213,8 +213,8 @@ Certificate: {{ _validate_cert_path }} Key: {{ lookup('vars', _validate_key_fact) }} when: - - _validate_key_modulus.rc == 0 - - _validate_cert_modulus.stdout != _validate_key_modulus.stdout + - _elasticstack_validate_key_modulus.rc == 0 + - _elasticstack_validate_cert_modulus.stdout != _elasticstack_validate_key_modulus.stdout - name: "Fail if EC key does not match certificate — {{ _validate_service }}" ansible.builtin.fail: @@ -223,21 +223,21 @@ Certificate: {{ _validate_cert_path }} Key: {{ lookup('vars', _validate_key_fact) }} when: - - _validate_key_modulus.rc != 0 - - _validate_ec_key_fp is defined - - _validate_ec_key_fp.rc == 0 - - _validate_ec_cert_fp.stdout != _validate_ec_key_fp.stdout + - _elasticstack_validate_key_modulus.rc != 0 + - _elasticstack_validate_ec_key_fp is defined + - _elasticstack_validate_ec_key_fp.rc == 0 + - _elasticstack_validate_ec_cert_fp.stdout != _elasticstack_validate_ec_key_fp.stdout # --- SAN hostname check (warn only) --- - name: "Check SAN for hostname match — {{ _validate_service }}" - when: _validate_pem_probe.rc == 0 + when: _elasticstack_validate_pem_probe.rc == 0 block: - name: "Extract SAN from certificate — {{ _validate_service }}" ansible.builtin.shell: cmd: openssl x509 -in {{ _validate_cert_path }} -noout -ext subjectAltName 2>/dev/null || true executable: /bin/bash - register: _validate_san_output + register: _elasticstack_validate_san_output changed_when: false delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false @@ -247,10 +247,10 @@ msg: >- Certificate SAN does not include this node's hostname ({{ ansible_facts.hostname }}) or IP. This may cause TLS verification failures. - SAN: {{ _validate_san_output.stdout | default('(none)') }} + SAN: {{ _elasticstack_validate_san_output.stdout | default('(none)') }} when: - - _validate_san_output.stdout | default('') | length > 0 - - ansible_facts.hostname not in _validate_san_output.stdout - - ansible_facts.fqdn not in _validate_san_output.stdout - - inventory_hostname not in _validate_san_output.stdout - - (ansible_facts.all_ipv4_addresses | default([]) | select('search', _validate_san_output.stdout) | list | length) == 0 + - _elasticstack_validate_san_output.stdout | default('') | length > 0 + - ansible_facts.hostname not in _elasticstack_validate_san_output.stdout + - ansible_facts.fqdn not in _elasticstack_validate_san_output.stdout + - inventory_hostname not in _elasticstack_validate_san_output.stdout + - (ansible_facts.all_ipv4_addresses | default([]) | select('search', _elasticstack_validate_san_output.stdout) | list | length) == 0 diff --git a/roles/elasticstack/tasks/packages.yml b/roles/elasticstack/tasks/packages.yml index 00edae2a..d125280f 100644 --- a/roles/elasticstack/tasks/packages.yml +++ b/roles/elasticstack/tasks/packages.yml @@ -20,8 +20,8 @@ - python3-cryptography - python3-packaging - openssl - register: _security_packages_install - until: _security_packages_install is success + register: _elasticstack_security_packages_install + until: _elasticstack_security_packages_install is success retries: 3 delay: 10 tags: diff --git a/roles/kibana/tasks/kibana-security.yml b/roles/kibana/tasks/kibana-security.yml index 45dbb0b6..0afcaf75 100644 --- a/roles/kibana/tasks/kibana-security.yml +++ b/roles/kibana/tasks/kibana-security.yml @@ -292,7 +292,7 @@ loop: - encryption_key - savedobjects_encryption_key - register: _preserved_enckeys + register: _kibana_preserved_enckeys - name: Restore encryption keys preserved during CA renewal ansible.builtin.copy: @@ -300,7 +300,7 @@ dest: "{{ elasticstack_ca_dir }}/{{ item.item }}" remote_src: true mode: "0600" - loop: "{{ _preserved_enckeys.results }}" + loop: "{{ _kibana_preserved_enckeys.results }}" when: item.stat.exists loop_control: label: "{{ item.item }}" @@ -309,7 +309,7 @@ ansible.builtin.file: path: "/tmp/.kibana_preserved_{{ item.item }}" state: absent - loop: "{{ _preserved_enckeys.results }}" + loop: "{{ _kibana_preserved_enckeys.results }}" when: item.stat.exists loop_control: label: "{{ item.item }}" diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml index 50a346e6..fe4d1be9 100644 --- a/roles/kibana/tasks/main.yml +++ b/roles/kibana/tasks/main.yml @@ -33,7 +33,7 @@ 'kibana' + ((elasticstack_versionseparator + elasticstack_version | - string ) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | + string) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | replace(' ', '') }} - name: Install Kibana - rpm - full stack diff --git a/roles/logstash/tasks/main.yml b/roles/logstash/tasks/main.yml index 2882cef9..eed663ae 100644 --- a/roles/logstash/tasks/main.yml +++ b/roles/logstash/tasks/main.yml @@ -102,7 +102,7 @@ 'logstash' + ((elasticstack_versionseparator + elasticstack_version | - string ) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | + string) if (elasticstack_version is defined and elasticstack_version | length > 0 and elasticstack_version != 'latest') else '') | replace(' ', '') }} when: diff --git a/roles/repos/tasks/debian.yml b/roles/repos/tasks/debian.yml index 022af53e..e59c2265 100644 --- a/roles/repos/tasks/debian.yml +++ b/roles/repos/tasks/debian.yml @@ -5,8 +5,8 @@ - gpg - gpg-agent state: present - register: _gpg_install_deb - until: _gpg_install_deb is success + register: _repos_gpg_install_deb + until: _repos_gpg_install_deb is success retries: 3 delay: 10 @@ -15,8 +15,8 @@ url: "{{ elasticstack_repo_key }}" dest: /usr/share/keyrings/elasticsearch.asc mode: "0644" - register: _elastic_key_download - until: _elastic_key_download is success + register: _repos_elastic_key_download + until: _repos_elastic_key_download is success retries: 3 delay: 10 diff --git a/roles/repos/tasks/redhat.yml b/roles/repos/tasks/redhat.yml index dd0d2204..e0e0ad3a 100644 --- a/roles/repos/tasks/redhat.yml +++ b/roles/repos/tasks/redhat.yml @@ -7,8 +7,8 @@ ansible.builtin.package: name: gnupg state: present - register: _gpg_install_rh - until: _gpg_install_rh is success + register: _repos_gpg_install_rh + until: _repos_gpg_install_rh is success retries: 3 delay: 10 @@ -31,20 +31,20 @@ - name: Install crypto-policies-scripts ansible.builtin.package: name: crypto-policies-scripts - register: _crypto_policies_install - until: _crypto_policies_install is success + register: _repos_crypto_policies_install + until: _repos_crypto_policies_install is success retries: 3 delay: 10 - name: Check current crypto policy ansible.builtin.command: update-crypto-policies --show - register: _crypto_policy + register: _repos_crypto_policy changed_when: false - name: Set Crypto policies to legacy ansible.builtin.command: "update-crypto-policies --set LEGACY" changed_when: true - when: _crypto_policy.stdout != 'LEGACY' + when: _repos_crypto_policy.stdout != 'LEGACY' - name: Ensure Elastic repository key is available (RedHat) ansible.builtin.rpm_key: From af32d8c3906b266f302dec0987f1240b78310ee2 Mon Sep 17 00:00:00 2001 From: Sam Crauwels Date: Tue, 10 Mar 2026 15:21:46 +0100 Subject: [PATCH 2/5] Fix name[prefix] violations and tighten ansible-lint config Add the required filename_stem prefix to all 404 task names across 31 files so they pass the name[prefix] rule at production profile. Remove jinja[spacing], risky-shell-pipe, and var-naming[no-role-prefix] from warn_list since those are now clean too. --- .ansible-lint | 3 - roles/beats/tasks/auditbeat.yml | 8 +- roles/beats/tasks/beat-install.yml | 14 +- roles/beats/tasks/beats-security.yml | 60 +++--- roles/beats/tasks/filebeat.yml | 14 +- roles/beats/tasks/metricbeat.yml | 10 +- roles/beats/tasks/restart_and_verify_beat.yml | 10 +- .../tasks/elasticsearch-keystore.yml | 46 ++--- .../tasks/elasticsearch-parameters.yml | 2 +- .../tasks/elasticsearch-rolling-upgrade.yml | 58 +++--- .../tasks/elasticsearch-security.yml | 178 +++++++++--------- .../restart_and_verify_elasticsearch.yml | 10 +- .../elasticsearch/tasks/wait_for_instance.yml | 10 +- roles/elasticstack/tasks/certs/ca_ensure.yml | 4 +- .../tasks/certs/ca_extract_public.yml | 2 +- .../elasticstack/tasks/certs/cert_backup.yml | 6 +- .../tasks/certs/cert_check_expiry.yml | 12 +- .../tasks/certs/cert_distribute.yml | 4 +- .../tasks/certs/cert_expiry_warn.yml | 10 +- .../tasks/certs/cert_generate.yml | 2 +- .../tasks/certs/cert_validate.yml | 58 +++--- .../tasks/elasticstack-passwords.yml | 8 +- .../tasks/elasticstack-versions.yml | 2 +- roles/elasticstack/tasks/fetch_password.yml | 4 +- roles/elasticstack/tasks/packages.yml | 6 +- roles/kibana/tasks/kibana-keystore.yml | 40 ++-- roles/kibana/tasks/kibana-security.yml | 80 ++++---- .../tasks/restart_and_verify_kibana.yml | 12 +- roles/logstash/tasks/logstash-security.yml | 86 ++++----- .../tasks/restart_and_verify_logstash.yml | 10 +- roles/repos/tasks/debian.yml | 8 +- roles/repos/tasks/redhat.yml | 18 +- 32 files changed, 396 insertions(+), 399 deletions(-) diff --git a/.ansible-lint b/.ansible-lint index 11654659..e7ab3912 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -9,14 +9,11 @@ exclude_paths: warn_list: - experimental - - jinja[spacing] - key-order[task] - name[casing] - name[missing] - package-latest - - risky-shell-pipe - schema[meta] - - var-naming[no-role-prefix] skip_list: - command-instead-of-module diff --git a/roles/beats/tasks/auditbeat.yml b/roles/beats/tasks/auditbeat.yml index 5af5a17a..b0615cf3 100644 --- a/roles/beats/tasks/auditbeat.yml +++ b/roles/beats/tasks/auditbeat.yml @@ -1,11 +1,11 @@ --- -- name: Install Auditbeat +- name: auditbeat | Install Auditbeat ansible.builtin.include_tasks: beat-install.yml vars: _beat_name: auditbeat -- name: Configure Auditbeat +- name: auditbeat | Configure Auditbeat ansible.builtin.template: src: auditbeat.yml.j2 dest: /etc/auditbeat/auditbeat.yml @@ -19,7 +19,7 @@ - beats_auditbeat_configuration - beats_configuration -- name: Setup Auditbeat in Elasticsearch +- name: auditbeat | Setup Auditbeat in Elasticsearch ansible.builtin.shell: > /usr/bin/auditbeat setup --pipelines --index-management && /usr/bin/auditbeat version > /etc/auditbeat/pipeline_created @@ -33,7 +33,7 @@ - beats_auditbeat_setup | bool - beats_auditbeat_output == "elasticsearch" -- name: Start Auditbeat +- name: auditbeat | Start Auditbeat ansible.builtin.service: name: auditbeat state: started diff --git a/roles/beats/tasks/beat-install.yml b/roles/beats/tasks/beat-install.yml index b208e34b..7d585e44 100644 --- a/roles/beats/tasks/beat-install.yml +++ b/roles/beats/tasks/beat-install.yml @@ -5,7 +5,7 @@ # Required variables: # _beat_name: Beat name (e.g. "filebeat", "auditbeat", "metricbeat") -- name: "Construct exact package name for {{ _beat_name }}" +- name: beat-install | Construct exact package name for {{ _beat_name }} ansible.builtin.set_fact: beats_package: >- {{ @@ -15,7 +15,7 @@ string) if (elasticstack_version is defined and elasticstack_version | length > 0) else '') | replace(' ', '') }} -- name: "Install rpm full-stack package for {{ _beat_name }}" +- name: beat-install | Install rpm full-stack package for {{ _beat_name }} ansible.builtin.package: name: "{{ beats_package }}" enablerepo: @@ -29,7 +29,7 @@ - ansible_facts.os_family == "RedHat" - elasticstack_full_stack | bool -- name: "Install rpm standalone package for {{ _beat_name }}" +- name: beat-install | Install rpm standalone package for {{ _beat_name }} ansible.builtin.package: name: "{{ beats_package }}" register: beats_install_rpm @@ -41,7 +41,7 @@ - ansible_facts.os_family == "RedHat" - not elasticstack_full_stack | bool -- name: "Install deb package for {{ _beat_name }}" +- name: beat-install | Install deb package for {{ _beat_name }} ansible.builtin.package: name: "{{ beats_package }}" register: beats_install_deb @@ -52,7 +52,7 @@ when: - ansible_facts.os_family == "Debian" -- name: "Install latest rpm full-stack package for {{ _beat_name }}" # noqa: package-latest +- name: beat-install | "Install latest rpm full-stack package for {{ _beat_name }}" # noqa: package-latest ansible.builtin.package: name: "{{ _beat_name }}" state: latest @@ -69,7 +69,7 @@ - ansible_facts.os_family == "RedHat" - elasticstack_full_stack | bool -- name: "Install latest rpm standalone package for {{ _beat_name }}" # noqa: package-latest +- name: beat-install | "Install latest rpm standalone package for {{ _beat_name }}" # noqa: package-latest ansible.builtin.package: name: "{{ _beat_name }}" state: latest @@ -84,7 +84,7 @@ - ansible_facts.os_family == "RedHat" - not elasticstack_full_stack | bool -- name: "Install latest deb package for {{ _beat_name }}" # noqa: package-latest +- name: beat-install | "Install latest deb package for {{ _beat_name }}" # noqa: package-latest ansible.builtin.package: name: "{{ _beat_name }}" state: latest diff --git a/roles/beats/tasks/beats-security.yml b/roles/beats/tasks/beats-security.yml index 061374e5..4ecf94ad 100644 --- a/roles/beats/tasks/beats-security.yml +++ b/roles/beats/tasks/beats-security.yml @@ -4,16 +4,16 @@ # External certificates # ============================================================ -- name: Handle external certificates +- name: beats-security | Handle external certificates when: beats_cert_source == 'external' block: # -- Detect content mode vs file mode -- - - name: Detect content mode for Beats + - name: beats-security | Detect content mode for Beats ansible.builtin.set_fact: _beats_content_mode: "{{ beats_tls_certificate_content | default('', true) | length > 0 }}" - - name: Validate certificate is provided + - name: beats-security | Validate certificate is provided ansible.builtin.assert: that: - (beats_tls_certificate_file | length > 0) or @@ -23,13 +23,13 @@ beats_tls_certificate_file or beats_tls_certificate_content # -- Content mode: set format to PEM -- - - name: Set format facts for content mode + - name: beats-security | Set format facts for content mode ansible.builtin.set_fact: _beats_cert_format: pem _beats_ca_extracted: false when: _beats_content_mode | bool - - name: Check for CA chain in certificate content + - name: beats-security | Check for CA chain in certificate content ansible.builtin.set_fact: _beats_ca_extracted: true when: @@ -38,7 +38,7 @@ beats_tls_certificate_content | default('') | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 - - name: Validate key content is provided (content mode) + - name: beats-security | Validate key content is provided (content mode) ansible.builtin.assert: that: - beats_tls_key_content | default('', true) | length > 0 @@ -48,7 +48,7 @@ when: _beats_content_mode | bool # -- File mode: validate and detect format -- - - name: Validate Beats certificate (file mode) + - name: beats-security | Validate Beats certificate (file mode) ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_validate.yml" vars: @@ -64,7 +64,7 @@ # -- Deploy certificates -- - - name: Create certificate directory + - name: beats-security | Create certificate directory ansible.builtin.file: path: /etc/beats/certs state: directory @@ -72,7 +72,7 @@ group: root mode: "0700" - - name: Write Beats certificate (from content) + - name: beats-security | Write Beats certificate (from content) ansible.builtin.copy: content: "{{ beats_tls_certificate_content }}" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.crt" @@ -85,7 +85,7 @@ - Restart Auditbeat - Restart Metricbeat - - name: Copy Beats certificate (from file) + - name: beats-security | Copy Beats certificate (from file) ansible.builtin.copy: src: "{{ beats_tls_certificate_file }}" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.crt" @@ -99,7 +99,7 @@ - Restart Auditbeat - Restart Metricbeat - - name: Write Beats key (from content) + - name: beats-security | Write Beats key (from content) ansible.builtin.copy: content: "{{ beats_tls_key_content }}" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.key" @@ -112,7 +112,7 @@ - Restart Auditbeat - Restart Metricbeat - - name: Copy Beats key (from file) + - name: beats-security | Copy Beats key (from file) ansible.builtin.copy: src: "{{ _beats_resolved_key }}" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.key" @@ -130,7 +130,7 @@ # -- CA certificate -- - - name: Write CA certificate (from content) + - name: beats-security | Write CA certificate (from content) ansible.builtin.copy: content: "{{ beats_tls_ca_content }}" dest: /etc/beats/certs/ca.crt @@ -143,7 +143,7 @@ - Restart Auditbeat - Restart Metricbeat - - name: Copy CA certificate (from file) + - name: beats-security | Copy CA certificate (from file) ansible.builtin.copy: src: "{{ beats_tls_ca_file }}" dest: /etc/beats/certs/ca.crt @@ -160,7 +160,7 @@ - Restart Metricbeat # Extract CA chain from the already-deployed cert on the node - - name: Read CA chain from PEM bundle + - name: beats-security | Read CA chain from PEM bundle ansible.builtin.shell: cmd: >- awk '/-----BEGIN CERTIFICATE-----/{n++} n>1' @@ -172,7 +172,7 @@ - beats_tls_ca_content | default('', true) | length == 0 - _beats_ca_extracted | bool - - name: Write extracted CA chain + - name: beats-security | Write extracted CA chain ansible.builtin.copy: content: "{{ _beats_extracted_ca_chain.stdout }}\n" dest: /etc/beats/certs/ca.crt @@ -192,11 +192,11 @@ # Auto-generated certificates (elasticsearch_ca) # ============================================================ -- name: Handle auto-generated certificates +- name: beats-security | Handle auto-generated certificates when: beats_cert_source == 'elasticsearch_ca' block: # -- Check Beats certificate expiry (PEM format) -- - - name: Check Beats certificate expiry + - name: beats-security | Check Beats certificate expiry ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_check_expiry.yml" apply: @@ -213,7 +213,7 @@ - renew_beats_cert # -- Backup Beats certs on node -- - - name: Backup Beats certs on node + - name: beats-security | Backup Beats certs on node ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -229,7 +229,7 @@ - renew_beats_cert # -- Backup Beats cert on CA host -- - - name: Backup Beats cert on CA host + - name: beats-security | Backup Beats cert on CA host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -245,7 +245,7 @@ - renew_beats_cert # -- Backup Beats cert on Ansible controller -- - - name: Backup Beats cert on Ansible controller + - name: beats-security | Backup Beats cert on Ansible controller ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -262,7 +262,7 @@ - renew_beats_cert # -- Create Beats certificate directory (Beats-specific path/perms) -- - - name: Create certificate directory + - name: beats-security | Create certificate directory ansible.builtin.file: path: /etc/beats/certs state: directory @@ -275,7 +275,7 @@ - renew_beats_cert # -- Generate PEM certificate for Beats -- - - name: Generate Beats certificate + - name: beats-security | Generate Beats certificate ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_generate.yml" apply: @@ -297,7 +297,7 @@ - renew_beats_cert # -- Distribute PEM cert (zip) to Beats node (kept inline, zip requires unarchive) -- - - name: Fetch certificate zip from CA host to controller + - name: beats-security | Fetch certificate zip from CA host to controller ansible.builtin.fetch: src: "{{ elasticstack_ca_dir }}/{{ ansible_facts.hostname }}-beats.zip" dest: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ ansible_facts.hostname }}-beats.zip" @@ -308,7 +308,7 @@ - renew_ca - renew_beats_cert - - name: Extract certificate on Beats node + - name: beats-security | Extract certificate on Beats node ansible.builtin.unarchive: src: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ ansible_facts.hostname }}-beats.zip" dest: "/etc/beats/certs/" @@ -320,7 +320,7 @@ - renew_ca - renew_beats_cert - - name: Copy certificate to standard location + - name: beats-security | Copy certificate to standard location ansible.builtin.copy: src: "/etc/beats/certs/{{ ansible_facts.hostname }}/{{ ansible_facts.hostname }}.crt" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.crt" @@ -337,7 +337,7 @@ - renew_ca - renew_beats_cert - - name: Copy key to standard location + - name: beats-security | Copy key to standard location ansible.builtin.copy: src: "/etc/beats/certs/{{ ansible_facts.hostname }}/{{ ansible_facts.hostname }}.key" dest: "/etc/beats/certs/{{ inventory_hostname }}-beats.key" @@ -355,7 +355,7 @@ - renew_beats_cert # -- Distribute CA certificate to Beats nodes -- - - name: Distribute CA certificate to Beats nodes + - name: beats-security | Distribute CA certificate to Beats nodes ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -383,7 +383,7 @@ # ============================================================ # -- Fetch Beats password -- -- name: Fetch Beats password +- name: beats-security | Fetch Beats password ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: @@ -391,7 +391,7 @@ _password_fact: beats_writer_password # -- Certificate expiry warning -- -- name: Check Beats certificate expiry warning +- name: beats-security | Check Beats certificate expiry warning ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_expiry_warn.yml" vars: diff --git a/roles/beats/tasks/filebeat.yml b/roles/beats/tasks/filebeat.yml index 17a5dc97..6a95f0c6 100644 --- a/roles/beats/tasks/filebeat.yml +++ b/roles/beats/tasks/filebeat.yml @@ -1,11 +1,11 @@ --- -- name: Install Filebeat +- name: filebeat | Install Filebeat ansible.builtin.include_tasks: beat-install.yml vars: _beat_name: filebeat -- name: Configure Filebeat +- name: filebeat | Configure Filebeat ansible.builtin.template: src: filebeat.yml.j2 dest: /etc/filebeat/filebeat.yml @@ -19,7 +19,7 @@ - beats_filebeat_configuration - beats_configuration -- name: Configure modules +- name: filebeat | Configure modules when: beats_filebeat_modules is defined tags: - configuration @@ -27,13 +27,13 @@ - beats_configuration block: - - name: Enable modules + - name: filebeat | Enable modules ansible.builtin.command: "filebeat modules enable {{ item }}" args: creates: "/etc/filebeat/modules.d/{{ item }}.yml" loop: "{{ beats_filebeat_modules }}" - - name: Enable System module + - name: filebeat | Enable System module ansible.builtin.template: src: filebeat-system.yml.j2 dest: /etc/filebeat/modules.d/system.yml @@ -43,7 +43,7 @@ when: - elasticstack_release | int >= 8 - - name: Enable Ingest Pipelines + - name: filebeat | Enable Ingest Pipelines ansible.builtin.shell: > /usr/bin/filebeat setup --pipelines && /usr/bin/filebeat version > /etc/filebeat/{{ item }}_pipeline_created @@ -53,7 +53,7 @@ notify: - Restart Filebeat -- name: Start Filebeat +- name: filebeat | Start Filebeat ansible.builtin.service: name: filebeat state: started diff --git a/roles/beats/tasks/metricbeat.yml b/roles/beats/tasks/metricbeat.yml index d0324d64..31b7e21a 100644 --- a/roles/beats/tasks/metricbeat.yml +++ b/roles/beats/tasks/metricbeat.yml @@ -1,11 +1,11 @@ --- -- name: Install Metricbeat +- name: metricbeat | Install Metricbeat ansible.builtin.include_tasks: beat-install.yml vars: _beat_name: metricbeat -- name: Configure Metricbeat +- name: metricbeat | Configure Metricbeat ansible.builtin.template: src: metricbeat.yml.j2 dest: /etc/metricbeat/metricbeat.yml @@ -19,14 +19,14 @@ - beats_metricbeat_configuration - beats_configuration -- name: Enable modules +- name: metricbeat | Enable modules ansible.builtin.command: "/usr/bin/metricbeat modules enable {{ item }}" args: creates: "/etc/metricbeat/modules.d/{{ item }}.yml" loop: "{{ beats_metricbeat_modules }}" when: beats_metricbeat_modules is defined -- name: Enable Ingest Pipelines +- name: metricbeat | Enable Ingest Pipelines ansible.builtin.shell: > /usr/bin/metricbeat setup --pipelines --index-management && /usr/bin/metricbeat version > /etc/metricbeat/pipelines_created @@ -40,7 +40,7 @@ - beats_metricbeat_modules is defined - beats_metricbeat_output == "elasticsearch" -- name: Start Metricbeat +- name: metricbeat | Start Metricbeat ansible.builtin.service: name: metricbeat state: started diff --git a/roles/beats/tasks/restart_and_verify_beat.yml b/roles/beats/tasks/restart_and_verify_beat.yml index 87e3f8f2..3d7fa0b4 100644 --- a/roles/beats/tasks/restart_and_verify_beat.yml +++ b/roles/beats/tasks/restart_and_verify_beat.yml @@ -1,13 +1,13 @@ --- -- name: "Restart and verify beat — {{ _beat_service_name }}" # noqa: name[template] +- name: restart_and_verify_beat | "Restart and verify beat — {{ _beat_service_name }}" # noqa: name[template] block: - - name: "Restart beat service — {{ _beat_service_name }}" # noqa: name[template] + - name: restart_and_verify_beat | "Restart beat service — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.service: name: "{{ _beat_service_name }}" state: restarted - - name: "Verify beat service is running — {{ _beat_service_name }}" # noqa: name[template] + - name: restart_and_verify_beat | "Verify beat service is running — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.systemd: name: "{{ _beat_service_name }}" register: _beats_service_state @@ -16,13 +16,13 @@ delay: 3 rescue: - - name: "Get recent journal output — {{ _beat_service_name }}" # noqa: name[template] + - name: restart_and_verify_beat | "Get recent journal output — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.command: cmd: "journalctl -u {{ _beat_service_name }} --no-pager -n 50" register: _beats_journal changed_when: false - - name: "Fail with startup diagnostics — {{ _beat_service_name }}" # noqa: name[template] + - name: restart_and_verify_beat | "Fail with startup diagnostics — {{ _beat_service_name }}" # noqa: name[template] ansible.builtin.fail: msg: | {{ _beat_service_name }} failed to start. diff --git a/roles/elasticsearch/tasks/elasticsearch-keystore.yml b/roles/elasticsearch/tasks/elasticsearch-keystore.yml index db437568..d9b242ba 100644 --- a/roles/elasticsearch/tasks/elasticsearch-keystore.yml +++ b/roles/elasticsearch/tasks/elasticsearch-keystore.yml @@ -1,16 +1,16 @@ --- -- name: Create keystore +- name: elasticsearch-keystore | Create keystore ansible.builtin.command: /usr/share/elasticsearch/bin/elasticsearch-keystore create args: creates: /etc/elasticsearch/elasticsearch.keystore -- name: Check for bootstrap password +- name: elasticsearch-keystore | Check for bootstrap password ansible.builtin.command: /usr/share/elasticsearch/bin/elasticsearch-keystore list changed_when: false register: elasticsearch_keystore -- name: Remove autoconfiguration password hash +- name: elasticsearch-keystore | Remove autoconfiguration password hash ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove autoconfiguration.password_hash @@ -19,7 +19,7 @@ notify: - Restart Elasticsearch -- name: Get current bootstrap password +- name: elasticsearch-keystore | Get current bootstrap password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show bootstrap.password @@ -29,7 +29,7 @@ no_log: true ignore_errors: "{{ ansible_check_mode }}" -- name: Set bootstrap password +- name: elasticsearch-keystore | Set bootstrap password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -x -f 'bootstrap.password' @@ -49,7 +49,7 @@ # For external P12: set passwords from http passphrase variable # For external PEM: skip entirely (no keystore needed) -- name: Get xpack.security.http.ssl.keystore.secure_password +- name: elasticsearch-keystore | Get xpack.security.http.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.http.ssl.keystore.secure_password @@ -62,7 +62,7 @@ no_log: true changed_when: false -- name: Set xpack.security.http.ssl.keystore.secure_password +- name: elasticsearch-keystore | Set xpack.security.http.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.http.ssl.keystore.secure_password' @@ -84,7 +84,7 @@ notify: - Restart Elasticsearch -- name: Remove xpack.security.http.ssl.keystore.secure_password +- name: elasticsearch-keystore | Remove xpack.security.http.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove xpack.security.http.ssl.keystore.secure_password @@ -96,7 +96,7 @@ notify: - Restart Elasticsearch -- name: Get xpack.security.http.ssl.truststore.secure_password +- name: elasticsearch-keystore | Get xpack.security.http.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.http.ssl.truststore.secure_password @@ -109,7 +109,7 @@ no_log: true changed_when: false -- name: Set xpack.security.http.ssl.truststore.secure_password +- name: elasticsearch-keystore | Set xpack.security.http.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.http.ssl.truststore.secure_password' @@ -131,7 +131,7 @@ notify: - Restart Elasticsearch -- name: Remove xpack.security.http.ssl.truststore.secure_password +- name: elasticsearch-keystore | Remove xpack.security.http.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove xpack.security.http.ssl.truststore.secure_password @@ -145,7 +145,7 @@ # --- Transport SSL keystore/truststore passwords --- -- name: Get xpack.security.transport.ssl.keystore.secure_password +- name: elasticsearch-keystore | Get xpack.security.transport.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.transport.ssl.keystore.secure_password @@ -158,7 +158,7 @@ no_log: true changed_when: false -- name: Set xpack.security.transport.ssl.keystore.secure_password +- name: elasticsearch-keystore | Set xpack.security.transport.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.transport.ssl.keystore.secure_password' @@ -180,7 +180,7 @@ notify: - Restart Elasticsearch -- name: Remove xpack.security.transport.ssl.keystore.secure_password +- name: elasticsearch-keystore | Remove xpack.security.transport.ssl.keystore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove xpack.security.transport.ssl.keystore.secure_password @@ -192,7 +192,7 @@ notify: - Restart Elasticsearch -- name: Get xpack.security.transport.ssl.truststore.secure_password +- name: elasticsearch-keystore | Get xpack.security.transport.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.transport.ssl.truststore.secure_password @@ -205,7 +205,7 @@ no_log: true changed_when: false -- name: Set xpack.security.transport.ssl.truststore.secure_password +- name: elasticsearch-keystore | Set xpack.security.transport.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.transport.ssl.truststore.secure_password' @@ -227,7 +227,7 @@ notify: - Restart Elasticsearch -- name: Remove xpack.security.transport.ssl.truststore.secure_password +- name: elasticsearch-keystore | Remove xpack.security.transport.ssl.truststore.secure_password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore remove xpack.security.transport.ssl.truststore.secure_password @@ -241,7 +241,7 @@ # --- PEM key passphrase in keystore for external PEM certs --- -- name: Get xpack.security.transport.ssl.secure_key_passphrase +- name: elasticsearch-keystore | Get xpack.security.transport.ssl.secure_key_passphrase ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.transport.ssl.secure_key_passphrase @@ -254,7 +254,7 @@ no_log: true ignore_errors: "{{ ansible_check_mode }}" -- name: Set xpack.security.transport.ssl.secure_key_passphrase +- name: elasticsearch-keystore | Set xpack.security.transport.ssl.secure_key_passphrase ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.transport.ssl.secure_key_passphrase' @@ -272,7 +272,7 @@ notify: - Restart Elasticsearch -- name: Get xpack.security.http.ssl.secure_key_passphrase +- name: elasticsearch-keystore | Get xpack.security.http.ssl.secure_key_passphrase ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore show xpack.security.http.ssl.secure_key_passphrase @@ -286,7 +286,7 @@ no_log: true ignore_errors: "{{ ansible_check_mode }}" -- name: Set xpack.security.http.ssl.secure_key_passphrase +- name: elasticsearch-keystore | Set xpack.security.http.ssl.secure_key_passphrase ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-keystore add -f -x 'xpack.security.http.ssl.secure_key_passphrase' @@ -306,12 +306,12 @@ notify: - Restart Elasticsearch -- name: Check keystore exists +- name: elasticsearch-keystore | Check keystore exists ansible.builtin.stat: path: /etc/elasticsearch/elasticsearch.keystore register: _elasticsearch_keystore_file -- name: Ensure keystore permissions +- name: elasticsearch-keystore | Ensure keystore permissions ansible.builtin.file: path: /etc/elasticsearch/elasticsearch.keystore owner: root diff --git a/roles/elasticsearch/tasks/elasticsearch-parameters.yml b/roles/elasticsearch/tasks/elasticsearch-parameters.yml index bdda69a0..4d307ace 100644 --- a/roles/elasticsearch/tasks/elasticsearch-parameters.yml +++ b/roles/elasticsearch/tasks/elasticsearch-parameters.yml @@ -1,5 +1,5 @@ --- -- name: Warn when security is disabled +- name: elasticsearch-parameters | Warn when security is disabled ansible.builtin.debug: msg: >- Elasticsearch security is disabled. TLS, authentication, and RBAC will not be configured. diff --git a/roles/elasticsearch/tasks/elasticsearch-rolling-upgrade.yml b/roles/elasticsearch/tasks/elasticsearch-rolling-upgrade.yml index a6b77cfd..fe7690ce 100644 --- a/roles/elasticsearch/tasks/elasticsearch-rolling-upgrade.yml +++ b/roles/elasticsearch/tasks/elasticsearch-rolling-upgrade.yml @@ -12,20 +12,20 @@ # For now we support upgrade only for clusters with security enabled # If you positively need support for safely upgrading clusters without security, # feel free to open an issue at https://github.com/Oddly/ansible-collection-elasticstack/issues -- name: Set connection protocol to https +- name: elasticsearch-rolling-upgrade | Set connection protocol to https ansible.builtin.set_fact: elasticsearch_http_protocol: "https" -- name: Check for running Elasticsearch service +- name: elasticsearch-rolling-upgrade | Check for running Elasticsearch service ansible.builtin.systemd: name: elasticsearch register: elasticsearch_running -- name: Update stopped services right away +- name: elasticsearch-rolling-upgrade | Update stopped services right away when: - elasticsearch_running.status.ActiveState == "inactive" block: - - name: Update stopped Elasticsearch - rpm with managed repositories + - name: elasticsearch-rolling-upgrade | Update stopped Elasticsearch - rpm with managed repositories ansible.builtin.package: name: "{{ elasticsearch_package }}" enablerepo: @@ -34,18 +34,18 @@ - ansible_facts.os_family == "RedHat" - elasticstack_full_stack | bool - - name: Update stopped Elasticsearch - deb or unmanaged repositories rpm + - name: elasticsearch-rolling-upgrade | Update stopped Elasticsearch - deb or unmanaged repositories rpm ansible.builtin.package: name: "{{ elasticsearch_package }}" when: - ansible_facts.os_family == "Debian" or not elasticstack_full_stack | bool -- name: Update single instances without extra caution +- name: elasticsearch-rolling-upgrade | Update single instances without extra caution when: - groups[elasticstack_elasticsearch_group_name] | length == 1 block: - - name: Update single instances without extra caution - deb or unmanaged repositories rpm + - name: elasticsearch-rolling-upgrade | Update single instances without extra caution - deb or unmanaged repositories rpm ansible.builtin.package: name: "{{ elasticsearch_package }}" when: @@ -54,7 +54,7 @@ notify: - Restart Elasticsearch - - name: Update single instances without extra caution - rpm with managed repositories + - name: elasticsearch-rolling-upgrade | Update single instances without extra caution - rpm with managed repositories ansible.builtin.package: name: "{{ elasticsearch_package }}" enablerepo: @@ -66,7 +66,7 @@ - Restart Elasticsearch -- name: Be careful about upgrade when Elasticsearch is running +- name: elasticsearch-rolling-upgrade | Be careful about upgrade when Elasticsearch is running when: - elasticsearch_running.status.ActiveState == "active" - groups[elasticstack_elasticsearch_group_name] | length > 1 @@ -78,7 +78,7 @@ # During cross-major-version rolling upgrades (8→9), an old-version node may # be permanently unable to reach the new master — that's OK, we'll skip the # pre-upgrade cluster operations and proceed directly to stop/upgrade/start. - - name: Wait for Elasticsearch cluster API to be responsive + - name: elasticsearch-rolling-upgrade | Wait for Elasticsearch cluster API to be responsive ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/health" method: GET @@ -95,11 +95,11 @@ no_log: "{{ elasticstack_no_log }}" changed_when: false - - name: Set cluster reachability flag + - name: elasticsearch-rolling-upgrade | Set cluster reachability flag ansible.builtin.set_fact: _elasticsearch_cluster_reachable: "{{ (elasticsearch_responsive.status | default(0)) == 200 }}" - - name: Warn if cluster API is unreachable (cross-major upgrade) + - name: elasticsearch-rolling-upgrade | Warn if cluster API is unreachable (cross-major upgrade) ansible.builtin.debug: msg: >- Cluster API returned {{ elasticsearch_responsive.status | default('unknown') }} @@ -109,7 +109,7 @@ # Usually we should not need this step. It's only there to recover from broken upgrade plays # Without this step the cluster would never recover and the play would always fail - - name: Enable shard allocation for the cluster + - name: elasticsearch-rolling-upgrade | Enable shard allocation for the cluster ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings" method: PUT @@ -128,7 +128,7 @@ # this step is key!!! Don't restart more nodes # until all shards have completed recovery - - name: Wait for cluster health to return to green + - name: elasticsearch-rolling-upgrade | Wait for cluster health to return to green ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/health" method: GET @@ -148,7 +148,7 @@ # shards that will come back when the node restarts. Use "primaries" rather # than "none" per the official upgrade docs — primaries still need to be # assigned for indexing to continue during the upgrade. - - name: Disable shard allocation for the cluster + - name: elasticsearch-rolling-upgrade | Disable shard allocation for the cluster ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings" method: PUT @@ -168,7 +168,7 @@ # Put ML into upgrade mode so jobs are suspended gracefully and model # state is saved. This is idempotent — calling it when already in # upgrade mode or when ML is disabled is a no-op (returns 200). - - name: Set ML upgrade mode + - name: elasticsearch-rolling-upgrade | Set ML upgrade mode ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_ml/set_upgrade_mode?enabled=true" method: POST @@ -184,7 +184,7 @@ - _elasticsearch_cluster_reachable | bool - elasticsearch_ml_enabled | bool - - name: Flush indices to speed up shard recovery + - name: elasticsearch-rolling-upgrade | Flush indices to speed up shard recovery ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_flush" method: POST @@ -197,14 +197,14 @@ no_log: "{{ elasticstack_no_log }}" when: _elasticsearch_cluster_reachable | bool - - name: Warn if flush failed + - name: elasticsearch-rolling-upgrade | Warn if flush failed ansible.builtin.debug: msg: "Flush returned status {{ elasticsearch_flush_response.status | default('unknown') }}: {{ elasticsearch_flush_response.msg | default('') }}" when: - _elasticsearch_cluster_reachable | bool - (elasticsearch_flush_response.status | default(0)) != 200 - - name: Shutdown elasticsearch service + - name: elasticsearch-rolling-upgrade | Shutdown elasticsearch service ansible.builtin.service: name: elasticsearch enabled: true @@ -212,7 +212,7 @@ when: - not elasticsearch_unsafe_upgrade_restart | bool - - name: Update Elasticsearch - rpm with managed repositories + - name: elasticsearch-rolling-upgrade | Update Elasticsearch - rpm with managed repositories ansible.builtin.package: name: "{{ elasticsearch_package }}" enablerepo: @@ -221,14 +221,14 @@ - ansible_facts.os_family == "RedHat" - elasticstack_full_stack | bool - - name: Update Elasticsearch - deb or unmanaged repositories rpm + - name: elasticsearch-rolling-upgrade | Update Elasticsearch - deb or unmanaged repositories rpm ansible.builtin.package: name: "{{ elasticsearch_package }}" when: - ansible_facts.os_family == "Debian" or not elasticstack_full_stack | bool - - name: Start elasticsearch + - name: elasticsearch-rolling-upgrade | Start elasticsearch ansible.builtin.service: name: elasticsearch enabled: true @@ -237,7 +237,7 @@ - elasticsearch_running.status.ActiveState == "active" - not elasticsearch_unsafe_upgrade_restart | bool - - name: Restart elasticsearch (fast, for non-prod) + - name: elasticsearch-rolling-upgrade | Restart elasticsearch (fast, for non-prod) ansible.builtin.service: name: elasticsearch enabled: true @@ -246,14 +246,14 @@ - elasticsearch_running.status.ActiveState == "active" - elasticsearch_unsafe_upgrade_restart | bool - - name: Wait for elasticsearch node to come back up if it was stopped + - name: elasticsearch-rolling-upgrade | Wait for elasticsearch node to come back up if it was stopped ansible.builtin.wait_for: host: "{{ elasticsearch_api_host }}" port: "{{ elasticstack_elasticsearch_http_port }}" delay: 30 timeout: 600 - - name: Confirm the node joins the cluster + - name: elasticsearch-rolling-upgrade | Confirm the node joins the cluster ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cat/nodes?h=name" method: GET @@ -269,7 +269,7 @@ no_log: "{{ elasticstack_no_log }}" changed_when: false - - name: Enable shard allocation for the cluster + - name: elasticsearch-rolling-upgrade | Enable shard allocation for the cluster ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings" method: PUT @@ -285,7 +285,7 @@ delay: 30 no_log: "{{ elasticstack_no_log }}" - - name: Wait for cluster health to return to yellow or green + - name: elasticsearch-rolling-upgrade | Wait for cluster health to return to yellow or green ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/health" method: GET @@ -301,7 +301,7 @@ changed_when: false # Take ML out of upgrade mode so jobs resume on the upgraded node. - - name: Disable ML upgrade mode + - name: elasticsearch-rolling-upgrade | Disable ML upgrade mode ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_ml/set_upgrade_mode?enabled=false" method: POST @@ -318,6 +318,6 @@ - _elasticsearch_ml_upgrade_mode is defined - (_elasticsearch_ml_upgrade_mode.status | default(0)) == 200 - - name: Record that rolling upgrade performed its own restart + - name: elasticsearch-rolling-upgrade | Record that rolling upgrade performed its own restart ansible.builtin.set_fact: _elasticsearch_rolling_upgrade_performed: true diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index f8551ac4..8e04f94e 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -13,7 +13,7 @@ # External certificates # ============================================================ -- name: Validate certificate source setting +- name: elasticsearch-security | Validate certificate source setting ansible.builtin.assert: that: - elasticsearch_cert_source in ['elasticsearch_ca', 'external'] @@ -21,7 +21,7 @@ Invalid elasticsearch_cert_source '{{ elasticsearch_cert_source }}'. Must be 'elasticsearch_ca' or 'external'. -- name: Handle external certificates +- name: elasticsearch-security | Handle external certificates when: elasticsearch_cert_source == 'external' block: @@ -29,14 +29,14 @@ # Content mode: PEM strings in variables (always PEM format) # File mode: paths to cert files (PEM or P12 auto-detected) - - name: Detect content mode for transport layer + - name: elasticsearch-security | Detect content mode for transport layer ansible.builtin.set_fact: _elasticsearch_transport_content_mode: "{{ elasticsearch_transport_tls_certificate_content | default('', true) | length > 0 }}" _elasticsearch_http_content_mode: >- {{ (elasticsearch_http_tls_certificate_content | default('', true) | length > 0) or (elasticsearch_transport_tls_certificate_content | default('', true) | length > 0) }} - - name: Validate transport certificate is provided + - name: elasticsearch-security | Validate transport certificate is provided ansible.builtin.assert: that: - (elasticsearch_transport_tls_certificate | length > 0) or @@ -47,7 +47,7 @@ # -- Resolve effective HTTP variables (content takes precedence, then falls back to transport) -- - - name: Resolve effective HTTP cert variables + - name: elasticsearch-security | Resolve effective HTTP cert variables ansible.builtin.set_fact: _elasticsearch_effective_http_cert: "{{ elasticsearch_http_tls_certificate | default(elasticsearch_transport_tls_certificate, true) }}" _elasticsearch_effective_http_key: "{{ elasticsearch_http_tls_key | default(elasticsearch_transport_tls_key, true) }}" @@ -61,10 +61,10 @@ # ---- Content mode: PEM strings in variables ---- - - name: Handle content-mode certificates (transport) + - name: elasticsearch-security | Handle content-mode certificates (transport) when: _elasticsearch_transport_content_mode | bool block: - - name: Validate transport key content is provided + - name: elasticsearch-security | Validate transport key content is provided ansible.builtin.assert: that: - elasticsearch_transport_tls_key_content | default('', true) | length > 0 @@ -72,30 +72,30 @@ Content mode requires both certificate and key content. Set elasticsearch_transport_tls_key_content. - - name: Set transport format facts for content mode + - name: elasticsearch-security | Set transport format facts for content mode ansible.builtin.set_fact: _elasticsearch_transport_cert_format: pem _elasticsearch_transport_ca_extracted: false - - name: Check for CA chain in transport content + - name: elasticsearch-security | Check for CA chain in transport content ansible.builtin.set_fact: _elasticsearch_transport_ca_extracted: true when: >- elasticsearch_transport_tls_certificate_content | default('') | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 - - name: Handle content-mode certificates (HTTP) + - name: elasticsearch-security | Handle content-mode certificates (HTTP) when: - _elasticsearch_http_content_mode | bool - elasticsearch_http_security | bool block: - - name: Set HTTP format facts for content mode + - name: elasticsearch-security | Set HTTP format facts for content mode ansible.builtin.set_fact: _elasticsearch_http_cert_format: pem # ---- File mode: paths to cert files ---- - - name: Validate transport certificate (file mode) + - name: elasticsearch-security | Validate transport certificate (file mode) ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_validate.yml" vars: @@ -109,7 +109,7 @@ _validate_ca_extracted_fact: _elasticsearch_transport_ca_extracted when: not (_elasticsearch_transport_content_mode | bool) - - name: Validate HTTP certificate (file mode) + - name: elasticsearch-security | Validate HTTP certificate (file mode) ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_validate.yml" vars: @@ -125,14 +125,14 @@ - not (_elasticsearch_http_content_mode | bool) - elasticsearch_http_security | bool - - name: Default HTTP format to transport format when HTTP security disabled + - name: elasticsearch-security | Default HTTP format to transport format when HTTP security disabled ansible.builtin.set_fact: _elasticsearch_http_cert_format: "{{ _elasticsearch_transport_cert_format }}" when: not (elasticsearch_http_security | bool) # ---- Deploy certificates (both modes converge here) ---- - - name: Create certificate directory + - name: elasticsearch-security | Create certificate directory ansible.builtin.file: state: directory path: /etc/elasticsearch/certs @@ -142,7 +142,7 @@ # -- Transport cert/key -- - - name: Write transport certificate (from content) + - name: elasticsearch-security | Write transport certificate (from content) ansible.builtin.copy: content: "{{ elasticsearch_transport_tls_certificate_content }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-transport.crt" @@ -151,7 +151,7 @@ mode: "0640" when: _elasticsearch_transport_content_mode | bool - - name: Copy transport certificate (from file) + - name: elasticsearch-security | Copy transport certificate (from file) ansible.builtin.copy: src: "{{ elasticsearch_transport_tls_certificate }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-transport{{ '.p12' if _elasticsearch_transport_cert_format == 'p12' else '.crt' }}" @@ -161,7 +161,7 @@ remote_src: "{{ elasticsearch_tls_remote_src }}" when: not (_elasticsearch_transport_content_mode | bool) - - name: Write transport key (from content) + - name: elasticsearch-security | Write transport key (from content) ansible.builtin.copy: content: "{{ elasticsearch_transport_tls_key_content }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-transport.key" @@ -170,7 +170,7 @@ mode: "0640" when: _elasticsearch_transport_content_mode | bool - - name: Copy transport key (from file, PEM only) + - name: elasticsearch-security | Copy transport key (from file, PEM only) ansible.builtin.copy: src: "{{ _elasticsearch_transport_resolved_key }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-transport.key" @@ -184,7 +184,7 @@ # -- HTTP cert/key (falls back to transport if not set separately) -- - - name: Write HTTP certificate (from content) + - name: elasticsearch-security | Write HTTP certificate (from content) ansible.builtin.copy: content: "{{ _elasticsearch_effective_http_cert_content }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-http.crt" @@ -195,7 +195,7 @@ - _elasticsearch_http_content_mode | bool - elasticsearch_http_security | bool - - name: Copy HTTP certificate (from file) + - name: elasticsearch-security | Copy HTTP certificate (from file) ansible.builtin.copy: src: "{{ _elasticsearch_effective_http_cert }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-http{{ '.p12' if _elasticsearch_http_cert_format == 'p12' else '.crt' }}" @@ -207,7 +207,7 @@ - not (_elasticsearch_http_content_mode | bool) - elasticsearch_http_security | bool - - name: Write HTTP key (from content) + - name: elasticsearch-security | Write HTTP key (from content) ansible.builtin.copy: content: "{{ _elasticsearch_effective_http_key_content }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-http.key" @@ -218,7 +218,7 @@ - _elasticsearch_http_content_mode | bool - elasticsearch_http_security | bool - - name: Copy HTTP key (from file, PEM only) + - name: elasticsearch-security | Copy HTTP key (from file, PEM only) ansible.builtin.copy: src: "{{ _elasticsearch_http_resolved_key }}" dest: "/etc/elasticsearch/certs/{{ inventory_hostname }}-http.key" @@ -233,7 +233,7 @@ # -- CA certificate -- - - name: Write CA certificate (from content) + - name: elasticsearch-security | Write CA certificate (from content) ansible.builtin.copy: content: "{{ elasticsearch_tls_ca_certificate_content }}" dest: /etc/elasticsearch/certs/ca.crt @@ -242,7 +242,7 @@ mode: "0640" when: elasticsearch_tls_ca_certificate_content | default('', true) | length > 0 - - name: Copy CA certificate (from file) + - name: elasticsearch-security | Copy CA certificate (from file) ansible.builtin.copy: src: "{{ elasticsearch_tls_ca_certificate }}" dest: /etc/elasticsearch/certs/ca.crt @@ -256,7 +256,7 @@ # Extract CA chain from the already-deployed transport cert on the node. # Uses copy+content for idempotency (only writes when content changes). - - name: Read CA chain from PEM bundle (transport cert) + - name: elasticsearch-security | Read CA chain from PEM bundle (transport cert) ansible.builtin.shell: cmd: >- awk '/-----BEGIN CERTIFICATE-----/{n++} n>1' @@ -268,7 +268,7 @@ - elasticsearch_tls_ca_certificate_content | default('', true) | length == 0 - _elasticsearch_transport_ca_extracted | bool - - name: Write extracted CA chain + - name: elasticsearch-security | Write extracted CA chain ansible.builtin.copy: content: "{{ _elasticsearch_extracted_ca_chain.stdout }}\n" dest: /etc/elasticsearch/certs/ca.crt @@ -280,7 +280,7 @@ - elasticsearch_tls_ca_certificate_content | default('', true) | length == 0 - _elasticsearch_transport_ca_extracted | bool - - name: Set effective CA availability fact + - name: elasticsearch-security | Set effective CA availability fact ansible.builtin.set_fact: _elasticsearch_external_has_ca: >- {{ (elasticsearch_tls_ca_certificate | length > 0) or @@ -289,7 +289,7 @@ # -- Remove stale auto-generated P12 files from a previous elasticsearch_ca deployment -- - - name: Find stale auto-generated P12 certificate files + - name: elasticsearch-security | Find stale auto-generated P12 certificate files ansible.builtin.find: paths: /etc/elasticsearch/certs patterns: "*.p12" @@ -298,7 +298,7 @@ - "{{ inventory_hostname }}-http.p12" register: _elasticsearch_stale_p12_files - - name: Remove stale auto-generated P12 files + - name: elasticsearch-security | Remove stale auto-generated P12 files ansible.builtin.file: path: "{{ item.path }}" state: absent @@ -309,18 +309,18 @@ # -- Keystore setup for external certs -- - - name: Import Tasks elasticsearch-keystore.yml + - name: elasticsearch-security | Import Tasks elasticsearch-keystore.yml ansible.builtin.import_tasks: elasticsearch-keystore.yml # ============================================================ # Auto-generated certificates (elasticsearch_ca) # ============================================================ -- name: Handle auto-generated certificates +- name: elasticsearch-security | Handle auto-generated certificates when: elasticsearch_cert_source == 'elasticsearch_ca' block: # -- Check CA expiry on CA host -- - - name: Check CA certificate expiry + - name: elasticsearch-security | Check CA certificate expiry ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_check_expiry.yml" apply: @@ -339,7 +339,7 @@ # -- Stop Logstash for CA renewal (orchestration, kept inline) -- # Use default([]) + length > 0 for group checks (Ansible 2.20 compatibility) - - name: Stop Logstash + - name: elasticsearch-security | Stop Logstash ansible.builtin.service: name: logstash state: stopped @@ -355,7 +355,7 @@ # The CA backup removes the entire elasticstack_ca_dir, which also # contains Kibana encryption key files. If those are regenerated, # Kibana can't decrypt its existing saved objects and enters 503. - - name: Check for Kibana encryption keys before CA backup + - name: elasticsearch-security | Check for Kibana encryption keys before CA backup ansible.builtin.stat: path: "{{ elasticstack_ca_dir }}/{{ item }}" loop: @@ -368,7 +368,7 @@ tags: - renew_ca - - name: Preserve Kibana encryption keys before CA backup + - name: elasticsearch-security | Preserve Kibana encryption keys before CA backup ansible.builtin.copy: src: "{{ elasticstack_ca_dir }}/{{ item.item }}" dest: "/tmp/.kibana_preserved_{{ item.item }}" @@ -386,7 +386,7 @@ - renew_ca # -- Backup CA directory on CA host -- - - name: Backup CA directory on CA host + - name: elasticsearch-security | Backup CA directory on CA host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -402,7 +402,7 @@ - renew_ca # -- Backup CA cert on Ansible controller -- - - name: Backup CA cert on Ansible controller + - name: elasticsearch-security | Backup CA cert on Ansible controller ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -419,7 +419,7 @@ - renew_ca # -- Check ES node certificate expiry -- - - name: Check Elasticsearch certificate expiry + - name: elasticsearch-security | Check Elasticsearch certificate expiry ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_check_expiry.yml" apply: @@ -436,7 +436,7 @@ - renew_es_cert # -- Backup ES certs on the node -- - - name: Backup Elasticsearch certs on node + - name: elasticsearch-security | Backup Elasticsearch certs on node ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -452,7 +452,7 @@ - renew_es_cert # -- Backup ES cert on CA host -- - - name: Backup Elasticsearch cert on CA host + - name: elasticsearch-security | Backup Elasticsearch cert on CA host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -468,7 +468,7 @@ - renew_es_cert # -- Backup ES cert on Ansible controller -- - - name: Backup Elasticsearch cert on Ansible controller + - name: elasticsearch-security | Backup Elasticsearch cert on Ansible controller ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -485,11 +485,11 @@ - renew_es_cert # -- Keystore setup (kept inline, import_tasks) -- - - name: Import Tasks elasticsearch-keystore.yml + - name: elasticsearch-security | Import Tasks elasticsearch-keystore.yml ansible.builtin.import_tasks: elasticsearch-keystore.yml # -- Create CA on CA host -- - - name: Ensure CA exists on CA host + - name: elasticsearch-security | Ensure CA exists on CA host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/ca_ensure.yml" apply: @@ -504,7 +504,7 @@ - renew_es_cert # -- Generate node certificates on CA host -- - - name: Generate Elasticsearch node certificate + - name: elasticsearch-security | Generate Elasticsearch node certificate ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_generate.yml" apply: @@ -527,7 +527,7 @@ - renew_es_cert # -- Extract CA public certificate -- - - name: Extract CA public certificate + - name: elasticsearch-security | Extract CA public certificate ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/ca_extract_public.yml" apply: @@ -544,7 +544,7 @@ - renew_ca # -- Create ES certificate directory (ES-specific path/perms, kept inline) -- - - name: Create certificate directory + - name: elasticsearch-security | Create certificate directory ansible.builtin.file: state: directory path: /etc/elasticsearch/certs @@ -557,7 +557,7 @@ - renew_es_cert # -- Distribute CA certificate to ES nodes -- - - name: Distribute CA certificate to Elasticsearch nodes + - name: elasticsearch-security | Distribute CA certificate to Elasticsearch nodes ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -579,7 +579,7 @@ - renew_es_cert # -- Distribute node certificate to ES nodes -- - - name: Distribute node certificate to Elasticsearch nodes + - name: elasticsearch-security | Distribute node certificate to Elasticsearch nodes ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -604,7 +604,7 @@ # Post-certificate tasks (shared by both cert sources) # ============================================================ -- name: Start Elasticsearch for security tasks +- name: elasticsearch-security | Start Elasticsearch for security tasks ansible.builtin.service: name: elasticsearch state: started @@ -612,23 +612,23 @@ register: elasticsearch_freshstart_security when: not ansible_check_mode -- name: Wait for all instances to start +- name: elasticsearch-security | Wait for all instances to start ansible.builtin.include_tasks: wait_for_instance.yml loop: "{{ groups[elasticstack_elasticsearch_group_name] }}" -- name: Restart if Elasticsearch was already running +- name: elasticsearch-security | Restart if Elasticsearch was already running when: - not elasticsearch_freshstart.changed | bool - not elasticsearch_freshstart_security.changed | bool block: - - name: Force all notified handlers to run at this point, not waiting for normal sync points + - name: elasticsearch-security | Force all notified handlers to run at this point, not waiting for normal sync points ansible.builtin.meta: flush_handlers tags: - certificates - renew_ca - renew_es_cert - - name: Wait for all instances to start + - name: elasticsearch-security | Wait for all instances to start ansible.builtin.include_tasks: wait_for_instance.yml loop: "{{ groups[elasticstack_elasticsearch_group_name] }}" tags: @@ -636,21 +636,21 @@ - renew_ca - renew_es_cert -- name: Check for passwords being set +- name: elasticsearch-security | Check for passwords being set ansible.builtin.stat: path: "{{ elasticstack_initial_passwords }}" delegate_to: "{{ elasticstack_ca_host }}" register: elasticsearch_passwords_file -- name: Setting elasticsearch_http_protocol +- name: elasticsearch-security | Setting elasticsearch_http_protocol ansible.builtin.set_fact: elasticsearch_http_protocol: "https" when: elasticsearch_http_security -- name: Runtime cluster setup (requires running Elasticsearch) +- name: elasticsearch-security | Runtime cluster setup (requires running Elasticsearch) when: not ansible_check_mode block: - - name: Check for API with bootstrap password + - name: elasticsearch-security | Check for API with bootstrap password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}" user: elastic @@ -670,13 +670,13 @@ # If the bootstrap password doesn't work, a previous setup-passwords run # may have partially completed (native realm initialized but passwords # file never written). Reset the elastic user password to recover. - - name: Recover elastic user after interrupted security setup + - name: elasticsearch-security | Recover elastic user after interrupted security setup when: - not elasticsearch_passwords_file.stat.exists | bool - elasticsearch_api_status_bootstrap is failed - inventory_hostname == elasticstack_ca_host block: - - name: Reset elastic user password via elasticsearch-reset-password + - name: elasticsearch-security | Reset elastic user password via elasticsearch-reset-password ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -b -s @@ -684,7 +684,7 @@ changed_when: true no_log: "{{ elasticstack_no_log }}" - - name: Verify API access with recovered password + - name: elasticsearch-security | Verify API access with recovered password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}" user: elastic @@ -697,7 +697,7 @@ delay: 5 no_log: "{{ elasticstack_no_log }}" - - name: Write recovered password to initial_passwords file + - name: elasticsearch-security | Write recovered password to initial_passwords file ansible.builtin.copy: dest: "{{ elasticstack_initial_passwords }}" content: | @@ -711,13 +711,13 @@ # Re-stat outside the block so the variable is always a stat result. # When a block is skipped, Ansible clobbers register variables inside it # with {skipped: true} dicts, losing the original .stat attributes. - - name: Re-stat passwords file after recovery attempt + - name: elasticsearch-security | Re-stat passwords file after recovery attempt ansible.builtin.stat: path: "{{ elasticstack_initial_passwords }}" delegate_to: "{{ elasticstack_ca_host }}" register: elasticsearch_passwords_file - - name: Fail if bootstrap password check failed and recovery was not attempted + - name: elasticsearch-security | Fail if bootstrap password check failed and recovery was not attempted ansible.builtin.fail: msg: >- Bootstrap password authentication failed and this is not the CA host, @@ -731,7 +731,7 @@ # We need this check twice. One to wait for the API to be actually available. And a second time to # check the actual return code. Should not cause a huge delay. - - name: Check for cluster status with bootstrap password + - name: elasticsearch-security | Check for cluster status with bootstrap password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/health?pretty" user: elastic @@ -747,7 +747,7 @@ retries: 30 delay: 10 - - name: Fetch Elastic password from file + - name: elasticsearch-security | Fetch Elastic password from file ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: @@ -762,7 +762,7 @@ # actually been applied to ES yet. Clear it so the API check below # is safely skipped (the bootstrap-password check already confirmed # the cluster is reachable). - - name: Clear premature user-defined password on fresh install # noqa: var-naming[no-role-prefix] + - name: elasticsearch-security | Clear premature user-defined password on fresh install # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "" @@ -770,7 +770,7 @@ - not elasticsearch_passwords_file.stat.exists | bool - elasticsearch_elastic_password | default('') | length > 0 - - name: Check for API availability with elastic password + - name: elasticsearch-security | Check for API availability with elastic password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}" user: elastic @@ -792,7 +792,7 @@ # If the auto-generated password returned 401, the user-defined # password was already applied in a previous run. Switch to it. - - name: Switch to user-defined password after prior change # noqa: var-naming[no-role-prefix] + - name: elasticsearch-security | Switch to user-defined password after prior change # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "{{ elasticsearch_elastic_password }}" @@ -802,7 +802,7 @@ - elasticsearch_api_status is defined - (elasticsearch_api_status.status | default(0)) == 401 - - name: Verify API availability with user-defined password + - name: elasticsearch-security | Verify API availability with user-defined password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}" user: elastic @@ -820,16 +820,16 @@ retries: 10 delay: 5 - - name: Work around low resources on CI/CD nodes + - name: elasticsearch-security | Work around low resources on CI/CD nodes when: ansible_facts.virtualization_type in ["container", "docker", "lxc"] block: - - name: Remove cache + - name: elasticsearch-security | Remove cache ansible.builtin.shell: cmd: rm -rf /var/cache/* executable: /bin/bash changed_when: false - - name: Set lenient disk watermarks for CI containers + - name: elasticsearch-security | Set lenient disk watermarks for CI containers ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/settings" method: PUT @@ -854,7 +854,7 @@ # We need this check twice. One to wait for the API to be actually available. And a second time to # check the actual return code. Should not cause a huge delay. - - name: Check for cluster status with elastic password + - name: elasticsearch-security | Check for cluster status with elastic password ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_cluster/health?pretty" user: elastic @@ -871,7 +871,7 @@ retries: 20 delay: 10 - - name: Leave a file showing that the cluster is set up + - name: elasticsearch-security | Leave a file showing that the cluster is set up ansible.builtin.template: dest: "{{ elasticsearch_initialized_file }}" src: elasticsearch_initialized.j2 @@ -879,13 +879,13 @@ group: root mode: "0600" - - name: Set var that cluster is set up + - name: elasticsearch-security | Set var that cluster is set up ansible.builtin.set_fact: elasticsearch_cluster_set_up: true # ES 8.x: elasticsearch-setup-passwords creates all built-in user passwords # ES 9.x: setup-passwords is removed; use elasticsearch-reset-password per user - - name: Create initial passwords (ES 8.x) + - name: elasticsearch-security | Create initial passwords (ES 8.x) ansible.builtin.shell: > set -o pipefail; /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto -b > @@ -903,17 +903,17 @@ - elasticstack_release | int < 9 no_log: "{{ elasticstack_no_log }}" - - name: Create initial passwords (ES 9.x) + - name: elasticsearch-security | Create initial passwords (ES 9.x) when: - inventory_hostname == elasticstack_ca_host - elasticstack_release | int >= 9 block: - - name: Check if passwords file already exists + - name: elasticsearch-security | Check if passwords file already exists ansible.builtin.stat: path: "{{ elasticstack_initial_passwords }}" register: _elasticsearch_pw_file_check - - name: Reset built-in user passwords + - name: elasticsearch-security | Reset built-in user passwords ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-reset-password -u {{ item }} -b -s @@ -928,7 +928,7 @@ no_log: "{{ elasticstack_no_log }}" when: not _elasticsearch_pw_file_check.stat.exists - - name: Write passwords file + - name: elasticsearch-security | Write passwords file ansible.builtin.copy: dest: "{{ elasticstack_initial_passwords }}" content: | @@ -942,7 +942,7 @@ no_log: "{{ elasticstack_no_log }}" when: not _elasticsearch_pw_file_check.stat.exists - - name: Set permissions on passwords file + - name: elasticsearch-security | Set permissions on passwords file ansible.builtin.file: path: "{{ elasticstack_initial_passwords }}" owner: root @@ -950,20 +950,20 @@ mode: "0600" when: inventory_hostname == elasticstack_ca_host - - name: Change elastic password to user-defined value + - name: elasticsearch-security | Change elastic password to user-defined value when: - inventory_hostname == elasticstack_ca_host - elasticsearch_elastic_password | default('') | length > 0 - elasticstack_password.stdout | default('') != elasticsearch_elastic_password block: - - name: Fetch auto-generated elastic password + - name: elasticsearch-security | Fetch auto-generated elastic password ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: _password_user: elastic _password_fact: _elasticsearch_initial_password - - name: Change elastic user password via API + - name: elasticsearch-security | Change elastic user password via API ansible.builtin.uri: url: "{{ elasticsearch_http_protocol }}://{{ elasticsearch_api_host }}:{{ elasticstack_elasticsearch_http_port }}/_security/user/elastic/_password" method: POST @@ -977,7 +977,7 @@ status_code: 200 no_log: "{{ elasticstack_no_log }}" - - name: Set elastic password fact to user-defined value # noqa: var-naming[no-role-prefix] + - name: elasticsearch-security | Set elastic password fact to user-defined value # noqa: var-naming[no-role-prefix] ansible.builtin.set_fact: elasticstack_password: stdout: "{{ elasticsearch_elastic_password }}" @@ -986,7 +986,7 @@ # On fresh install the passwords file was just created but elasticstack_password # may still be unset (the shared role couldn't fetch it because the file didn't # exist yet). Fetch now so downstream tasks (cluster settings, watermarks) work. - - name: Fetch elastic password after initial setup + - name: elasticsearch-security | Fetch elastic password after initial setup ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: @@ -998,7 +998,7 @@ # Maybe make sure that Elasticsearch is using the right protocol http(s) to connect, even in newly setup clusters # -- Certificate expiry warnings -- -- name: Check transport certificate expiry warning +- name: elasticsearch-security | Check transport certificate expiry warning ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_expiry_warn.yml" vars: @@ -1019,7 +1019,7 @@ _warn_service_name: "Elasticsearch transport" _warn_cert_source: "{{ elasticsearch_cert_source }}" -- name: Check HTTP certificate expiry warning +- name: elasticsearch-security | Check HTTP certificate expiry warning ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_expiry_warn.yml" vars: diff --git a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml index 6dd99be2..573c7644 100644 --- a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml +++ b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml @@ -1,14 +1,14 @@ --- -- name: Restart and verify Elasticsearch +- name: restart_and_verify_elasticsearch | Restart and verify Elasticsearch block: - - name: Restart Elasticsearch service + - name: restart_and_verify_elasticsearch | Restart Elasticsearch service ansible.builtin.service: name: elasticsearch state: restarted daemon_reload: true - - name: Verify Elasticsearch is running + - name: restart_and_verify_elasticsearch | Verify Elasticsearch is running ansible.builtin.systemd: name: elasticsearch register: _elasticsearch_service_state @@ -17,13 +17,13 @@ delay: 3 rescue: - - name: Get recent Elasticsearch journal output + - name: restart_and_verify_elasticsearch | Get recent Elasticsearch journal output ansible.builtin.command: cmd: journalctl -u elasticsearch --no-pager -n 50 register: _elasticsearch_journal changed_when: false - - name: Fail with Elasticsearch startup diagnostics + - name: restart_and_verify_elasticsearch | Fail with Elasticsearch startup diagnostics ansible.builtin.fail: msg: | Elasticsearch failed to start. diff --git a/roles/elasticsearch/tasks/wait_for_instance.yml b/roles/elasticsearch/tasks/wait_for_instance.yml index 2daa3e73..05922055 100644 --- a/roles/elasticsearch/tasks/wait_for_instance.yml +++ b/roles/elasticsearch/tasks/wait_for_instance.yml @@ -1,13 +1,13 @@ --- -- name: Wait for Elasticsearch to be ready +- name: wait_for_instance | Wait for Elasticsearch to be ready when: not ansible_check_mode tags: - certificates - renew_ca - renew_es_cert block: - - name: Wait for Elasticsearch port with service health check + - name: wait_for_instance | Wait for Elasticsearch port with service health check ansible.builtin.shell: cmd: | set -o pipefail @@ -27,13 +27,13 @@ failed_when: _elasticsearch_wait_result.rc == 2 rescue: - - name: Get recent Elasticsearch journal output + - name: wait_for_instance | Get recent Elasticsearch journal output ansible.builtin.command: cmd: journalctl -u elasticsearch --no-pager -n 50 register: _elasticsearch_wait_journal changed_when: false - - name: Fail with Elasticsearch diagnostics (service crashed) + - name: wait_for_instance | Fail with Elasticsearch diagnostics (service crashed) ansible.builtin.fail: msg: | Elasticsearch service died while waiting for port {{ elasticstack_elasticsearch_http_port }}. @@ -42,7 +42,7 @@ {{ _elasticsearch_wait_journal.stdout }} when: _elasticsearch_wait_result.rc | default(0) == 2 - - name: Fail with Elasticsearch diagnostics (port timeout) + - name: wait_for_instance | Fail with Elasticsearch diagnostics (port timeout) ansible.builtin.fail: msg: | Elasticsearch port {{ elasticstack_elasticsearch_http_port }} did not become available within 600s. diff --git a/roles/elasticstack/tasks/certs/ca_ensure.yml b/roles/elasticstack/tasks/certs/ca_ensure.yml index ecc39a0a..323ab525 100644 --- a/roles/elasticstack/tasks/certs/ca_ensure.yml +++ b/roles/elasticstack/tasks/certs/ca_ensure.yml @@ -11,7 +11,7 @@ # The passphrase is passed on the command line but the task uses no_log # to prevent Ansible from recording it, and the process lifetime is brief. -- name: Create CA directory +- name: certs | ca_ensure | Create CA directory ansible.builtin.file: path: "{{ elasticstack_ca_dir }}" owner: root @@ -23,7 +23,7 @@ - renew_ca - renew_es_cert -- name: Create CA +- name: certs | ca_ensure | Create CA ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-certutil ca --ca-dn "{{ elasticstack_ca_name }}" diff --git a/roles/elasticstack/tasks/certs/ca_extract_public.yml b/roles/elasticstack/tasks/certs/ca_extract_public.yml index 022ec533..ceebe61c 100644 --- a/roles/elasticstack/tasks/certs/ca_extract_public.yml +++ b/roles/elasticstack/tasks/certs/ca_extract_public.yml @@ -7,7 +7,7 @@ # _cert_ca_out: Output path for CA cert (PEM) # _cert_pass: Passphrase for the P12 file -- name: "Extract CA public certificate - {{ _cert_p12_path | basename }}" +- name: certs | ca_extract_public | Extract CA public certificate - {{ _cert_p12_path | basename }} ansible.builtin.shell: cmd: > set -o pipefail && diff --git a/roles/elasticstack/tasks/certs/cert_backup.yml b/roles/elasticstack/tasks/certs/cert_backup.yml index 15bd03d8..a957320d 100644 --- a/roles/elasticstack/tasks/certs/cert_backup.yml +++ b/roles/elasticstack/tasks/certs/cert_backup.yml @@ -9,14 +9,14 @@ # Optional variables: # _backup_become: Whether to use become (default: true unless localhost) -- name: "Check backup path exists - {{ _backup_path }}" +- name: certs | cert_backup | Check backup path exists - {{ _backup_path }} ansible.builtin.stat: path: "{{ _backup_path }}" register: elasticstack_backup_stat delegate_to: "{{ _backup_target if _backup_target != inventory_hostname else omit }}" become: "{{ _backup_become | default(_backup_target != 'localhost') }}" -- name: "Create timestamped backup - {{ _backup_path }}" +- name: certs | cert_backup | Create timestamped backup - {{ _backup_path }} ansible.builtin.copy: src: "{{ _backup_path }}" dest: "{{ _backup_path }}_{{ ansible_facts.date_time.iso8601_micro }}" @@ -27,7 +27,7 @@ become: "{{ _backup_become | default(_backup_target != 'localhost') }}" register: elasticstack_backup_result -- name: "Remove original after backup - {{ _backup_path }}" # noqa: no-handler +- name: certs | cert_backup | "Remove original after backup - {{ _backup_path }}" # noqa: no-handler ansible.builtin.file: path: "{{ _backup_path }}" state: absent diff --git a/roles/elasticstack/tasks/certs/cert_check_expiry.yml b/roles/elasticstack/tasks/certs/cert_check_expiry.yml index 63162ae1..5d0556ec 100644 --- a/roles/elasticstack/tasks/certs/cert_check_expiry.yml +++ b/roles/elasticstack/tasks/certs/cert_check_expiry.yml @@ -16,34 +16,34 @@ # {{ _cert_expiry_fact }}: true if cert expires within buffer # {{ _cert_days_fact }}: days until expiration -- name: Check if certificate exists +- name: certs | cert_check_expiry | Check if certificate exists ansible.builtin.stat: path: "{{ _cert_path }}" register: elasticstack_cert_stat -- name: Check certificate expiry +- name: certs | cert_check_expiry | Check certificate expiry when: elasticstack_cert_stat.stat.exists block: - - name: Get certificate info + - name: certs | cert_check_expiry | Get certificate info oddly.elasticstack.cert_info: path: "{{ _cert_path }}" passphrase: "{{ _cert_pass | default(omit, true) }}" format: "{{ _cert_format | default('p12') }}" register: elasticstack_cert_info - - name: Calculate days until expiration + - name: certs | cert_check_expiry | Calculate days until expiration ansible.builtin.set_fact: "{{ _cert_days_fact | default('elasticstack_cert_expiration_days') }}": >- {{ (((elasticstack_cert_info.not_valid_after | regex_replace('[+-]\d{2}:\d{2}$', '')) | to_datetime('%Y-%m-%d %H:%M:%S')) - (ansible_facts.date_time.date | to_datetime('%Y-%m-%d'))).days }} - - name: "Set certificate expiry fact - {{ _cert_expiry_fact }}" + - name: certs | cert_check_expiry | Set certificate expiry fact - {{ _cert_expiry_fact }} ansible.builtin.set_fact: "{{ _cert_expiry_fact }}": true when: (hostvars[inventory_hostname][_cert_days_fact | default('elasticstack_cert_expiration_days')]) | int <= _cert_buffer_days | int - - name: Show certificate renewal message + - name: certs | cert_check_expiry | Show certificate renewal message ansible.builtin.debug: msg: >- Certificate {{ _cert_path }} will expire in {{ hostvars[inventory_hostname][_cert_days_fact | default('elasticstack_cert_expiration_days')] }} days. diff --git a/roles/elasticstack/tasks/certs/cert_distribute.yml b/roles/elasticstack/tasks/certs/cert_distribute.yml index 0f340775..0d9420ab 100644 --- a/roles/elasticstack/tasks/certs/cert_distribute.yml +++ b/roles/elasticstack/tasks/certs/cert_distribute.yml @@ -11,7 +11,7 @@ # Optional variables: # _cert_notify: Handler name(s) to notify on change (list or string) -- name: "Fetch certificate from CA host - {{ _cert_src | basename }}" +- name: certs | cert_distribute | Fetch certificate from CA host - {{ _cert_src | basename }} ansible.builtin.fetch: src: "{{ _cert_src }}" dest: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ _cert_src | basename }}" @@ -23,7 +23,7 @@ tags: - certificates -- name: "Copy certificate to target node - {{ _cert_src | basename }}" +- name: certs | cert_distribute | Copy certificate to target node - {{ _cert_src | basename }} ansible.builtin.copy: src: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ _cert_src | basename }}" dest: "{{ _cert_dest }}" diff --git a/roles/elasticstack/tasks/certs/cert_expiry_warn.yml b/roles/elasticstack/tasks/certs/cert_expiry_warn.yml index 070d0f2c..7f554d55 100644 --- a/roles/elasticstack/tasks/certs/cert_expiry_warn.yml +++ b/roles/elasticstack/tasks/certs/cert_expiry_warn.yml @@ -11,15 +11,15 @@ # _warn_cert_format 'pem' or 'p12' (default: 'p12') # _warn_cert_source 'elasticsearch_ca' or 'external' (default: 'elasticsearch_ca') -- name: "Check certificate file exists — {{ _warn_service_name }}" +- name: certs | cert_expiry_warn | Check certificate file exists — {{ _warn_service_name }} ansible.builtin.stat: path: "{{ _warn_cert_path }}" register: _elasticstack_warn_cert_file -- name: "Check certificate expiry — {{ _warn_service_name }}" +- name: certs | cert_expiry_warn | Check certificate expiry — {{ _warn_service_name }} when: _elasticstack_warn_cert_file.stat.exists block: - - name: "Read certificate info — {{ _warn_service_name }}" + - name: certs | cert_expiry_warn | Read certificate info — {{ _warn_service_name }} oddly.elasticstack.cert_info: path: "{{ _warn_cert_path }}" passphrase: "{{ _warn_cert_pass | default(omit, true) }}" @@ -27,7 +27,7 @@ register: _elasticstack_warn_cert_result ignore_errors: true # noqa: ignore-errors - - name: "Calculate days until certificate expires — {{ _warn_service_name }}" + - name: certs | cert_expiry_warn | Calculate days until certificate expires — {{ _warn_service_name }} ansible.builtin.set_fact: _elasticstack_warn_days_remaining: >- {{ ((((_elasticstack_warn_cert_result.not_valid_after | regex_replace('[+-]\d{2}:\d{2}$', '')) @@ -35,7 +35,7 @@ | to_datetime('%Y-%m-%d'))).days) }} when: _elasticstack_warn_cert_result is succeeded - - name: "Certificate expires soon — {{ _warn_service_name }}" + - name: certs | cert_expiry_warn | Certificate expires soon — {{ _warn_service_name }} ansible.builtin.debug: msg: >- Certificate {{ _warn_cert_path }} on {{ inventory_hostname }} diff --git a/roles/elasticstack/tasks/certs/cert_generate.yml b/roles/elasticstack/tasks/certs/cert_generate.yml index 8c443b1c..eac08495 100644 --- a/roles/elasticstack/tasks/certs/cert_generate.yml +++ b/roles/elasticstack/tasks/certs/cert_generate.yml @@ -13,7 +13,7 @@ # Optional variables: # _cert_format: "p12" (default) or "pem" -- name: "Generate certificate - {{ _cert_name }}" +- name: certs | cert_generate | Generate certificate - {{ _cert_name }} ansible.builtin.command: > /usr/share/elasticsearch/bin/elasticsearch-certutil cert --ca {{ elasticstack_ca_dir }}/elastic-stack-ca.p12 diff --git a/roles/elasticstack/tasks/certs/cert_validate.yml b/roles/elasticstack/tasks/certs/cert_validate.yml index abe08f3a..bdaa07c4 100644 --- a/roles/elasticstack/tasks/certs/cert_validate.yml +++ b/roles/elasticstack/tasks/certs/cert_validate.yml @@ -13,14 +13,14 @@ # _validate_key_fact Fact name to store resolved key path # _validate_ca_extracted_fact Fact name for whether CA was auto-extracted from chain -- name: "Check certificate file exists — {{ _validate_service }}" +- name: certs | cert_validate | Check certificate file exists — {{ _validate_service }} ansible.builtin.stat: path: "{{ _validate_cert_path }}" register: _elasticstack_validate_cert_stat delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false -- name: "Fail if certificate file missing — {{ _validate_service }}" +- name: certs | cert_validate | Fail if certificate file missing — {{ _validate_service }} ansible.builtin.fail: msg: >- Certificate file not found: {{ _validate_cert_path }} @@ -29,7 +29,7 @@ # --- Format detection --- -- name: "Probe for PEM format — {{ _validate_service }}" +- name: certs | cert_validate | Probe for PEM format — {{ _validate_service }} ansible.builtin.command: cmd: openssl x509 -in {{ _validate_cert_path }} -noout register: _elasticstack_validate_pem_probe @@ -38,7 +38,7 @@ delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false -- name: "Probe for P12 format — {{ _validate_service }}" +- name: certs | cert_validate | Probe for P12 format — {{ _validate_service }} ansible.builtin.command: cmd: >- openssl pkcs12 -in {{ _validate_cert_path }} -noout @@ -51,7 +51,7 @@ no_log: true when: _elasticstack_validate_pem_probe.rc != 0 -- name: "Fail if format unrecognised — {{ _validate_service }}" +- name: certs | cert_validate | Fail if format unrecognised — {{ _validate_service }} ansible.builtin.fail: msg: >- Unsupported certificate format for {{ _validate_service }}. @@ -60,46 +60,46 @@ - _elasticstack_validate_pem_probe.rc != 0 - (_elasticstack_validate_p12_probe.rc | default(1)) != 0 -- name: "Set detected format fact — {{ _validate_service }}" +- name: certs | cert_validate | Set detected format fact — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_format_fact }}": "{{ 'pem' if _elasticstack_validate_pem_probe.rc == 0 else 'p12' }}" # --- PEM key auto-detection --- -- name: "Derive key path from certificate path — {{ _validate_service }}" +- name: certs | cert_validate | Derive key path from certificate path — {{ _validate_service }} when: - (_validate_key_path | default('', true)) | length == 0 - _elasticstack_validate_pem_probe.rc == 0 block: - - name: "Compute derived key path — {{ _validate_service }}" + - name: certs | cert_validate | Compute derived key path — {{ _validate_service }} ansible.builtin.set_fact: _elasticstack_validate_derived_key: "{{ _validate_cert_path | regex_replace('\\.(crt|pem|cert)$', '.key') }}" - - name: "Check derived key exists — {{ _validate_service }}" + - name: certs | cert_validate | Check derived key exists — {{ _validate_service }} ansible.builtin.stat: path: "{{ _elasticstack_validate_derived_key }}" register: _elasticstack_validate_derived_key_stat delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - - name: "Fail if derived key not found — {{ _validate_service }}" + - name: certs | cert_validate | Fail if derived key not found — {{ _validate_service }} ansible.builtin.fail: msg: >- Could not find key file {{ _elasticstack_validate_derived_key }} (derived from cert path). Set the corresponding *_tls_key variable explicitly. when: not _elasticstack_validate_derived_key_stat.stat.exists - - name: "Set resolved key fact — {{ _validate_service }}" + - name: certs | cert_validate | Set resolved key fact — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_key_fact }}": "{{ _elasticstack_validate_derived_key }}" -- name: "Set explicit key fact — {{ _validate_service }}" +- name: certs | cert_validate | Set explicit key fact — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_key_fact }}": "{{ _validate_key_path }}" when: - (_validate_key_path | default('', true)) | length > 0 -- name: "Set empty key fact for P12 — {{ _validate_service }}" +- name: certs | cert_validate | Set empty key fact for P12 — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_key_fact }}": "" when: @@ -107,10 +107,10 @@ # --- PEM CA chain extraction --- -- name: "Check for CA chain in PEM bundle — {{ _validate_service }}" +- name: certs | cert_validate | Check for CA chain in PEM bundle — {{ _validate_service }} when: _elasticstack_validate_pem_probe.rc == 0 block: - - name: "Count PEM blocks in certificate file — {{ _validate_service }}" + - name: certs | cert_validate | Count PEM blocks in certificate file — {{ _validate_service }} ansible.builtin.command: cmd: grep -c 'BEGIN CERTIFICATE' {{ _validate_cert_path }} register: _elasticstack_validate_pem_count @@ -118,18 +118,18 @@ delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - - name: "Set CA extracted fact — {{ _validate_service }}" + - name: certs | cert_validate | Set CA extracted fact — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_ca_extracted_fact }}": "{{ (_elasticstack_validate_pem_count.stdout | int) > 1 }}" -- name: "Set CA extracted fact for P12 — {{ _validate_service }}" +- name: certs | cert_validate | Set CA extracted fact for P12 — {{ _validate_service }} ansible.builtin.set_fact: "{{ _validate_ca_extracted_fact }}": false when: _elasticstack_validate_pem_probe.rc != 0 # --- Certificate expiry check --- -- name: "Check certificate has not expired — {{ _validate_service }}" +- name: certs | cert_validate | Check certificate has not expired — {{ _validate_service }} ansible.builtin.command: cmd: openssl x509 -in {{ _validate_cert_path }} -noout -checkend 0 register: _elasticstack_validate_expiry_check @@ -139,7 +139,7 @@ become: false when: _elasticstack_validate_pem_probe.rc == 0 -- name: "Fail if certificate already expired — {{ _validate_service }}" +- name: certs | cert_validate | Fail if certificate already expired — {{ _validate_service }} ansible.builtin.fail: msg: >- {{ _validate_service }} certificate {{ _validate_cert_path }} has already expired. @@ -150,12 +150,12 @@ # --- PEM key-cert match verification --- -- name: "Verify key matches certificate — {{ _validate_service }}" +- name: certs | cert_validate | Verify key matches certificate — {{ _validate_service }} when: - _elasticstack_validate_pem_probe.rc == 0 - lookup('vars', _validate_key_fact) | length > 0 block: - - name: "Get certificate modulus — {{ _validate_service }}" + - name: certs | cert_validate | Get certificate modulus — {{ _validate_service }} ansible.builtin.shell: cmd: set -o pipefail && openssl x509 -in {{ _validate_cert_path }} -noout -modulus 2>/dev/null | openssl md5 executable: /bin/bash @@ -164,7 +164,7 @@ delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - - name: "Get key modulus — {{ _validate_service }}" + - name: certs | cert_validate | Get key modulus — {{ _validate_service }} ansible.builtin.shell: cmd: >- set -o pipefail && @@ -180,7 +180,7 @@ no_log: true # If RSA modulus failed, try EC key - - name: "Get EC key fingerprint — {{ _validate_service }}" + - name: certs | cert_validate | Get EC key fingerprint — {{ _validate_service }} ansible.builtin.shell: cmd: >- set -o pipefail && @@ -196,7 +196,7 @@ no_log: true when: _elasticstack_validate_key_modulus.rc != 0 - - name: "Get EC cert public key fingerprint — {{ _validate_service }}" + - name: certs | cert_validate | Get EC cert public key fingerprint — {{ _validate_service }} ansible.builtin.shell: cmd: set -o pipefail && openssl x509 -in {{ _validate_cert_path }} -noout -pubkey 2>/dev/null | openssl md5 executable: /bin/bash @@ -206,7 +206,7 @@ become: false when: _elasticstack_validate_key_modulus.rc != 0 - - name: "Fail if RSA key does not match certificate — {{ _validate_service }}" + - name: certs | cert_validate | Fail if RSA key does not match certificate — {{ _validate_service }} ansible.builtin.fail: msg: >- {{ _validate_service }} private key does not match the certificate. @@ -216,7 +216,7 @@ - _elasticstack_validate_key_modulus.rc == 0 - _elasticstack_validate_cert_modulus.stdout != _elasticstack_validate_key_modulus.stdout - - name: "Fail if EC key does not match certificate — {{ _validate_service }}" + - name: certs | cert_validate | Fail if EC key does not match certificate — {{ _validate_service }} ansible.builtin.fail: msg: >- {{ _validate_service }} private key does not match the certificate. @@ -230,10 +230,10 @@ # --- SAN hostname check (warn only) --- -- name: "Check SAN for hostname match — {{ _validate_service }}" +- name: certs | cert_validate | Check SAN for hostname match — {{ _validate_service }} when: _elasticstack_validate_pem_probe.rc == 0 block: - - name: "Extract SAN from certificate — {{ _validate_service }}" + - name: certs | cert_validate | Extract SAN from certificate — {{ _validate_service }} ansible.builtin.shell: cmd: openssl x509 -in {{ _validate_cert_path }} -noout -ext subjectAltName 2>/dev/null || true executable: /bin/bash @@ -242,7 +242,7 @@ delegate_to: "{{ omit if (_validate_remote_src | bool) else 'localhost' }}" become: false - - name: "Warn if SAN does not include this node — {{ _validate_service }}" + - name: certs | cert_validate | Warn if SAN does not include this node — {{ _validate_service }} ansible.builtin.debug: msg: >- Certificate SAN does not include this node's hostname diff --git a/roles/elasticstack/tasks/elasticstack-passwords.yml b/roles/elasticstack/tasks/elasticstack-passwords.yml index ea44b3b5..4ac615de 100644 --- a/roles/elasticstack/tasks/elasticstack-passwords.yml +++ b/roles/elasticstack/tasks/elasticstack-passwords.yml @@ -1,6 +1,6 @@ --- -- name: Use user-defined elastic password +- name: elasticstack-passwords | Use user-defined elastic password ansible.builtin.set_fact: elasticstack_password: stdout: "{{ elasticsearch_elastic_password }}" @@ -8,18 +8,18 @@ when: - elasticsearch_elastic_password | default('') | length > 0 -- name: Fetch elastic password from file +- name: elasticstack-passwords | Fetch elastic password from file when: - elasticsearch_elastic_password | default('') | length == 0 - groups[elasticstack_elasticsearch_group_name] is defined block: - - name: Check for passwords being set + - name: elasticstack-passwords | Check for passwords being set ansible.builtin.stat: path: "{{ elasticstack_initial_passwords }}" delegate_to: "{{ elasticstack_ca_host }}" register: elasticstack_passwords_file - - name: Fetch Elastic password + - name: elasticstack-passwords | Fetch Elastic password ansible.builtin.include_tasks: fetch_password.yml vars: _password_user: elastic diff --git a/roles/elasticstack/tasks/elasticstack-versions.yml b/roles/elasticstack/tasks/elasticstack-versions.yml index 1e90f8e8..b7458b3e 100644 --- a/roles/elasticstack/tasks/elasticstack-versions.yml +++ b/roles/elasticstack/tasks/elasticstack-versions.yml @@ -1,5 +1,5 @@ --- -- name: Gather package facts +- name: elasticstack-versions | Gather package facts ansible.builtin.package_facts: manager: auto diff --git a/roles/elasticstack/tasks/fetch_password.yml b/roles/elasticstack/tasks/fetch_password.yml index 6b664098..280d2369 100644 --- a/roles/elasticstack/tasks/fetch_password.yml +++ b/roles/elasticstack/tasks/fetch_password.yml @@ -8,7 +8,7 @@ # # The result is a shell register dict — use {{ fact_name.stdout }} downstream. -- name: "Fetch password - {{ _password_user }}" +- name: fetch_password | Fetch password - {{ _password_user }} ansible.builtin.shell: > set -o pipefail; grep "PASSWORD {{ _password_user }} " {{ elasticstack_initial_passwords }} | @@ -20,7 +20,7 @@ no_log: "{{ elasticstack_no_log }}" delegate_to: "{{ elasticstack_ca_host }}" -- name: "Register password fact - {{ _password_fact }}" +- name: fetch_password | Register password fact - {{ _password_fact }} ansible.builtin.set_fact: "{{ _password_fact }}": "{{ elasticstack_fetched_password_result }}" no_log: "{{ elasticstack_no_log }}" diff --git a/roles/elasticstack/tasks/packages.yml b/roles/elasticstack/tasks/packages.yml index d125280f..6c4fd8de 100644 --- a/roles/elasticstack/tasks/packages.yml +++ b/roles/elasticstack/tasks/packages.yml @@ -1,19 +1,19 @@ --- -- name: Bootstrap python3-apt for Ansible apt module +- name: packages | Bootstrap python3-apt for Ansible apt module ansible.builtin.raw: apt-get install -y python3-apt changed_when: false when: - ansible_facts.os_family == 'Debian' - ansible_facts.pkg_mgr == 'apt' -- name: Update apt cache. +- name: packages | Update apt cache. ansible.builtin.apt: update_cache: true cache_valid_time: 600 changed_when: false when: ansible_facts.os_family == 'Debian' -- name: Install packages for security tasks +- name: packages | Install packages for security tasks ansible.builtin.package: name: - unzip diff --git a/roles/kibana/tasks/kibana-keystore.yml b/roles/kibana/tasks/kibana-keystore.yml index 7a97d252..04d0a47f 100644 --- a/roles/kibana/tasks/kibana-keystore.yml +++ b/roles/kibana/tasks/kibana-keystore.yml @@ -4,7 +4,7 @@ # --- Keystore password file (when keystore encryption is enabled) --- -- name: Ensure systemd override directory exists +- name: kibana-keystore | Ensure systemd override directory exists ansible.builtin.file: path: /etc/systemd/system/kibana.service.d state: directory @@ -12,7 +12,7 @@ group: root mode: "0755" -- name: Write keystore password file +- name: kibana-keystore | Write keystore password file ansible.builtin.copy: content: "{{ kibana_keystore_password }}" dest: /etc/kibana/.keystore_password @@ -22,13 +22,13 @@ no_log: true when: kibana_keystore_password | length > 0 -- name: Remove keystore password file when not needed +- name: kibana-keystore | Remove keystore password file when not needed ansible.builtin.file: path: /etc/kibana/.keystore_password state: absent when: kibana_keystore_password | length == 0 -- name: Configure systemd keystore passphrase file +- name: kibana-keystore | Configure systemd keystore passphrase file ansible.builtin.copy: content: | [Service] @@ -42,7 +42,7 @@ - Restart Kibana when: kibana_keystore_password | length > 0 -- name: Remove systemd keystore passphrase override when not needed +- name: kibana-keystore | Remove systemd keystore passphrase override when not needed ansible.builtin.file: path: /etc/systemd/system/kibana.service.d/keystore-password.conf state: absent @@ -53,7 +53,7 @@ # --- Build environment dict for keystore commands --- -- name: Set keystore command environment +- name: kibana-keystore | Set keystore command environment ansible.builtin.set_fact: _kibana_keystore_env: >- {{ {'KBN_KEYSTORE_PASSPHRASE_FILE': '/etc/kibana/.keystore_password'} @@ -62,13 +62,13 @@ # --- Create keystore --- -- name: Create Kibana keystore +- name: kibana-keystore | Create Kibana keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore create args: creates: /etc/kibana/kibana.keystore when: kibana_keystore_password | length == 0 -- name: Create password-protected Kibana keystore +- name: kibana-keystore | Create password-protected Kibana keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore create --password args: creates: /etc/kibana/kibana.keystore @@ -76,7 +76,7 @@ no_log: true when: kibana_keystore_password | length > 0 -- name: List Kibana keystore entries +- name: kibana-keystore | List Kibana keystore entries ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore list environment: "{{ _kibana_keystore_env }}" changed_when: false @@ -84,7 +84,7 @@ # --- elasticsearch.password --- -- name: Get elasticsearch.password from keystore +- name: kibana-keystore | Get elasticsearch.password from keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore show elasticsearch.password environment: "{{ _kibana_keystore_env }}" when: @@ -95,7 +95,7 @@ changed_when: false ignore_errors: "{{ ansible_check_mode }}" -- name: Set elasticsearch.password in keystore +- name: kibana-keystore | Set elasticsearch.password in keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore add -x -f 'elasticsearch.password' @@ -112,7 +112,7 @@ notify: - Restart Kibana -- name: Remove elasticsearch.password from keystore +- name: kibana-keystore | Remove elasticsearch.password from keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore remove elasticsearch.password @@ -126,7 +126,7 @@ # --- server.ssl.keystore.password --- -- name: Get server.ssl.keystore.password from keystore +- name: kibana-keystore | Get server.ssl.keystore.password from keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore show server.ssl.keystore.password environment: "{{ _kibana_keystore_env }}" when: @@ -137,7 +137,7 @@ changed_when: false ignore_errors: "{{ ansible_check_mode }}" -- name: Set server.ssl.keystore.password in keystore +- name: kibana-keystore | Set server.ssl.keystore.password in keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore add -x -f 'server.ssl.keystore.password' @@ -160,7 +160,7 @@ notify: - Restart Kibana -- name: Remove server.ssl.keystore.password from keystore +- name: kibana-keystore | Remove server.ssl.keystore.password from keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore remove server.ssl.keystore.password @@ -175,7 +175,7 @@ # --- xpack.security.encryptionKey --- -- name: Get xpack.security.encryptionKey from keystore +- name: kibana-keystore | Get xpack.security.encryptionKey from keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore show xpack.security.encryptionKey environment: "{{ _kibana_keystore_env }}" when: @@ -186,7 +186,7 @@ changed_when: false ignore_errors: "{{ ansible_check_mode }}" -- name: Set xpack.security.encryptionKey in keystore +- name: kibana-keystore | Set xpack.security.encryptionKey in keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore add -x -f 'xpack.security.encryptionKey' @@ -205,7 +205,7 @@ # --- xpack.encryptedSavedObjects.encryptionKey --- -- name: Get xpack.encryptedSavedObjects.encryptionKey from keystore +- name: kibana-keystore | Get xpack.encryptedSavedObjects.encryptionKey from keystore ansible.builtin.command: /usr/share/kibana/bin/kibana-keystore show xpack.encryptedSavedObjects.encryptionKey environment: "{{ _kibana_keystore_env }}" when: @@ -216,7 +216,7 @@ changed_when: false ignore_errors: "{{ ansible_check_mode }}" -- name: Set xpack.encryptedSavedObjects.encryptionKey in keystore +- name: kibana-keystore | Set xpack.encryptedSavedObjects.encryptionKey in keystore ansible.builtin.command: > /usr/share/kibana/bin/kibana-keystore add -x -f 'xpack.encryptedSavedObjects.encryptionKey' @@ -235,7 +235,7 @@ # --- Keystore permissions --- -- name: Ensure Kibana keystore permissions +- name: kibana-keystore | Ensure Kibana keystore permissions ansible.builtin.file: path: /etc/kibana/kibana.keystore owner: root diff --git a/roles/kibana/tasks/kibana-security.yml b/roles/kibana/tasks/kibana-security.yml index 0afcaf75..9c38ca4c 100644 --- a/roles/kibana/tasks/kibana-security.yml +++ b/roles/kibana/tasks/kibana-security.yml @@ -4,21 +4,21 @@ # External certificates # ============================================================ -- name: Handle external certificates +- name: kibana-security | Handle external certificates when: kibana_cert_source == 'external' block: # -- Kibana web UI certificate (only when kibana_tls is enabled) -- - - name: Handle Kibana web UI certificates + - name: kibana-security | Handle Kibana web UI certificates when: kibana_tls | bool block: # -- Detect content mode vs file mode -- - - name: Detect content mode for Kibana + - name: kibana-security | Detect content mode for Kibana ansible.builtin.set_fact: _kibana_content_mode: "{{ kibana_tls_certificate_content | default('', true) | length > 0 }}" - - name: Validate certificate is provided + - name: kibana-security | Validate certificate is provided ansible.builtin.assert: that: - (kibana_tls_certificate_file | length > 0) or @@ -28,13 +28,13 @@ kibana_tls_certificate_file or kibana_tls_certificate_content # -- Content mode: set format to PEM -- - - name: Set format facts for content mode + - name: kibana-security | Set format facts for content mode ansible.builtin.set_fact: _kibana_cert_format: pem _kibana_ca_extracted: false when: _kibana_content_mode | bool - - name: Check for CA chain in certificate content + - name: kibana-security | Check for CA chain in certificate content ansible.builtin.set_fact: _kibana_ca_extracted: true when: @@ -43,7 +43,7 @@ kibana_tls_certificate_content | default('') | regex_findall('-----BEGIN CERTIFICATE-----') | length > 1 - - name: Validate key content is provided (content mode) + - name: kibana-security | Validate key content is provided (content mode) ansible.builtin.assert: that: - kibana_tls_key_content | default('', true) | length > 0 @@ -53,7 +53,7 @@ when: _kibana_content_mode | bool # -- File mode: validate and detect format -- - - name: Validate Kibana certificate (file mode) + - name: kibana-security | Validate Kibana certificate (file mode) ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_validate.yml" vars: @@ -68,7 +68,7 @@ when: not (_kibana_content_mode | bool) # -- Ensure cert directory exists before deploying -- - - name: Create certificate directory + - name: kibana-security | Create certificate directory ansible.builtin.file: path: /etc/kibana/certs state: directory @@ -78,7 +78,7 @@ # -- Deploy certificates -- - - name: Write Kibana certificate (from content) + - name: kibana-security | Write Kibana certificate (from content) ansible.builtin.copy: content: "{{ kibana_tls_certificate_content }}" dest: "/etc/kibana/certs/{{ inventory_hostname }}-kibana.crt" @@ -88,7 +88,7 @@ when: _kibana_content_mode | bool notify: Restart Kibana - - name: Copy Kibana certificate (from file) + - name: kibana-security | Copy Kibana certificate (from file) ansible.builtin.copy: src: "{{ kibana_tls_certificate_file }}" dest: "/etc/kibana/certs/{{ inventory_hostname }}-kibana{{ '.p12' if _kibana_cert_format == 'p12' else '.crt' }}" @@ -99,7 +99,7 @@ when: not (_kibana_content_mode | bool) notify: Restart Kibana - - name: Write Kibana key (from content) + - name: kibana-security | Write Kibana key (from content) ansible.builtin.copy: content: "{{ kibana_tls_key_content }}" dest: "/etc/kibana/certs/{{ inventory_hostname }}-kibana.key" @@ -109,7 +109,7 @@ when: _kibana_content_mode | bool notify: Restart Kibana - - name: Copy Kibana key (from file, PEM only) + - name: kibana-security | Copy Kibana key (from file, PEM only) ansible.builtin.copy: src: "{{ _kibana_resolved_key }}" dest: "/etc/kibana/certs/{{ inventory_hostname }}-kibana.key" @@ -124,7 +124,7 @@ # -- CA certificate (always handled, independent of kibana_tls) -- - - name: Create certificate directory + - name: kibana-security | Create certificate directory ansible.builtin.file: path: /etc/kibana/certs state: directory @@ -132,7 +132,7 @@ group: kibana mode: "0750" - - name: Write CA certificate (from content) + - name: kibana-security | Write CA certificate (from content) ansible.builtin.copy: content: "{{ kibana_tls_ca_content }}" dest: /etc/kibana/certs/ca.crt @@ -142,7 +142,7 @@ when: kibana_tls_ca_content | default('', true) | length > 0 notify: Restart Kibana - - name: Copy CA certificate (from file) + - name: kibana-security | Copy CA certificate (from file) ansible.builtin.copy: src: "{{ kibana_tls_ca_file }}" dest: /etc/kibana/certs/ca.crt @@ -156,7 +156,7 @@ notify: Restart Kibana # Extract CA chain from the already-deployed cert on the node - - name: Read CA chain from PEM bundle + - name: kibana-security | Read CA chain from PEM bundle ansible.builtin.shell: cmd: >- awk '/-----BEGIN CERTIFICATE-----/{n++} n>1' @@ -169,7 +169,7 @@ - _kibana_ca_extracted | default(false) | bool - (_kibana_cert_format | default('')) == 'pem' - - name: Write extracted CA chain + - name: kibana-security | Write extracted CA chain ansible.builtin.copy: content: "{{ _kibana_extracted_ca_chain.stdout }}\n" dest: /etc/kibana/certs/ca.crt @@ -183,7 +183,7 @@ - (_kibana_cert_format | default('')) == 'pem' notify: Restart Kibana - - name: Set effective CA availability fact + - name: kibana-security | Set effective CA availability fact ansible.builtin.set_fact: _kibana_has_ca: >- {{ (kibana_tls_ca_file | length > 0) or @@ -195,11 +195,11 @@ # Auto-generated certificates (elasticsearch_ca) # ============================================================ -- name: Handle auto-generated certificates +- name: kibana-security | Handle auto-generated certificates when: kibana_cert_source == 'elasticsearch_ca' block: # -- Check Kibana certificate expiry -- - - name: Check Kibana certificate expiry + - name: kibana-security | Check Kibana certificate expiry ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_check_expiry.yml" apply: @@ -216,7 +216,7 @@ - renew_kibana_cert # -- Backup Kibana certs on node -- - - name: Backup Kibana certs on node + - name: kibana-security | Backup Kibana certs on node ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -232,7 +232,7 @@ - renew_kibana_cert # -- Backup Kibana cert on CA host -- - - name: Backup Kibana cert on CA host + - name: kibana-security | Backup Kibana cert on CA host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -248,7 +248,7 @@ - renew_kibana_cert # -- Backup Kibana cert on Ansible controller -- - - name: Backup Kibana cert on Ansible controller + - name: kibana-security | Backup Kibana cert on Ansible controller ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" apply: @@ -269,7 +269,7 @@ # ============================================================ # -- Encryption keys (Kibana-specific, kept inline) -- -- name: Block for key generation +- name: kibana-security | Block for key generation delegate_to: "{{ elasticstack_ca_host }}" run_once: true tags: @@ -277,7 +277,7 @@ - renew_ca - renew_kibana_cert block: - - name: Ensure CA directory exists for encryption keys + - name: kibana-security | Ensure CA directory exists for encryption keys ansible.builtin.file: path: "{{ elasticstack_ca_dir }}" state: directory @@ -286,7 +286,7 @@ mode: "0700" check_mode: false - - name: Check for preserved encryption keys from CA renewal + - name: kibana-security | Check for preserved encryption keys from CA renewal ansible.builtin.stat: path: "/tmp/.kibana_preserved_{{ item }}" loop: @@ -294,7 +294,7 @@ - savedobjects_encryption_key register: _kibana_preserved_enckeys - - name: Restore encryption keys preserved during CA renewal + - name: kibana-security | Restore encryption keys preserved during CA renewal ansible.builtin.copy: src: "/tmp/.kibana_preserved_{{ item.item }}" dest: "{{ elasticstack_ca_dir }}/{{ item.item }}" @@ -305,7 +305,7 @@ loop_control: label: "{{ item.item }}" - - name: Clean up preserved encryption key copies + - name: kibana-security | Clean up preserved encryption key copies ansible.builtin.file: path: "/tmp/.kibana_preserved_{{ item.item }}" state: absent @@ -314,7 +314,7 @@ loop_control: label: "{{ item.item }}" - - name: Generate encryption key + - name: kibana-security | Generate encryption key ansible.builtin.shell: > set -o pipefail; openssl rand -base64 36 > @@ -325,13 +325,13 @@ changed_when: false check_mode: false - - name: Fetch encryption key + - name: kibana-security | Fetch encryption key ansible.builtin.slurp: src: "{{ elasticstack_ca_dir }}/encryption_key" register: kibana_encryption_key check_mode: false - - name: Generate saved objects encryption key + - name: kibana-security | Generate saved objects encryption key ansible.builtin.shell: > set -o pipefail; openssl rand -base64 36 > @@ -342,18 +342,18 @@ changed_when: false check_mode: false - - name: Fetch saved objects encryption key + - name: kibana-security | Fetch saved objects encryption key ansible.builtin.slurp: src: "{{ elasticstack_ca_dir }}/savedobjects_encryption_key" register: kibana_savedobjects_encryption_key check_mode: false # -- Auto-generated cert tasks (only for elasticsearch_ca) -- -- name: Handle auto-generated Kibana certificate distribution +- name: kibana-security | Handle auto-generated Kibana certificate distribution when: kibana_cert_source == 'elasticsearch_ca' block: # -- Create Kibana certificate directory (Kibana-specific path/perms) -- - - name: Create certificate directory + - name: kibana-security | Create certificate directory ansible.builtin.file: path: /etc/kibana/certs state: directory @@ -366,7 +366,7 @@ - renew_kibana_cert # -- Generate Kibana certificate -- - - name: Generate Kibana certificate + - name: kibana-security | Generate Kibana certificate ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_generate.yml" apply: @@ -387,7 +387,7 @@ - renew_kibana_cert # -- Distribute Kibana certificate -- - - name: Distribute Kibana certificate + - name: kibana-security | Distribute Kibana certificate ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -409,7 +409,7 @@ - renew_kibana_cert # -- Fetch Kibana password -- -- name: Fetch Kibana password +- name: kibana-security | Fetch Kibana password ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: @@ -417,7 +417,7 @@ _password_fact: kibana_password # -- Distribute CA certificate to Kibana nodes -- -- name: Distribute CA certificate to Kibana nodes +- name: kibana-security | Distribute CA certificate to Kibana nodes ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -440,7 +440,7 @@ - renew_kibana_cert # -- Certificate expiry warning -- -- name: Check Kibana certificate expiry warning +- name: kibana-security | Check Kibana certificate expiry warning ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_expiry_warn.yml" vars: diff --git a/roles/kibana/tasks/restart_and_verify_kibana.yml b/roles/kibana/tasks/restart_and_verify_kibana.yml index c50c1a8e..5441d2b9 100644 --- a/roles/kibana/tasks/restart_and_verify_kibana.yml +++ b/roles/kibana/tasks/restart_and_verify_kibana.yml @@ -1,13 +1,13 @@ --- -- name: Restart and verify Kibana +- name: restart_and_verify_kibana | Restart and verify Kibana block: - - name: Restart Kibana service + - name: restart_and_verify_kibana | Restart Kibana service ansible.builtin.service: name: kibana state: restarted - - name: Verify Kibana is running + - name: restart_and_verify_kibana | Verify Kibana is running ansible.builtin.systemd: name: kibana register: _kibana_service_state @@ -15,7 +15,7 @@ retries: 5 delay: 3 - - name: Wait for Kibana HTTP readiness + - name: restart_and_verify_kibana | Wait for Kibana HTTP readiness ansible.builtin.shell: cmd: | HTTP_CODE=$(curl -sk -o /dev/null -w '%{http_code}' http://localhost:5601/api/status 2>/dev/null) || true @@ -30,13 +30,13 @@ changed_when: false rescue: - - name: Get recent Kibana journal output + - name: restart_and_verify_kibana | Get recent Kibana journal output ansible.builtin.command: cmd: journalctl -u kibana --no-pager -n 50 register: _kibana_journal changed_when: false - - name: Fail with Kibana startup diagnostics + - name: restart_and_verify_kibana | Fail with Kibana startup diagnostics ansible.builtin.fail: msg: | Kibana failed to start. diff --git a/roles/logstash/tasks/logstash-security.yml b/roles/logstash/tasks/logstash-security.yml index f7cc1676..f7071b76 100644 --- a/roles/logstash/tasks/logstash-security.yml +++ b/roles/logstash/tasks/logstash-security.yml @@ -1,19 +1,19 @@ --- -- name: Determine certificate management mode +- name: logstash-security | Determine certificate management mode ansible.builtin.set_fact: _logstash_cert_mode: "{{ logstash_cert_source | default('elasticsearch_ca') }}" -- name: Handle external certificates +- name: logstash-security | Handle external certificates when: _logstash_cert_mode == 'external' block: - - name: Validate external certificate paths are provided + - name: logstash-security | Validate external certificate paths are provided ansible.builtin.assert: that: - logstash_tls_certificate_file is defined - logstash_tls_key_file is defined fail_msg: "External cert mode requires logstash_tls_certificate_file and logstash_tls_key_file" - - name: Create certificate directory + - name: logstash-security | Create certificate directory ansible.builtin.file: state: directory path: "{{ logstash_certs_dir }}" @@ -21,7 +21,7 @@ group: logstash mode: "0750" - - name: Copy external certificate + - name: logstash-security | Copy external certificate ansible.builtin.copy: src: "{{ logstash_tls_certificate_file }}" dest: "{{ logstash_certs_dir }}/{{ inventory_hostname }}-server.crt" @@ -32,7 +32,7 @@ notify: - Restart Logstash - - name: Copy external key + - name: logstash-security | Copy external key ansible.builtin.copy: src: "{{ logstash_tls_key_file }}" dest: "{{ logstash_certs_dir }}/{{ inventory_hostname }}-pkcs8.key" @@ -43,7 +43,7 @@ notify: - Restart Logstash - - name: Copy external CA certificate + - name: logstash-security | Copy external CA certificate ansible.builtin.copy: src: "{{ logstash_tls_ca_file }}" dest: "{{ logstash_certs_dir }}/ca.crt" @@ -55,11 +55,11 @@ notify: - Restart Logstash -- name: Handle Elasticsearch CA certificates +- name: logstash-security | Handle Elasticsearch CA certificates when: _logstash_cert_mode == 'elasticsearch_ca' block: # -- Check Logstash certificate expiry -- - - name: Check Logstash certificate expiry + - name: logstash-security | Check Logstash certificate expiry ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_check_expiry.yml" apply: @@ -74,7 +74,7 @@ - certificates # -- Determine if renewal is needed (Logstash-specific combined logic) -- - - name: Determine if certificate renewal is needed + - name: logstash-security | Determine if certificate renewal is needed ansible.builtin.set_fact: _logstash_needs_cert_renewal: >- {{ (not (elasticstack_cert_stat.stat.exists | default(false))) or @@ -82,7 +82,7 @@ (logstash_cert_will_expire_soon | default(false) | bool) }} # -- Backup and remove existing certificates -- - - name: Backup Logstash cert directory + - name: logstash-security | Backup Logstash cert directory ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_backup.yml" vars: @@ -92,7 +92,7 @@ - _logstash_needs_cert_renewal | bool - elasticstack_cert_stat.stat.exists | default(false) - - name: Remove old certificate on CA host + - name: logstash-security | Remove old certificate on CA host ansible.builtin.file: path: "{{ elasticstack_ca_dir }}/{{ ansible_facts.hostname }}-ls.p12" state: absent @@ -101,7 +101,7 @@ - _logstash_needs_cert_renewal | bool - elasticstack_cert_stat.stat.exists | default(false) - - name: Remove old PEM certificate zip on CA host + - name: logstash-security | Remove old PEM certificate zip on CA host ansible.builtin.file: path: "{{ elasticstack_ca_dir }}/{{ ansible_facts.hostname }}-ls.zip" state: absent @@ -111,10 +111,10 @@ - elasticstack_cert_stat.stat.exists | default(false) # -- Generate and distribute certificates -- - - name: Generate and distribute certificates + - name: logstash-security | Generate and distribute certificates when: _logstash_needs_cert_renewal | bool block: - - name: Create certificate directory + - name: logstash-security | Create certificate directory ansible.builtin.file: state: directory path: "{{ logstash_certs_dir }}" @@ -125,7 +125,7 @@ - certificates # -- P12 certificate -- - - name: Generate P12 certificate for Logstash + - name: logstash-security | Generate P12 certificate for Logstash ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_generate.yml" apply: @@ -141,7 +141,7 @@ tags: - certificates - - name: Distribute P12 certificate to Logstash host + - name: logstash-security | Distribute P12 certificate to Logstash host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -156,7 +156,7 @@ tags: - certificates - - name: Copy P12 as keystore + - name: logstash-security | Copy P12 as keystore ansible.builtin.copy: src: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ ansible_facts.hostname }}-ls.p12" dest: "{{ logstash_certs_dir }}/keystore.pfx" @@ -169,7 +169,7 @@ - certificates # -- PEM certificate (kept inline, zip+PKCS8 flow is Logstash-specific) -- - - name: Generate PEM certificate for Logstash + - name: logstash-security | Generate PEM certificate for Logstash ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_generate.yml" apply: @@ -186,7 +186,7 @@ tags: - certificates - - name: Fetch PEM certificate zip to controller + - name: logstash-security | Fetch PEM certificate zip to controller ansible.builtin.fetch: src: "{{ elasticstack_ca_dir }}/{{ ansible_facts.hostname }}-ls.zip" dest: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ ansible_facts.hostname }}-ls.zip" @@ -195,7 +195,7 @@ tags: - certificates - - name: Extract PEM certificate on Logstash host + - name: logstash-security | Extract PEM certificate on Logstash host ansible.builtin.unarchive: src: "{{ lookup('config', 'DEFAULT_LOCAL_TMP') | dirname }}/certs/{{ inventory_hostname }}/{{ ansible_facts.hostname }}-ls.zip" dest: "{{ logstash_certs_dir }}/" @@ -205,7 +205,7 @@ tags: - certificates - - name: Copy server certificate to standard location + - name: logstash-security | Copy server certificate to standard location ansible.builtin.copy: src: "{{ logstash_certs_dir }}/{{ ansible_facts.hostname }}/{{ ansible_facts.hostname }}.crt" dest: "{{ logstash_certs_dir }}/{{ inventory_hostname }}-server.crt" @@ -216,7 +216,7 @@ tags: - certificates - - name: Copy encrypted key + - name: logstash-security | Copy encrypted key ansible.builtin.copy: src: "{{ logstash_certs_dir }}/{{ ansible_facts.hostname }}/{{ ansible_facts.hostname }}.key" dest: "{{ logstash_certs_dir }}/{{ inventory_hostname }}.key" @@ -227,7 +227,7 @@ tags: - certificates - - name: Create unencrypted PKCS8 key for Logstash + - name: logstash-security | Create unencrypted PKCS8 key for Logstash ansible.builtin.command: > openssl pkcs8 -in {{ logstash_certs_dir }}/{{ inventory_hostname }}.key @@ -241,7 +241,7 @@ tags: - certificates - - name: Set permissions on PKCS8 key + - name: logstash-security | Set permissions on PKCS8 key ansible.builtin.file: path: "{{ logstash_certs_dir }}/{{ inventory_hostname }}-pkcs8.key" owner: root @@ -251,7 +251,7 @@ - certificates # -- CA certificate distribution -- - - name: Distribute CA certificate to Logstash host + - name: logstash-security | Distribute CA certificate to Logstash host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -268,10 +268,10 @@ tags: - certificates -- name: Handle standalone certificates +- name: logstash-security | Handle standalone certificates when: _logstash_cert_mode == 'standalone' block: - - name: Create certificate directory + - name: logstash-security | Create certificate directory ansible.builtin.file: state: directory path: "{{ logstash_certs_dir }}" @@ -281,7 +281,7 @@ tags: - certificates - - name: Generate standalone CA key + - name: logstash-security | Generate standalone CA key ansible.builtin.command: cmd: >- openssl genrsa -out {{ logstash_certs_dir }}/standalone-ca.key 4096 @@ -289,7 +289,7 @@ tags: - certificates - - name: Generate standalone CA certificate + - name: logstash-security | Generate standalone CA certificate ansible.builtin.command: cmd: >- openssl req -x509 -new -nodes @@ -301,7 +301,7 @@ tags: - certificates - - name: Generate server key + - name: logstash-security | Generate server key ansible.builtin.command: cmd: >- openssl genrsa -out {{ logstash_certs_dir }}/{{ inventory_hostname }}.key 2048 @@ -309,7 +309,7 @@ tags: - certificates - - name: Generate server certificate + - name: logstash-security | Generate server certificate ansible.builtin.shell: | set -e openssl req -new \ @@ -331,7 +331,7 @@ tags: - certificates - - name: Create PKCS8 key for Logstash + - name: logstash-security | Create PKCS8 key for Logstash ansible.builtin.command: > openssl pkcs8 -topk8 @@ -345,7 +345,7 @@ tags: - certificates - - name: Set permissions on standalone certificates + - name: logstash-security | Set permissions on standalone certificates ansible.builtin.file: path: "{{ item }}" owner: root @@ -359,7 +359,7 @@ tags: - certificates - - name: Create PKCS12 keystore for ES output + - name: logstash-security | Create PKCS12 keystore for ES output ansible.builtin.command: > openssl pkcs12 -export -in {{ logstash_certs_dir }}/{{ inventory_hostname }}-server.crt @@ -375,7 +375,7 @@ tags: - certificates - - name: Set keystore permissions + - name: logstash-security | Set keystore permissions ansible.builtin.file: path: "{{ logstash_certs_dir }}/keystore.pfx" owner: root @@ -384,7 +384,7 @@ tags: - certificates - - name: Distribute ES CA certificate to Logstash host + - name: logstash-security | Distribute ES CA certificate to Logstash host ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/certs/cert_distribute.yml" apply: @@ -401,26 +401,26 @@ tags: - certificates -- name: Manage Elasticsearch user and role +- name: logstash-security | Manage Elasticsearch user and role when: _logstash_cert_mode in ['elasticsearch_ca', 'standalone'] block: - - name: Validate logstash user password length + - name: logstash-security | Validate logstash user password length ansible.builtin.fail: msg: "Logstash user password must be at least 6 characters long" when: logstash_user_password | length < 6 - - name: Fetch Elastic admin password + - name: logstash-security | Fetch Elastic admin password ansible.builtin.include_tasks: file: "{{ role_path }}/../elasticstack/tasks/fetch_password.yml" vars: _password_user: elastic _password_fact: _logstash_elastic_password - - name: Set ES API URL fact + - name: logstash-security | Set ES API URL fact ansible.builtin.set_fact: _logstash_es_url: "https://{{ hostvars[elasticstack_ca_host].ansible_host | default(hostvars[elasticstack_ca_host].inventory_hostname) }}:{{ elasticstack_elasticsearch_http_port }}" - - name: Create logstash role + - name: logstash-security | Create logstash role ansible.builtin.uri: url: "{{ _logstash_es_url }}/_security/role/{{ logstash_role_name }}" method: PUT @@ -441,7 +441,7 @@ no_log: "{{ elasticstack_no_log }}" when: logstash_create_role | bool - - name: Create logstash user + - name: logstash-security | Create logstash user ansible.builtin.uri: url: "{{ _logstash_es_url }}/_security/user/{{ logstash_user_name }}" method: PUT diff --git a/roles/logstash/tasks/restart_and_verify_logstash.yml b/roles/logstash/tasks/restart_and_verify_logstash.yml index b3af5977..201a1b47 100644 --- a/roles/logstash/tasks/restart_and_verify_logstash.yml +++ b/roles/logstash/tasks/restart_and_verify_logstash.yml @@ -1,14 +1,14 @@ --- -- name: Restart and verify Logstash +- name: restart_and_verify_logstash | Restart and verify Logstash block: - - name: Restart Logstash service + - name: restart_and_verify_logstash | Restart Logstash service ansible.builtin.service: name: logstash state: restarted daemon_reload: true - - name: Verify Logstash is running + - name: restart_and_verify_logstash | Verify Logstash is running ansible.builtin.systemd: name: logstash register: _logstash_service_state @@ -17,13 +17,13 @@ delay: 3 rescue: - - name: Get recent Logstash journal output + - name: restart_and_verify_logstash | Get recent Logstash journal output ansible.builtin.command: cmd: journalctl -u logstash --no-pager -n 50 register: _logstash_journal changed_when: false - - name: Fail with Logstash startup diagnostics + - name: restart_and_verify_logstash | Fail with Logstash startup diagnostics ansible.builtin.fail: msg: | Logstash failed to start. diff --git a/roles/repos/tasks/debian.yml b/roles/repos/tasks/debian.yml index e59c2265..e13db7c0 100644 --- a/roles/repos/tasks/debian.yml +++ b/roles/repos/tasks/debian.yml @@ -1,5 +1,5 @@ --- -- name: Ensure gpg exists, for signing keys +- name: debian | Ensure gpg exists, for signing keys ansible.builtin.apt: name: - gpg @@ -10,7 +10,7 @@ retries: 3 delay: 10 -- name: Ensure Elastic Stack key is available (Debian) +- name: debian | Ensure Elastic Stack key is available (Debian) ansible.builtin.get_url: url: "{{ elasticstack_repo_key }}" dest: /usr/share/keyrings/elasticsearch.asc @@ -20,7 +20,7 @@ retries: 3 delay: 10 -- name: Ensure Elastic Stack apt repo is absent (Debian legacy format) +- name: debian | Ensure Elastic Stack apt repo is absent (Debian legacy format) ansible.builtin.file: path: /etc/apt/sources.list.d/artifacts_elastic_co_packages_{{ item }}_x_apt.list state: absent @@ -29,7 +29,7 @@ - "8" - "9" -- name: Ensure Elastic Stack apt repository is configured (Debian) +- name: debian | Ensure Elastic Stack apt repository is configured (Debian) ansible.builtin.apt_repository: repo: deb [signed-by=/usr/share/keyrings/elasticsearch.asc] {{ elasticstack_repo_base_url }}/packages/{{ elasticstack_release }}.x/apt stable main state: present diff --git a/roles/repos/tasks/redhat.yml b/roles/repos/tasks/redhat.yml index e0e0ad3a..5a184808 100644 --- a/roles/repos/tasks/redhat.yml +++ b/roles/repos/tasks/redhat.yml @@ -3,7 +3,7 @@ # See https://github.com/elastic/elasticsearch/issues/85876 # for more information why this is needed -- name: Ensure gpg exists, for signing keys +- name: redhat | Ensure gpg exists, for signing keys ansible.builtin.package: name: gnupg state: present @@ -12,23 +12,23 @@ retries: 3 delay: 10 -- name: Workaround for EL > 8 +- name: redhat | Workaround for EL > 8 when: - ansible_facts.distribution_major_version | int >= 9 block: - - name: Show a warning + - name: redhat | Show a warning ansible.builtin.debug: msg: "For this workaround to work, please set elasticstack_rpm_workaround to true" when: - not elasticstack_rpm_workaround | bool - - name: Enable workaround for rpm keys + - name: redhat | Enable workaround for rpm keys when: - elasticstack_rpm_workaround | bool block: - - name: Install crypto-policies-scripts + - name: redhat | Install crypto-policies-scripts ansible.builtin.package: name: crypto-policies-scripts register: _repos_crypto_policies_install @@ -36,22 +36,22 @@ retries: 3 delay: 10 - - name: Check current crypto policy + - name: redhat | Check current crypto policy ansible.builtin.command: update-crypto-policies --show register: _repos_crypto_policy changed_when: false - - name: Set Crypto policies to legacy + - name: redhat | Set Crypto policies to legacy ansible.builtin.command: "update-crypto-policies --set LEGACY" changed_when: true when: _repos_crypto_policy.stdout != 'LEGACY' -- name: Ensure Elastic repository key is available (RedHat) +- name: redhat | Ensure Elastic repository key is available (RedHat) ansible.builtin.rpm_key: key: "{{ elasticstack_repo_key }}" state: present -- name: Ensure Elastic Stack yum repository is configured (RedHat) +- name: redhat | Ensure Elastic Stack yum repository is configured (RedHat) ansible.builtin.yum_repository: name: elastic-{{ elasticstack_release }}.x description: Elastic Release {{ elasticstack_release }}.x From b10dc0c8ebe07db4e2520764ab65eb8228ba9cd8 Mon Sep 17 00:00:00 2001 From: Sam Crauwels Date: Tue, 10 Mar 2026 15:42:09 +0100 Subject: [PATCH 3/5] Clean up variable definitions and naming (non-breaking items from #36) Move internal sentinel variables (*_freshstart) from defaults/ to vars/ with underscore prefixes so users can't accidentally override them. Add missing defaults for elasticsearch_fs_repo, kibana_extra_config, beats_ca_dir, beats_filebeat_modules, logstash_pipeline_unsafe_shutdown, and logstash_skip_root_check. Document elasticstack_cert_pass. Fix security flag propagation so that setting elasticstack_security: false at the stack level actually disables security in Elasticsearch, Kibana, and Logstash (previously only Beats respected it). Fix Logstash security flag to check elasticstack_security instead of elasticstack_full_stack. Update template guards for elasticsearch_fs_repo and kibana_extra_config to handle the new explicit defaults without rendering empty blocks. Refs #36 --- roles/beats/defaults/main.yml | 11 ++++++++--- roles/beats/tasks/filebeat.yml | 2 +- roles/elasticsearch/defaults/main.yml | 11 +++++------ roles/elasticsearch/handlers/main.yml | 4 ++-- roles/elasticsearch/tasks/elasticsearch-security.yml | 6 +++--- roles/elasticsearch/tasks/main.yml | 9 ++++++++- roles/elasticsearch/templates/elasticsearch.yml.j2 | 2 +- roles/elasticsearch/vars/main.yml | 7 +++++++ roles/elasticstack/defaults/main.yml | 7 +++++++ roles/kibana/defaults/main.yml | 12 +++++++++--- roles/kibana/handlers/main.yml | 2 +- roles/kibana/tasks/main.yml | 9 ++++++++- roles/kibana/templates/kibana.yml.j2 | 4 ++-- roles/kibana/vars/main.yml | 5 +++++ roles/logstash/defaults/main.yml | 7 ++++--- roles/logstash/handlers/main.yml | 4 ++-- roles/logstash/tasks/main.yml | 4 ++-- roles/logstash/vars/main.yml | 5 +++++ 18 files changed, 80 insertions(+), 31 deletions(-) create mode 100644 roles/kibana/vars/main.yml create mode 100644 roles/logstash/vars/main.yml diff --git a/roles/beats/defaults/main.yml b/roles/beats/defaults/main.yml index f6b0542b..57d28622 100644 --- a/roles/beats/defaults/main.yml +++ b/roles/beats/defaults/main.yml @@ -35,6 +35,12 @@ beats_logging_permissions: "0644" # === TLS Certificates === +# @var beats_ca_dir:description: > +# Directory where Beat TLS certificates are stored. Auto-set to +# /etc/beats/certs in full_stack mode or /opt/ca otherwise. +# @end +beats_ca_dir: "/etc/beats/certs" + # @var beats_tls_key:description: Path to the Beat TLS private key file beats_tls_key: "{{ beats_ca_dir }}/{{ inventory_hostname }}-beats.key" # @var beats_tls_cert:description: Path to the Beat TLS certificate file @@ -152,14 +158,13 @@ beats_filebeat_extra_inputs: [] # @var beats_filebeat_mysql_slowlog_input:description: Enable MySQL slow query log input with multiline parsing beats_filebeat_mysql_slowlog_input: false -# @var beats_filebeat_modules:description: List of Filebeat modules to enable +# @var beats_filebeat_modules:description: List of Filebeat modules to enable. Leave empty to disable module management # @var beats_filebeat_modules:example: > # beats_filebeat_modules: # - system # - nginx # @end -# beats_filebeat_modules: -# - system +beats_filebeat_modules: [] # === Auditbeat Configuration === diff --git a/roles/beats/tasks/filebeat.yml b/roles/beats/tasks/filebeat.yml index 6a95f0c6..93813192 100644 --- a/roles/beats/tasks/filebeat.yml +++ b/roles/beats/tasks/filebeat.yml @@ -20,7 +20,7 @@ - beats_configuration - name: filebeat | Configure modules - when: beats_filebeat_modules is defined + when: beats_filebeat_modules | length > 0 tags: - configuration - beats_filebeat_configuration diff --git a/roles/elasticsearch/defaults/main.yml b/roles/elasticsearch/defaults/main.yml index f27e39af..8c871c84 100644 --- a/roles/elasticsearch/defaults/main.yml +++ b/roles/elasticsearch/defaults/main.yml @@ -182,9 +182,8 @@ elasticsearch_unsafe_upgrade_restart: false # @end elasticsearch_extra_config: {} -# @var elasticsearch_freshstart:description: Internal flag tracking whether this is a fresh installation. Do not set manually -elasticsearch_freshstart: - changed: false -# @var elasticsearch_freshstart_security:description: Internal flag tracking whether security was just initialized. Do not set manually -elasticsearch_freshstart_security: - changed: false +# @var elasticsearch_fs_repo:description: > +# List of filesystem paths registered as snapshot repositories in +# elasticsearch.yml (path.repo). Leave empty to disable. +# @end +elasticsearch_fs_repo: [] diff --git a/roles/elasticsearch/handlers/main.yml b/roles/elasticsearch/handlers/main.yml index 59d35193..20e8b9ae 100644 --- a/roles/elasticsearch/handlers/main.yml +++ b/roles/elasticsearch/handlers/main.yml @@ -5,8 +5,8 @@ when: - not ansible_check_mode - elasticsearch_enable | bool - - not elasticsearch_freshstart.changed | bool - - not elasticsearch_freshstart_security.changed | bool + - not _elasticsearch_freshstart.changed | bool + - not _elasticsearch_freshstart_security.changed | bool - not _elasticsearch_rolling_upgrade_performed | default(false) | bool - name: Restart kibana if available for elasticsearch certificates diff --git a/roles/elasticsearch/tasks/elasticsearch-security.yml b/roles/elasticsearch/tasks/elasticsearch-security.yml index 8e04f94e..9ec3f83c 100644 --- a/roles/elasticsearch/tasks/elasticsearch-security.yml +++ b/roles/elasticsearch/tasks/elasticsearch-security.yml @@ -609,7 +609,7 @@ name: elasticsearch state: started enabled: true - register: elasticsearch_freshstart_security + register: _elasticsearch_freshstart_security when: not ansible_check_mode - name: elasticsearch-security | Wait for all instances to start @@ -618,8 +618,8 @@ - name: elasticsearch-security | Restart if Elasticsearch was already running when: - - not elasticsearch_freshstart.changed | bool - - not elasticsearch_freshstart_security.changed | bool + - not _elasticsearch_freshstart.changed | bool + - not _elasticsearch_freshstart_security.changed | bool block: - name: elasticsearch-security | Force all notified handlers to run at this point, not waiting for normal sync points ansible.builtin.meta: flush_handlers diff --git a/roles/elasticsearch/tasks/main.yml b/roles/elasticsearch/tasks/main.yml index 8cc16335..eabf7ace 100644 --- a/roles/elasticsearch/tasks/main.yml +++ b/roles/elasticsearch/tasks/main.yml @@ -22,6 +22,13 @@ name: oddly.elasticstack.elasticstack when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) +- name: main | Propagate stack-level security setting + ansible.builtin.set_fact: + elasticsearch_security: "{{ elasticstack_security | bool }}" + when: + - elasticstack_full_stack | bool + - not elasticstack_security | bool + - name: Check-set-parameters ansible.builtin.include_tasks: elasticsearch-parameters.yml @@ -477,7 +484,7 @@ name: elasticsearch state: started enabled: true - register: elasticsearch_freshstart + register: _elasticsearch_freshstart when: not ansible_check_mode # The comment in the following task will disable KICS security checks for this diff --git a/roles/elasticsearch/templates/elasticsearch.yml.j2 b/roles/elasticsearch/templates/elasticsearch.yml.j2 index e33c517b..94fa4ff4 100644 --- a/roles/elasticsearch/templates/elasticsearch.yml.j2 +++ b/roles/elasticsearch/templates/elasticsearch.yml.j2 @@ -114,7 +114,7 @@ xpack.security.enabled: false bootstrap.memory_lock: true {% endif %} -{% if elasticsearch_fs_repo is defined %} +{% if elasticsearch_fs_repo | default([]) | length > 0 %} path: repo: {% for path in elasticsearch_fs_repo %} diff --git a/roles/elasticsearch/vars/main.yml b/roles/elasticsearch/vars/main.yml index c8a6b214..af01504e 100644 --- a/roles/elasticsearch/vars/main.yml +++ b/roles/elasticsearch/vars/main.yml @@ -36,3 +36,10 @@ _elasticsearch_managed_keys: - xpack.security.http.ssl.keystore.path - xpack.security.http.ssl.truststore.path - bootstrap.memory_lock + +# Internal sentinel values — prevent handler errors on first run. +# These are overwritten by task register output; users must never set them. +_elasticsearch_freshstart: + changed: false +_elasticsearch_freshstart_security: + changed: false diff --git a/roles/elasticstack/defaults/main.yml b/roles/elasticstack/defaults/main.yml index 09d34928..1a2d9f02 100644 --- a/roles/elasticstack/defaults/main.yml +++ b/roles/elasticstack/defaults/main.yml @@ -36,6 +36,13 @@ elasticstack_ca_expiration_buffer: 30 elasticstack_ca_name: "CN=Elastic Certificate Tool Autogenerated CA" # @var elasticstack_ca_pass:description: Passphrase for the CA private key elasticstack_ca_pass: PleaseChangeMe +# @var elasticstack_cert_pass:description: > +# Common passphrase applied to all role TLS private keys when set. +# Overrides elasticsearch_tls_key_passphrase, kibana_tls_key_passphrase, +# logstash_tls_key_passphrase, and beats_tls_key_passphrase. +# Leave empty to use the per-role passphrases. +# @end +# elasticstack_cert_pass: # @var elasticstack_ca_validity_period:description: Validity period in days for the CA certificate elasticstack_ca_validity_period: 1095 # @var elasticstack_ca_will_expire_soon:description: Internal flag set when the CA certificate is within the expiration buffer. Do not set manually diff --git a/roles/kibana/defaults/main.yml b/roles/kibana/defaults/main.yml index 82048869..548f6a89 100644 --- a/roles/kibana/defaults/main.yml +++ b/roles/kibana/defaults/main.yml @@ -80,6 +80,12 @@ kibana_sniff_on_start: false # @end kibana_sniff_on_connection_fault: false -# @var kibana_freshstart:description: Internal flag tracking whether this is a fresh installation. Do not set manually -kibana_freshstart: - changed: false +# @var kibana_extra_config:description: > +# Dictionary of additional kibana.yml settings not covered by dedicated +# variables. Keys are written as-is into kibana.yml. +# @var kibana_extra_config:example: > +# kibana_extra_config: +# server.maxPayload: 1048576 +# xpack.reporting.enabled: false +# @end +kibana_extra_config: {} diff --git a/roles/kibana/handlers/main.yml b/roles/kibana/handlers/main.yml index 5d380ac1..fd093a5a 100644 --- a/roles/kibana/handlers/main.yml +++ b/roles/kibana/handlers/main.yml @@ -9,4 +9,4 @@ when: - not ansible_check_mode - kibana_enable | bool - - not kibana_freshstart.changed | bool + - not _kibana_freshstart.changed | bool diff --git a/roles/kibana/tasks/main.yml b/roles/kibana/tasks/main.yml index fe4d1be9..92e7f536 100644 --- a/roles/kibana/tasks/main.yml +++ b/roles/kibana/tasks/main.yml @@ -5,6 +5,13 @@ name: oddly.elasticstack.elasticstack when: not hostvars[inventory_hostname]._elasticstack_role_imported | default(false) +- name: main | Propagate stack-level security setting + ansible.builtin.set_fact: + kibana_security: "{{ elasticstack_security | bool }}" + when: + - elasticstack_full_stack | bool + - not elasticstack_security | bool + - name: Set common password for common certificates ansible.builtin.set_fact: kibana_tls_key_passphrase: "{{ elasticstack_cert_pass }}" @@ -124,7 +131,7 @@ when: - kibana_enable | bool - not ansible_check_mode - register: kibana_freshstart + register: _kibana_freshstart # Flush any pending restart handler (e.g. from cert renewal or config changes) # BEFORE waiting for Kibana to be ready. Without this, the wait task deadlocks: diff --git a/roles/kibana/templates/kibana.yml.j2 b/roles/kibana/templates/kibana.yml.j2 index 7c0629cc..ed4da5cc 100644 --- a/roles/kibana/templates/kibana.yml.j2 +++ b/roles/kibana/templates/kibana.yml.j2 @@ -42,6 +42,6 @@ server.ssl.keystore.path: "/etc/kibana/certs/{{ inventory_hostname }}-kibana.p12 {% endif %} {% endif %} -{% if kibana_extra_config is defined %} -{{ kibana_extra_config }} +{% if kibana_extra_config is defined and kibana_extra_config %} +{{ kibana_extra_config | to_nice_yaml(indent=2, sort_keys=False) }} {% endif %} diff --git a/roles/kibana/vars/main.yml b/roles/kibana/vars/main.yml new file mode 100644 index 00000000..cad325d0 --- /dev/null +++ b/roles/kibana/vars/main.yml @@ -0,0 +1,5 @@ +--- +# Internal sentinel values — prevent handler errors on first run. +# These are overwritten by task register output; users must never set them. +_kibana_freshstart: + changed: false diff --git a/roles/logstash/defaults/main.yml b/roles/logstash/defaults/main.yml index 64f2d94a..27ce0426 100644 --- a/roles/logstash/defaults/main.yml +++ b/roles/logstash/defaults/main.yml @@ -281,6 +281,7 @@ logstash_plugins: [] # === Internal Use === -# @var logstash_freshstart:description: Internal flag tracking whether this is a fresh installation. Do not set manually -logstash_freshstart: - changed: false +# @var logstash_pipeline_unsafe_shutdown:description: Allow Logstash to shut down even if there are inflight events in the pipeline +logstash_pipeline_unsafe_shutdown: false +# @var logstash_skip_root_check:description: Skip the Logstash 9.x root user upgrade warning check +logstash_skip_root_check: false diff --git a/roles/logstash/handlers/main.yml b/roles/logstash/handlers/main.yml index 37b6f6d1..2b75c84a 100644 --- a/roles/logstash/handlers/main.yml +++ b/roles/logstash/handlers/main.yml @@ -5,7 +5,7 @@ when: - not ansible_check_mode - logstash_enable | bool - - not logstash_freshstart.changed | bool + - not _logstash_freshstart.changed | bool - name: Restart Logstash noauto ansible.builtin.include_tasks: restart_and_verify_logstash.yml @@ -13,4 +13,4 @@ - not ansible_check_mode - not logstash_config_autoreload - logstash_enable | bool - - not logstash_freshstart.changed | bool + - not _logstash_freshstart.changed | bool diff --git a/roles/logstash/tasks/main.yml b/roles/logstash/tasks/main.yml index eed663ae..fee636aa 100644 --- a/roles/logstash/tasks/main.yml +++ b/roles/logstash/tasks/main.yml @@ -59,7 +59,7 @@ - name: Set up security flag ansible.builtin.set_fact: - _logstash_security: "{{ logstash_security | default(elasticstack_full_stack | default(false)) }}" + _logstash_security: "{{ logstash_security | default(elasticstack_security | default(false)) }}" tags: - certificates - renew_ca @@ -373,5 +373,5 @@ state: started enabled: true when: logstash_enable | bool - register: logstash_freshstart + register: _logstash_freshstart diff --git a/roles/logstash/vars/main.yml b/roles/logstash/vars/main.yml new file mode 100644 index 00000000..2031f6a3 --- /dev/null +++ b/roles/logstash/vars/main.yml @@ -0,0 +1,5 @@ +--- +# Internal sentinel values — prevent handler errors on first run. +# These are overwritten by task register output; users must never set them. +_logstash_freshstart: + changed: false From 984b3f99cf23e622c8fcbdaa95b8c3c2cff277ef Mon Sep 17 00:00:00 2001 From: Sam Crauwels Date: Tue, 10 Mar 2026 16:20:58 +0100 Subject: [PATCH 4/5] Add verify assertions for configured but untested variables Close gaps where molecule scenarios set non-default values but never checked whether they were actually applied. Add config file assertions for beats log level, metricbeat output, filebeat extra inputs, logstash heap size, pipeline.unsafe_shutdown, ident field names, HTTP extra input port, centralized pipeline HTTP binding, elasticsearch heap dump path, config backup count, managed logging, kibana extra_config, beats fields, and filebeat modules. Also add elasticsearch_manage_logging: true and beats_filebeat_extra_inputs to their respective converge files to test those code paths. Refs #36 --- molecule/beats_advanced/converge.yml | 7 +++ molecule/beats_advanced/verify.yml | 19 +++++++ molecule/elasticsearch_custom/converge.yml | 2 + molecule/elasticsearch_custom/verify.yml | 56 ++++++++++++++++--- molecule/elasticstack_default/verify.yml | 35 ++++++++++++ molecule/logstash_advanced/verify.yml | 42 ++++++++++++++ .../logstash_centralized_pipelines/verify.yml | 14 +++++ molecule/logstash_default/verify.yml | 20 +++++++ 8 files changed, 186 insertions(+), 9 deletions(-) diff --git a/molecule/beats_advanced/converge.yml b/molecule/beats_advanced/converge.yml index d7ce0bd8..fdb5986b 100644 --- a/molecule/beats_advanced/converge.yml +++ b/molecule/beats_advanced/converge.yml @@ -54,6 +54,13 @@ # Queue configuration (disk queue) beats_queue_type: disk beats_queue_disk_max_size: 512MB + # Extra inputs (verify rendering of beats_filebeat_extra_inputs) + beats_filebeat_extra_inputs: + - type: filestream + enabled: true + id: extra-test + paths: + - /var/log/extra-test.log tasks: - name: Include Elastic repos role ansible.builtin.include_role: diff --git a/molecule/beats_advanced/verify.yml b/molecule/beats_advanced/verify.yml index e1098c53..18ce90f2 100644 --- a/molecule/beats_advanced/verify.yml +++ b/molecule/beats_advanced/verify.yml @@ -60,6 +60,19 @@ - "'journald' in (filebeat_yml.content | b64decode)" fail_msg: "Journald input not found in filebeat.yml" + - name: Verify log level is debug + ansible.builtin.assert: + that: + - "'level: debug' in (filebeat_yml.content | b64decode)" + fail_msg: "Logging level not set to debug in filebeat.yml" + + - name: Verify extra input is rendered + ansible.builtin.assert: + that: + - "'extra-test' in (filebeat_yml.content | b64decode)" + - "'extra-test.log' in (filebeat_yml.content | b64decode)" + fail_msg: "beats_filebeat_extra_inputs not rendered in filebeat.yml" + - name: Verify file output is configured ansible.builtin.assert: that: @@ -107,6 +120,12 @@ - "'metricbeat.config.modules' in (metricbeat_yml.content | b64decode)" fail_msg: "Module config path not found in metricbeat.yml" + - name: Verify metricbeat output is logstash + ansible.builtin.assert: + that: + - "'output.logstash' in (metricbeat_yml.content | b64decode)" + fail_msg: "Metricbeat output not set to logstash" + - name: Verify system module is enabled ansible.builtin.stat: path: /etc/metricbeat/modules.d/system.yml diff --git a/molecule/elasticsearch_custom/converge.yml b/molecule/elasticsearch_custom/converge.yml index 51a3895e..f862048e 100644 --- a/molecule/elasticsearch_custom/converge.yml +++ b/molecule/elasticsearch_custom/converge.yml @@ -42,6 +42,8 @@ elasticsearch_pamlimits: true # Memory lock elasticsearch_memory_lock: true + # Managed logging (verify log4j2.properties is deployed) + elasticsearch_manage_logging: true tasks: - name: Include Elastic repos ansible.builtin.include_role: diff --git a/molecule/elasticsearch_custom/verify.yml b/molecule/elasticsearch_custom/verify.yml index f2dc60e6..51979f66 100644 --- a/molecule/elasticsearch_custom/verify.yml +++ b/molecule/elasticsearch_custom/verify.yml @@ -154,18 +154,35 @@ - "'auto_create_index' in es_yml.content | b64decode" fail_msg: "action.auto_create_index from elasticsearch_extra_config not found" - - name: Assert snapshot repo path is configured - ansible.builtin.assert: - that: - - "'/mnt/es-snapshots' in es_yml.content | b64decode" - fail_msg: "Snapshot repo path /mnt/es-snapshots not found in elasticsearch.yml" - - name: Assert indices.recovery.max_bytes_per_sec from extra_config ansible.builtin.assert: that: - "'indices.recovery.max_bytes_per_sec' in es_yml.content | b64decode" fail_msg: "indices.recovery.max_bytes_per_sec not found (should come from elasticsearch_extra_config)" + # ── Heap dump path ────────────────────────────────────────────── + + - name: Read JVM paths options + ansible.builtin.slurp: + src: /etc/elasticsearch/jvm.options.d/50-paths.options + register: jvm_paths + + - name: Verify heap dump path is set to custom location + ansible.builtin.assert: + that: + - "'-XX:HeapDumpPath=/data/elasticsearch' in jvm_paths.content | b64decode" + fail_msg: "HeapDumpPath not set to /data/elasticsearch" + + # ── Snapshot repo directory ────────────────────────────────── + + - name: Verify snapshot repo path in elasticsearch.yml + ansible.builtin.assert: + that: + - "'path' in es_yml.content | b64decode" + - "'repo' in es_yml.content | b64decode" + - "'/mnt/es-snapshots' in es_yml.content | b64decode" + fail_msg: "Snapshot repo path /mnt/es-snapshots not configured correctly" + # ── Config backup ───────────────────────────────────────────── - name: Verify config backup was created @@ -174,9 +191,11 @@ patterns: "elasticsearch.yml.*" register: backups - - name: Report backup status - ansible.builtin.debug: - msg: "Found {{ backups.files | length }} config backup(s)" + - name: Assert at least one backup exists + ansible.builtin.assert: + that: + - backups.files | length > 0 + fail_msg: "No elasticsearch.yml backup found despite elasticsearch_config_backup: true" # ── ML setting ──────────────────────────────────────────────── @@ -349,3 +368,22 @@ - "'xpack.security.transport.ssl.enabled: true' in es_yml.content | b64decode" - "'xpack.security.http.ssl.enabled: true' in es_yml.content | b64decode" fail_msg: "Security/TLS settings missing from elasticsearch.yml" + + # ── Managed logging (log4j2.properties) ────────────────────── + + - name: Check log4j2.properties exists + ansible.builtin.stat: + path: /etc/elasticsearch/log4j2.properties + register: es_log4j2 + + - name: Assert log4j2.properties is deployed + ansible.builtin.assert: + that: + - es_log4j2.stat.exists + fail_msg: "log4j2.properties not found (elasticsearch_manage_logging: true)" + + - name: Verify log4j2.properties is owned by elasticsearch group + ansible.builtin.assert: + that: + - es_log4j2.stat.gr_name == 'elasticsearch' + fail_msg: "log4j2.properties not owned by elasticsearch group" diff --git a/molecule/elasticstack_default/verify.yml b/molecule/elasticstack_default/verify.yml index da02a36a..ebe980ea 100644 --- a/molecule/elasticstack_default/verify.yml +++ b/molecule/elasticstack_default/verify.yml @@ -115,6 +115,18 @@ - kibana_status.json.status.overall.level == 'available' fail_msg: "Kibana status: {{ kibana_status.json.status.overall.level | default('unknown') }}" + - name: Read kibana.yml + ansible.builtin.slurp: + src: /etc/kibana/kibana.yml + register: kibana_yml + + - name: Verify kibana extra_config is rendered + ansible.builtin.assert: + that: + - "'ops.interval' in (kibana_yml.content | b64decode)" + - "'5000' in (kibana_yml.content | b64decode)" + fail_msg: "kibana_extra_config (ops.interval: 5000) not found in kibana.yml" + - name: Run Beats checks when: "'beats' in group_names" block: @@ -134,6 +146,29 @@ register: metricbeat_svc failed_when: metricbeat_svc.changed + - name: Read filebeat.yml + ansible.builtin.slurp: + src: /etc/filebeat/filebeat.yml + register: filebeat_yml + + - name: Verify beats_fields is rendered in filebeat.yml + ansible.builtin.assert: + that: + - "'testbed' in (filebeat_yml.content | b64decode)" + - "'molecule' in (filebeat_yml.content | b64decode)" + fail_msg: "beats_fields (testbed: molecule) not found in filebeat.yml" + + - name: Verify system module is enabled + ansible.builtin.stat: + path: /etc/filebeat/modules.d/system.yml + register: _fb_system_module + + - name: Assert filebeat system module file exists + ansible.builtin.assert: + that: + - _fb_system_module.stat.exists + fail_msg: "Filebeat system module not enabled despite beats_filebeat_modules: [system]" + - name: Check auditbeat package is installed ansible.builtin.package: name: auditbeat diff --git a/molecule/logstash_advanced/verify.yml b/molecule/logstash_advanced/verify.yml index fb5eda11..4e96046a 100644 --- a/molecule/logstash_advanced/verify.yml +++ b/molecule/logstash_advanced/verify.yml @@ -123,6 +123,40 @@ - "'config.reload.automatic: true' in (logstash_yml.content | b64decode)" fail_msg: "Config autoreload not enabled" + - name: Verify pipeline.unsafe_shutdown is set + ansible.builtin.assert: + that: + - "'pipeline.unsafe_shutdown' in (logstash_yml.content | b64decode)" + fail_msg: "pipeline.unsafe_shutdown not found in logstash.yml" + + # --- JVM heap verification --- + + - name: Read Logstash JVM options + ansible.builtin.slurp: + src: /etc/logstash/jvm.options + register: logstash_jvm + + - name: Verify Logstash heap is set to 512m + ansible.builtin.assert: + that: + - "'-Xms512m' in (logstash_jvm.content | b64decode)" + - "'-Xmx512m' in (logstash_jvm.content | b64decode)" + fail_msg: "Logstash heap not set to 512m" + + # --- Ident field name verification --- + + - name: Verify exact ident field name + ansible.builtin.assert: + that: + - "'[logstash][instance]' in (filter_content.content | b64decode)" + fail_msg: "Ident field name [logstash][instance] not found in 50-filter.conf" + + - name: Verify exact pipeline identifier field name + ansible.builtin.assert: + that: + - "'[logstash][pipeline]' in (filter_content.content | b64decode)" + fail_msg: "Pipeline identifier field name [logstash][pipeline] not found in 50-filter.conf" + # --- Pipelines.yml verification (queue settings are per-pipeline) --- - name: Read pipelines.yml @@ -204,6 +238,14 @@ ansible.builtin.fail: msg: "Logstash port 5044 did not open within timeout." + # --- HTTP extra input port check --- + + - name: Check Logstash HTTP input is listening on port 8080 + ansible.builtin.wait_for: + port: 8080 + timeout: 30 + state: started + # --- Config syntax validation --- - name: Get installed Logstash version diff --git a/molecule/logstash_centralized_pipelines/verify.yml b/molecule/logstash_centralized_pipelines/verify.yml index 28ed77f2..ada80fa7 100644 --- a/molecule/logstash_centralized_pipelines/verify.yml +++ b/molecule/logstash_centralized_pipelines/verify.yml @@ -76,6 +76,20 @@ - "'monitoring.enabled: false' in logstash_config" fail_msg: "Monitoring not disabled" + # --- HTTP API binding --- + + - name: Verify HTTP host is set + ansible.builtin.assert: + that: + - "'api.http.host: 127.0.0.1' in logstash_config" + fail_msg: "api.http.host not set to 127.0.0.1 in logstash.yml" + + - name: Verify HTTP port is set + ansible.builtin.assert: + that: + - "'api.http.port: 9600' in logstash_config" + fail_msg: "api.http.port not set to 9600 in logstash.yml" + # --- Extra config --- - name: Verify extra_config settings are rendered diff --git a/molecule/logstash_default/verify.yml b/molecule/logstash_default/verify.yml index b43406aa..6b641c31 100644 --- a/molecule/logstash_default/verify.yml +++ b/molecule/logstash_default/verify.yml @@ -226,3 +226,23 @@ that: - "'config.reload.automatic: true' in (logstash_yml.content | b64decode)" fail_msg: "Config autoreload not found in logstash.yml" + + - name: Verify pipeline.unsafe_shutdown is set + ansible.builtin.assert: + that: + - "'pipeline.unsafe_shutdown' in (logstash_yml.content | b64decode)" + fail_msg: "pipeline.unsafe_shutdown not found in logstash.yml" + + # --- JVM heap verification --- + + - name: Read Logstash JVM options + ansible.builtin.slurp: + src: /etc/logstash/jvm.options + register: logstash_jvm + + - name: Verify Logstash heap is set to 512m + ansible.builtin.assert: + that: + - "'-Xms512m' in (logstash_jvm.content | b64decode)" + - "'-Xmx512m' in (logstash_jvm.content | b64decode)" + fail_msg: "Logstash heap not set to 512m" From 1e931008a3a49b555d2315754d277efaaaf26ddc Mon Sep 17 00:00:00 2001 From: Sam Crauwels Date: Tue, 10 Mar 2026 18:32:44 +0100 Subject: [PATCH 5/5] Fix service restart failures from systemd rate limiting Clear the systemd failed state before restarting ES, Kibana, and Logstash services to prevent "start request repeated too quickly" failures. When multiple handlers or tasks restart a service in quick succession, systemd rate-limits the restarts and refuses further attempts until the failed counter is reset. The Kibana restart handler in the ES role also gains run_once to prevent every ES host from restarting the same Kibana instance simultaneously via delegation, which caused the second restart to kill the first startup. Also fixes kibana_extra_config in the elasticstack_default converge from a literal string to a dict, matching what the template's to_nice_yaml filter expects. --- molecule/elasticstack_default/converge.yml | 2 +- roles/elasticsearch/handlers/main.yml | 1 + roles/elasticsearch/handlers/restart_kibana.yml | 7 +++++++ .../tasks/restart_and_verify_elasticsearch.yml | 6 ++++++ roles/kibana/tasks/restart_and_verify_kibana.yml | 6 ++++++ roles/logstash/tasks/restart_and_verify_logstash.yml | 6 ++++++ 6 files changed, 27 insertions(+), 1 deletion(-) diff --git a/molecule/elasticstack_default/converge.yml b/molecule/elasticstack_default/converge.yml index 8f6ebb22..02bf80ab 100644 --- a/molecule/elasticstack_default/converge.yml +++ b/molecule/elasticstack_default/converge.yml @@ -25,7 +25,7 @@ beats_metricbeat: true beats_fields: - "testbed: molecule" - kibana_extra_config: |- + kibana_extra_config: ops.interval: 5000 tasks: - name: Enable Elastic installation on RHEL 9 diff --git a/roles/elasticsearch/handlers/main.yml b/roles/elasticsearch/handlers/main.yml index 20e8b9ae..fa191524 100644 --- a/roles/elasticsearch/handlers/main.yml +++ b/roles/elasticsearch/handlers/main.yml @@ -12,6 +12,7 @@ - name: Restart kibana if available for elasticsearch certificates ansible.builtin.include_tasks: handlers/restart_kibana.yml loop: "{{ groups[elasticstack_kibana_group_name] | default([]) }}" + run_once: true when: - elasticstack_full_stack | bool - "not 'renew_ca' in ansible_run_tags" diff --git a/roles/elasticsearch/handlers/restart_kibana.yml b/roles/elasticsearch/handlers/restart_kibana.yml index 588738d2..91c7c213 100644 --- a/roles/elasticsearch/handlers/restart_kibana.yml +++ b/roles/elasticsearch/handlers/restart_kibana.yml @@ -8,6 +8,13 @@ - name: Restart and wait for Kibana when: "'kibana' in hostvars[item].ansible_facts.packages | default({})" block: + - name: Reset Kibana failed state before restart + ansible.builtin.command: + cmd: systemctl reset-failed kibana.service + delegate_to: "{{ item }}" + changed_when: false + failed_when: false + - name: Restart Kibana service ansible.builtin.service: name: kibana diff --git a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml index 573c7644..5b7e42d9 100644 --- a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml +++ b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml @@ -2,6 +2,12 @@ - name: restart_and_verify_elasticsearch | Restart and verify Elasticsearch block: + - name: restart_and_verify_elasticsearch | Reset Elasticsearch failed state before restart + ansible.builtin.command: + cmd: systemctl reset-failed elasticsearch.service + changed_when: false + failed_when: false + - name: restart_and_verify_elasticsearch | Restart Elasticsearch service ansible.builtin.service: name: elasticsearch diff --git a/roles/kibana/tasks/restart_and_verify_kibana.yml b/roles/kibana/tasks/restart_and_verify_kibana.yml index 5441d2b9..4210e0ce 100644 --- a/roles/kibana/tasks/restart_and_verify_kibana.yml +++ b/roles/kibana/tasks/restart_and_verify_kibana.yml @@ -2,6 +2,12 @@ - name: restart_and_verify_kibana | Restart and verify Kibana block: + - name: restart_and_verify_kibana | Reset Kibana failed state before restart + ansible.builtin.command: + cmd: systemctl reset-failed kibana.service + changed_when: false + failed_when: false + - name: restart_and_verify_kibana | Restart Kibana service ansible.builtin.service: name: kibana diff --git a/roles/logstash/tasks/restart_and_verify_logstash.yml b/roles/logstash/tasks/restart_and_verify_logstash.yml index 201a1b47..65b41ccf 100644 --- a/roles/logstash/tasks/restart_and_verify_logstash.yml +++ b/roles/logstash/tasks/restart_and_verify_logstash.yml @@ -2,6 +2,12 @@ - name: restart_and_verify_logstash | Restart and verify Logstash block: + - name: restart_and_verify_logstash | Reset Logstash failed state before restart + ansible.builtin.command: + cmd: systemctl reset-failed logstash.service + changed_when: false + failed_when: false + - name: restart_and_verify_logstash | Restart Logstash service ansible.builtin.service: name: logstash