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/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/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/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" 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/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/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 0e5314c4..7d585e44 100644 --- a/roles/beats/tasks/beat-install.yml +++ b/roles/beats/tasks/beat-install.yml @@ -5,17 +5,17 @@ # 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: >- {{ _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 }}" +- 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..93813192 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,21 +19,21 @@ - beats_filebeat_configuration - beats_configuration -- name: Configure modules - when: beats_filebeat_modules is defined +- name: filebeat | Configure modules + when: beats_filebeat_modules | length > 0 tags: - configuration - beats_filebeat_configuration - 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 073cb7a5..3d7fa0b4 100644 --- a/roles/beats/tasks/restart_and_verify_beat.yml +++ b/roles/beats/tasks/restart_and_verify_beat.yml @@ -1,31 +1,31 @@ --- -- 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: _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 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: _beat_journal + 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. Recent log output: - {{ _beat_journal.stdout }} + {{ _beats_journal.stdout }} 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..fa191524 100644 --- a/roles/elasticsearch/handlers/main.yml +++ b/roles/elasticsearch/handlers/main.yml @@ -5,13 +5,14 @@ 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 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 e168027b..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 @@ -26,18 +33,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 +54,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-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 523b8ccf..9ec3f83c 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,21 +256,21 @@ # 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' /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 - 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: "{{ _extracted_ca_chain.stdout }}\n" + content: "{{ _elasticsearch_extracted_ca_chain.stdout }}\n" dest: /etc/elasticsearch/certs/ca.crt owner: root group: elasticsearch @@ -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,38 +289,38 @@ # -- 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" 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 + - name: elasticsearch-security | 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 -- - - 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,26 +355,26 @@ # 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: - 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" 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 }}" 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" @@ -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,31 +604,31 @@ # 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 enabled: true - register: elasticsearch_freshstart_security + 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 + - 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,39 +670,39 @@ # 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 - register: _elastic_reset_pw + register: _elasticsearch_elastic_reset_pw 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 - 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 }}" - - 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: | Changed password for user elastic - PASSWORD elastic = {{ _elastic_reset_pw.stdout }} + PASSWORD elastic = {{ _elasticsearch_elastic_reset_pw.stdout }} owner: root group: root mode: "0600" @@ -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 + - 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 + - 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 > @@ -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: @@ -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: _pw_file_check + 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 @@ -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 + - name: elasticsearch-security | 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,9 +940,9 @@ 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 + - 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 + - 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/main.yml b/roles/elasticsearch/tasks/main.yml index d4b1f539..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 @@ -34,7 +41,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 +52,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 +151,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 +183,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 +292,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 +300,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,31 +460,31 @@ - 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: 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 @@ -531,8 +538,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 +562,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 +584,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 +607,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/restart_and_verify_elasticsearch.yml b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml index 6dd99be2..5b7e42d9 100644 --- a/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml +++ b/roles/elasticsearch/tasks/restart_and_verify_elasticsearch.yml @@ -1,14 +1,20 @@ --- -- name: Restart and verify Elasticsearch +- name: restart_and_verify_elasticsearch | Restart and verify Elasticsearch block: - - name: Restart Elasticsearch service + - 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 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 +23,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 2a5e38ed..05922055 100644 --- a/roles/elasticsearch/tasks/wait_for_instance.yml +++ b/roles/elasticsearch/tasks/wait_for_instance.yml @@ -1,15 +1,16 @@ --- -- 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 if ! systemctl is-active --quiet elasticsearch; then exit 2 fi @@ -17,35 +18,36 @@ 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 + - name: wait_for_instance | 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) + - 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 }}. 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) + - 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. - 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/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/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 5dd88908..7f554d55 100644 --- a/roles/elasticstack/tasks/certs/cert_expiry_warn.yml +++ b/roles/elasticstack/tasks/certs/cert_expiry_warn.yml @@ -11,41 +11,41 @@ # _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: _warn_cert_file + register: _elasticstack_warn_cert_file -- name: "Check certificate expiry — {{ _warn_service_name }}" - when: _warn_cert_file.stat.exists +- 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) }}" 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 }}" + - name: certs | cert_expiry_warn | 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 }}" + - name: certs | cert_expiry_warn | 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_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 43be0ea9..bdaa07c4 100644 --- a/roles/elasticstack/tasks/certs/cert_validate.yml +++ b/roles/elasticstack/tasks/certs/cert_validate.yml @@ -13,158 +13,158 @@ # _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: _validate_cert_stat + 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 }} (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 }}" +- name: certs | cert_validate | 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' }}" 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 {{ ('-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 }}" +- name: certs | cert_validate | Fail if format unrecognised — {{ _validate_service }} ansible.builtin.fail: msg: >- 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 }}" +- name: certs | cert_validate | 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 }}" +- name: certs | cert_validate | 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 }}" + - name: certs | cert_validate | 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 }}" + - name: certs | cert_validate | 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 }}" + - name: certs | cert_validate | 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 }}" + - name: certs | cert_validate | 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 }}" +- 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: - - _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 +- 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: _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 }}" + - name: certs | cert_validate | 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 }}" +- name: certs | cert_validate | 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 }}" +- 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: _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 }}" +- name: certs | cert_validate | Fail if certificate already expired — {{ _validate_service }} ansible.builtin.fail: msg: >- {{ _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 }}" +- name: certs | cert_validate | 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 }}" + - 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 - register: _validate_cert_modulus + register: _elasticstack_validate_cert_modulus changed_when: false 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 && @@ -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' }}" @@ -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 && @@ -188,69 +188,69 @@ {{ ('-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 }}" + - 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 - 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 }}" + - 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. 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 }}" + - 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. 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 +- 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 - register: _validate_san_output + register: _elasticstack_validate_san_output changed_when: false 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 ({{ 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/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 00edae2a..6c4fd8de 100644 --- a/roles/elasticstack/tasks/packages.yml +++ b/roles/elasticstack/tasks/packages.yml @@ -1,27 +1,27 @@ --- -- 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 - 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/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/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 45dbb0b6..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,35 +286,35 @@ 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: - encryption_key - savedobjects_encryption_key - register: _preserved_enckeys + 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 }}" remote_src: true mode: "0600" - loop: "{{ _preserved_enckeys.results }}" + loop: "{{ _kibana_preserved_enckeys.results }}" when: item.stat.exists 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 - loop: "{{ _preserved_enckeys.results }}" + loop: "{{ _kibana_preserved_enckeys.results }}" when: item.stat.exists 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/main.yml b/roles/kibana/tasks/main.yml index 50a346e6..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 }}" @@ -33,7 +40,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 @@ -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/tasks/restart_and_verify_kibana.yml b/roles/kibana/tasks/restart_and_verify_kibana.yml index c50c1a8e..4210e0ce 100644 --- a/roles/kibana/tasks/restart_and_verify_kibana.yml +++ b/roles/kibana/tasks/restart_and_verify_kibana.yml @@ -1,13 +1,19 @@ --- -- name: Restart and verify Kibana +- name: restart_and_verify_kibana | Restart and verify Kibana block: - - name: Restart Kibana service + - 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 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 +21,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 +36,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/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/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/main.yml b/roles/logstash/tasks/main.yml index 2882cef9..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 @@ -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: @@ -373,5 +373,5 @@ state: started enabled: true when: logstash_enable | bool - register: logstash_freshstart + register: _logstash_freshstart diff --git a/roles/logstash/tasks/restart_and_verify_logstash.yml b/roles/logstash/tasks/restart_and_verify_logstash.yml index b3af5977..65b41ccf 100644 --- a/roles/logstash/tasks/restart_and_verify_logstash.yml +++ b/roles/logstash/tasks/restart_and_verify_logstash.yml @@ -1,14 +1,20 @@ --- -- name: Restart and verify Logstash +- name: restart_and_verify_logstash | Restart and verify Logstash block: - - name: Restart Logstash service + - 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 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 +23,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/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 diff --git a/roles/repos/tasks/debian.yml b/roles/repos/tasks/debian.yml index 022af53e..e13db7c0 100644 --- a/roles/repos/tasks/debian.yml +++ b/roles/repos/tasks/debian.yml @@ -1,26 +1,26 @@ --- -- name: Ensure gpg exists, for signing keys +- name: debian | Ensure gpg exists, for signing keys ansible.builtin.apt: name: - 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 -- 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 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 -- 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 dd0d2204..5a184808 100644 --- a/roles/repos/tasks/redhat.yml +++ b/roles/repos/tasks/redhat.yml @@ -3,55 +3,55 @@ # 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 - 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 -- 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: _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 + - name: redhat | 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 + - name: redhat | 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) +- 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