From abb71c587bdaea6016cf7b0198645a5a950bb694 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20K=C3=A4b?= <alexander.kaeb@h-da.de>
Date: Fri, 10 Mar 2023 14:15:38 +0100
Subject: [PATCH] feat(*): Update tasks to allow for a single host as CA cert
 source

Modify the role tasks to allow for a single host to be used for CA
cert download instead of separate hosts for the sidecar and graylog
CA certs. Furthermore, use ansible tempfile module instead of
predictable tempdir for cert creation.

Implements #2
---
 README.md                | 26 +++++++++++++---
 defaults/main.yml        |  1 -
 tasks/filebeat.yml       | 66 +++++++++++++++++++---------------------
 tasks/main.yml           | 63 ++++++++++++++++++++++++++++++++++----
 tasks/node-certs.yml     | 26 ++++++++--------
 templates/sidecar.yml.j2 |  2 +-
 6 files changed, 126 insertions(+), 58 deletions(-)

diff --git a/README.md b/README.md
index a6be0f6..a09500c 100644
--- a/README.md
+++ b/README.md
@@ -35,11 +35,22 @@ graylog_sidecar_server_api_token:
 ## Node Certificates
 
 For node certificates to be generated you will need to create an additional host group
-named `sidecar-ca` with a single host, that stores the CA certificate that should be
-used for client certificate generation.
+named `sidecar-ca` with a single host (or multiple but only the first will be used),
+that stores the CA certificate that should be used for client certificate generation.
 
-The CA file must be available at: `/etc/graylog/sidecar/sidecar-ca.pem`
-The CA file's key must be available at: `/etc/graylog/sidecar/sidecar-ca.key`
+In addition, the CA certificate that was used to create the certificates for the Graylog
+nodes themselves must also be available to be distributed, as it is required for TLS
+communication of `filebeat` for example. Therefore, make the graylog nodes available
+via a host group called `graylog-nodes`.
+
+You may also use a completely separate host to store the CA files for Graylog and the
+Sidecar service. If this is the case, you need to set the `use_central_ca_host` variable
+to `true` and provide a host group called `ca-store`. The other groups mentioned earlier
+may be omitted.
+
+The log node CA file must be available at: `/etc/graylog/graylog-ca.pem`
+The sidecar CA file must be available at: `/etc/graylog/sidecar/sidecar-ca.pem`
+The sidecar CA file's key must be available at: `/etc/graylog/sidecar/sidecar-ca.key`
 
 The location of the files can be configured via variable. The name of the files however
 must be as specified. The following variables are available in regard to the node
@@ -49,9 +60,16 @@ certificates.
 # Whether to generate node certificates (default: true)
 generate_node_certs: true
 
+# Whether to use a central host to obtain the required certificates from (default: false)
+use_central_ca_host: false
+
 # The local directory where certs are stored before being uploaded
 tmp_cert_dir: "/tmp/graylog-sidecar-certs"
 
+# The path where the CA certificate of the graylog nodes should be
+# fetched from the remote machine specified in the 'graylog-nodes' host group
+gl_node_ca_path: "/etc/graylog/"
+
 # The path where the CA certificate and key should be fetched from
 # the remote machine specified in the 'sidecar-ca' host group
 gl_sidecar_ca_path: "/etc/graylog/sidecar"
diff --git a/defaults/main.yml b/defaults/main.yml
index bc9969e..74e7a33 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -12,7 +12,6 @@ filebeat_repo_urls:
 
 # --- OTHER ---
 generate_node_certs: true
-tmp_cert_dir: "/tmp/graylog-sidecar-certs" # local directory
 gl_sidecar_ca_path: "/etc/graylog/sidecar"
 sidecar_cert_dir: "/etc/graylog/sidecar"
 cert_valid_days: 1095
diff --git a/tasks/filebeat.yml b/tasks/filebeat.yml
index 174651e..e94f359 100644
--- a/tasks/filebeat.yml
+++ b/tasks/filebeat.yml
@@ -1,39 +1,37 @@
-- name: Run filebeat tasks
-  when: (groups['sidecar-ca'] is defined | ternary(inventory_hostname not in groups['sidecar-ca'], true))
+---
+- name: Add filebeat repository (Debian | Ubuntu)
+  become: true
+  when: ansible_os_family == 'Debian'
   block:
