From 5bda80152d8318779157cceaf4fa1b2427173815 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20K=C3=A4b?= <alexander.kaeb@h-da.de>
Date: Thu, 23 Feb 2023 13:57:55 +0100
Subject: [PATCH] feat(node-certs): Add tasks to generate node certificates

As some communication to the graylog instance needs to be encrypted
using node certificates add tasks that will generate certificates
for the nodes the sidecar service will be installed on.

Implements #1
---
 README.md            | 31 ++++++++++++++++++++
 defaults/main.yml    |  7 +++++
 tasks/main.yml       |  4 +++
 tasks/node-certs.yml | 70 ++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 112 insertions(+)
 create mode 100644 tasks/node-certs.yml

diff --git a/README.md b/README.md
index 30c9eb1..40aa4b7 100644
--- a/README.md
+++ b/README.md
@@ -30,4 +30,35 @@ graylog_sidecar_server_url:
 
 # SECRET: token to be used for sidecars
 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.
+
+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`
+
+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
+certificates.
+
+```yaml
+# Whether to generate node certificates (default: true)
+generate_node_certs: true
+
+# The local directory where certs are stored before being uploaded
+tmp_cert_dir: "/tmp/graylog-sidecar-certs"
+
+# 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"
+
+# The directory where client certs should be stored at
+sidecar_cert_dir: "/etc/graylog/sidecar"
+
+# The time in days the client certificates will be valid
+cert_valid_days: 1095
 ```
\ No newline at end of file
diff --git a/defaults/main.yml b/defaults/main.yml
index 173f5cc..5f1d297 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -7,3 +7,10 @@ install_filebeat: true
 filebeat_repo_urls:
   "RedHat": "https://artifacts.elastic.co/packages/oss-8.x/yum"
   "Debian": "https://artifacts.elastic.co/packages/oss-8.x/apt"
+
+# --- 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
\ No newline at end of file
diff --git a/tasks/main.yml b/tasks/main.yml
index 0ecd2f4..5e79877 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -65,3 +65,7 @@
     cmd: update-crypto-policies --set DEFAULT
   changed_when: false
   become: true
+
+- name: Generate Node certificates
+  ansible.builtin.import_tasks: node-certs.yml
+  when: generate_node_certs
diff --git a/tasks/node-certs.yml b/tasks/node-certs.yml
new file mode 100644
index 0000000..462bb21
--- /dev/null
+++ b/tasks/node-certs.yml
@@ -0,0 +1,70 @@
+---
+- 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 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 }}"
+    state: directory
+    mode: 0755
+  run_once: true
+  delegate_to: localhost
+
+- name: Node Certificates | Fetch Sidecar CA Cert
+  ansible.builtin.fetch:
+    src: "{{ item }}"
+    dest: "{{ tmp_cert_dir }}/"
+    flat: true
+  with_items:
+    - "{{ gl_sidecar_ca_path }}/gl-sidecar.pem"
+    - "{{ gl_sidecar_ca_path }}/gl-sidecar.key"
+  delegate_to: "{{ groups['sidecar-ca'] | first }}"
+  become: true
+  run_once: true
+
+- name: Node Certificates
+  delegate_to: localhost
+  block:
+    - name: Node Certificates | Generate private keys
+      community.crypto.openssl_privatekey:
+        path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.key"
+        return_content: true
+        state: present
+
+    - name: Node Certificates | Create CSRs
+      community.crypto.openssl_csr_pipe:
+        privatekey_path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.key"
+        common_name: "{{ ansible_fqdn }}"  # CN
+        subject_alt_name:
+          - "DNS:{{ inventory_hostname }}"
+          - "DNS:{{ ansible_fqdn }}"
+          - "IP:{{ ansible_default_ipv6.address }}"
+          # - "IP:{{ ansible_default_ipv4.address }}"
+      register: "node_csr"
+
+    - name: Node Certificates | Generate Certificates
+      community.crypto.x509_certificate:
+        path: "{{ tmp_cert_dir }}/sidecar-{{ inventory_hostname }}.pem"
+        csr_content: "{{ node_csr.csr }}"
+        provider: ownca
+        ownca_path: "{{ tmp_cert_dir }}/gl-sidecar.pem"
+        ownca_privatekey_path: "{{ tmp_cert_dir }}/gl-sidecar.key"
+        ownca_privatekey_passphrase: "{{ ca_passphrase }}"
+        ownca_not_after: "+{{ cert_valid_days }}d"
+        ownca_not_before: "-1d"  # valid since yesterday
+
+- name: Node Certificates | Copy Certificates
+  become: true
+  block:
+    - name: Node Certificates | Copy Node certificates
+      ansible.builtin.copy:
+        src: "{{ tmp_cert_dir }}/{{ item }}"
+        dest: "{{ sidecar_cert_dir }}"
+        mode: 0600
+      with_items:
+        - "sidecar-{{ inventory_hostname }}.key"
+        - "sidecar-{{ inventory_hostname }}.pem"
+        - "gl-sidecar.pem"
\ No newline at end of file
-- 
GitLab