-    - name: Add filebeat repository (Debian | Ubuntu)
-      become: true
-      when: ansible_os_family == 'Debian'
-      block:
-        - name: Ensure Apt Can Use Https
-          ansible.builtin.apt:
-            name: apt-transport-https
-            state: present
-
-        - name: Ensure ES Signing Key Is Present
-          ansible.builtin.apt_key:
-            url: 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
-            id: '46095ACC8548582C1A2699A9D27D666CD88E42B4'
-            state: present^
+    - name: Ensure Apt Can Use Https
+      ansible.builtin.apt:
+        name: apt-transport-https
+        state: present
 
-        - name: Ensure ES Repo Is Enabled
-          ansible.builtin.apt_repository:
-            repo: "deb {{ filebeat_repo_urls['Debian'] }} stable main"
-            state: present
+    - name: Ensure ES Signing Key Is Present
+      ansible.builtin.apt_key:
+        url: 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
+        id: '46095ACC8548582C1A2699A9D27D666CD88E42B4'
+        state: present^
 
-    - name: Add filebeat repository (RedHat)
-      ansible.builtin.yum_repository:
-        name: elastic-8.x
-        description: Elastic Yum Repo 8.x
-        baseurl: "{{ filebeat_repo_urls['RedHat'] }}"
-        gpgcheck: true
-        gpgkey: 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
+    - name: Ensure ES Repo Is Enabled
+      ansible.builtin.apt_repository:
+        repo: "deb {{ filebeat_repo_urls['Debian'] }} stable main"
         state: present
-      when: ansible_os_family == 'RedHat'
-      become: true
 
-    - name: Install filebeat package
-      ansible.builtin.package:
-        name: filebeat
-        state: present
-      become: true
+- name: Add filebeat repository (RedHat)
+  ansible.builtin.yum_repository:
+    name: elastic-8.x
+    description: Elastic Yum Repo 8.x
+    baseurl: "{{ filebeat_repo_urls['RedHat'] }}"
+    gpgcheck: true
+    gpgkey: 'https://artifacts.elastic.co/GPG-KEY-elasticsearch'
+    state: present
+  when: ansible_os_family == 'RedHat'
+  become: true
+
+- name: Install filebeat package
+  ansible.builtin.package:
+    name: filebeat
+    state: present
+  become: true
diff --git a/tasks/main.yml b/tasks/main.yml
index 25aa826..a372e10 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -5,13 +5,64 @@
   changed_when: false
   become: true
 
-- name: Include sidecar tasks
-  ansible.builtin.include_tasks: sidecar.yml
-  when: (groups['sidecar-ca'] is defined | ternary(inventory_hostname not in groups['sidecar-ca'], true))
+- name: Verify that host groups are available if not using a single host
+  when: not use_central_ca_host
+  block:
+    - name: Fail if 'sidecar-ca' host group is missing # noqa: run_once[task]
+      ansible.builtin.fail:
+        msg: "Please add a host group 'sidecar-ca' with the host(s) storing the CA file first"
+      run_once: true
+      when: "not (groups['sidecar-ca'] is defined)"
 
-- name: Include filebeat tasks
-  ansible.builtin.import_tasks: filebeat.yml
-  when: install_filebeat and (groups['sidecar-ca'] is defined | ternary(inventory_hostname not in groups['sidecar-ca'], true))
+    - name: Fail if 'graylog-nodes' host group is missing # noqa: run_once[task]
+      ansible.builtin.fail:
+        msg: "Please add a host group 'graylog-nodes' with the host(s) storing the log node CA file first"
+      run_once: true
+      when: "not (groups['graylog-nodes'] is defined)"
+
+- name: Fail if 'ca-store' host group is missing while using opetion 'use_central_ca_host' # noqa: run_once[task]
+  ansible.builtin.fail:
+    msg: "Please add a host group 'sidecar-ca' with the host(s) storing the CA file first"
+  run_once: true
+  when: "(not (groups['ca-store'] is defined)) and use_central_ca_host"
+
+- name: Include tasks when not using single ca-host
+  when: not use_central_ca_host
+  block:
+    - name: Include sidecar tasks (when not using a single ca store)
+      ansible.builtin.include_tasks: sidecar.yml
+      when: >
+        (inventory_hostname not in groups['sidecar-ca']) and
+        (inventory_hostname not in groups['graylog-nodes'])
+
+    - name: Include filebeat tasks
+      ansible.builtin.include_tasks: filebeat.yml
+      when: >
+        install_filebeat and
+        (inventory_hostname not in groups['sidecar-ca']) and
+        (inventory_hostname not in groups['graylog-nodes'])
+
+- name: Include tasks when using single ca-host
+  when: use_central_ca_host
+  block:
+    - name: Include sidecar tasks (when using a single ca store)
+      ansible.builtin.include_tasks: sidecar.yml
+      when: >
+        ((groups['sidecar-ca'] is defined) and (groups['graylog-nodes'] is defined) | ternary(
+          (inventory_hostname not in groups['sidecar-ca']) and
+          (inventory_hostname not in groups['graylog-nodes'])
+        , true)) and
+        (inventory_hostname not in groups['ca-store'])
+
+    - name: Include filebeat tasks
+      ansible.builtin.include_tasks: filebeat.yml
+      when: >
+        install_filebeat and
+        ((groups['sidecar-ca'] is defined) and (groups['graylog-nodes'] is defined) | ternary(
+          (inventory_hostname not in groups['sidecar-ca']) and
+          (inventory_hostname not in groups['graylog-nodes'])
+        , true)) and
+        (inventory_hostname not in groups['ca-store'])
 
 - name: Switch back to default policy
   ansible.builtin.command:
diff --git a/tasks/node-certs.yml b/tasks/node-certs.yml
index 2f19039..21840ad 100644
--- a/tasks/node-certs.yml
+++ b/tasks/node-certs.yml
@@ -1,17 +1,11 @@
 ---
-- name: Fail if 'sidecar-ca' host group is missing # noqa: run_once[task]
-  ansible.builtin.fail:
-    msg: "Please add a host group 'sidecar-ca' with the host(s) storing the CA file first"
-  run_once: true
-  when: "not (groups['sidecar-ca'] is defined)"
-
 - name: Node Certificates | Create temporary directopry for certificates # noqa: run_once[task]
-  ansible.builtin.file:
-    path: "{{ tmp_cert_dir }}"
+  ansible.builtin.tempfile:
     state: directory
-    mode: 0755
+    prefix: "graylog."
   run_once: true
   delegate_to: localhost
+  register: tmp_cert_dir
 
 - name: Node Certificates | Fetch Sidecar CA Cert
   ansible.builtin.fetch:
@@ -21,13 +15,21 @@
   with_items:
     - "{{ gl_sidecar_ca_path }}/sidecar-ca.pem"
     - "{{ gl_sidecar_ca_path }}/sidecar-ca.key"
-  delegate_to: "{{ groups['sidecar-ca'] | first }}"
+  delegate_to: "{{ groups[use_central_ca_host | bool | ternary('ca-store', 'sidecar-ca')] | first }}"
+  become: true
+  run_once: true
+
+- name: Node Certificates | Fetch Graylog Node CA Cert
+  ansible.builtin.fetch:
+    src: "{{ gl_node_ca_path }}/graylog-ca.pem"
+    dest: "{{ tmp_cert_dir }}/"
+    flat: true
+  delegate_to: "{{ groups[use_central_ca_host | bool | ternary('ca-store', 'graylog-nodes')] | first }}"
   become: true
   run_once: true
 
 - name: Node Certificates
   delegate_to: localhost
-  when: (groups['sidecar-ca'] is defined | ternary(inventory_hostname not in groups['sidecar-ca'], true))
   block:
     - name: Node Certificates | Generate private keys
       community.crypto.openssl_privatekey:
@@ -55,7 +57,6 @@
 
 - name: Node Certificates | Copy Certificates
   become: true
-  when: (groups['sidecar-ca'] is defined | ternary(inventory_hostname not in groups['sidecar-ca'], true))
   block:
     - name: Node Certificates | Copy Node certificates
       ansible.builtin.copy:
@@ -66,3 +67,4 @@
         - { file: "sidecar-{{ inventory_hostname }}.key", mode: "0600" }
         - { file: "sidecar-{{ inventory_hostname }}.pem", mode: "0644" }
         - { file: "sidecar-ca.pem", mode: "0644" }
+        - { file: "graylog-ca.pem", mode: "0644" }
diff --git a/templates/sidecar.yml.j2 b/templates/sidecar.yml.j2
index 82b2c31..f55db49 100644
--- a/templates/sidecar.yml.j2
+++ b/templates/sidecar.yml.j2
@@ -2,4 +2,4 @@
 
 server_url: {{ graylog_sidecar_server_url }}
 server_api_token: {{ graylog_sidecar_server_api_token }}
-node_id: {{ graylog_sidecar_node_id }}
\ No newline at end of file
+node_id: {{ graylog_sidecar_node_id }}
-- 
GitLab