From 5da83db87431c7b87ae7fc8be7db0a70a869f150 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Pawe=C5=82=20Krupa?= <pawel@krupa.net.pl>
Date: Sun, 31 May 2020 16:53:35 +0200
Subject: [PATCH] *: add support for node_exporter TLS settings (#156)

[minor] release
---
 README.md                          | 33 ++++++++++++++++++++++++++++++
 defaults/main.yml                  |  6 ++++++
 molecule/alternative/playbook.yml  | 24 ++++++++++++++++++++++
 molecule/alternative/prepare.yml   | 22 +++++++++++++++++++-
 tasks/configure.yml                | 26 +++++++++++++++++++++--
 tasks/preflight.yml                | 24 ++++++++++++++++++++++
 templates/config.yaml.j2           | 18 ++++++++++++++++
 templates/node_exporter.service.j2 |  3 +++
 test-requirements.txt              |  1 +
 9 files changed, 154 insertions(+), 3 deletions(-)
 create mode 100644 templates/config.yaml.j2

diff --git a/README.md b/README.md
index 899e450..848b338 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,9 @@ All variables which can be overridden are stored in [defaults/main.yml](defaults
 | `node_exporter_enabled_collectors` | [ systemd, textfile ] | List of additionally enabled collectors. It adds collectors to [those enabled by default](https://github.com/prometheus/node_exporter#enabled-by-default) |
 | `node_exporter_disabled_collectors` | [] | List of disabled collectors. By default node_exporter disables collectors listed [here](https://github.com/prometheus/node_exporter#disabled-by-default). |
 | `node_exporter_textfile_dir` | "/var/lib/node_exporter" | Directory used by the [Textfile Collector](https://github.com/prometheus/node_exporter#textfile-collector). To get permissions to write metrics in this directory, users must be in `node-exp` system group.
+| `node_exporter_tls_server_config` | {} | Configuration for TLS authentication. Keys and values are the same as in [node_exporter docs](https://github.com/prometheus/node_exporter/blob/master/https/README.md#sample-config). |
+| `node_exporter_http_server_config` | {} | Config for HTTP/2 support. Keys and values are the same as in [node_exporter docs](https://github.com/prometheus/node_exporter/blob/master/https/README.md#sample-config). |
+| `node_exporter_basic_auth_users` | {} | Dictionary of users and password for basic authentication. Passwords are automatically hashed with bcrypt. |
 
 ## Example
 
@@ -40,6 +43,36 @@ Use it in a playbook as follows:
     - cloudalchemy.node-exporter
 ```
 
+### TLS config
+
+Before running node_exporter role, user needs to provision their own certificate and key.
+```yaml
+- hosts: all
+  pre_tasks:
+    - name: Create node_exporter cert dir
+      file:
+        path: "/etc/node_exporter"
+        state: directory
+        owner: root
+        group: root
+
+    - name: Create cert and key
+      openssl_certificate:
+        path: /etc/node_exporter/tls.cert
+        csr_path: /etc/node_exporter/tls.csr
+        privatekey_path: /etc/node_exporter/tls.key
+        provider: selfsigned
+  roles:
+    - cloudalchemy.node-exporter
+  vars:
+    node_exporter_tls_server_config:
+      cert_file: /etc/node_exporter/tls.cert
+      key_file: /etc/node_exporter/tls.key
+    node_exporter_basic_auth_users:
+      randomuser: examplepassword 
+```
+
+
 ### Demo site
 
 We provide demo site for full monitoring solution based on prometheus and grafana. Repository with code and links to running instances is [available on github](https://github.com/cloudalchemy/demo-site) and site is hosted on [DigitalOcean](https://digitalocean.com).
diff --git a/defaults/main.yml b/defaults/main.yml
index d520912..5217234 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -5,6 +5,12 @@ node_exporter_web_listen_address: "0.0.0.0:9100"
 
 node_exporter_textfile_dir: "/var/lib/node_exporter"
 
+node_exporter_tls_server_config: {}
+
+node_exporter_http_server_config: {}
+
+node_exporter_basic_auth_users: {}
+
 node_exporter_enabled_collectors:
   - systemd
   - textfile:
diff --git a/molecule/alternative/playbook.yml b/molecule/alternative/playbook.yml
index ce51111..991a4b8 100644
--- a/molecule/alternative/playbook.yml
+++ b/molecule/alternative/playbook.yml
@@ -4,6 +4,22 @@
   any_errors_fatal: true
   roles:
     - ansible-node-exporter
+  pre_tasks:
+    - name: Create node_exporter cert dir
+      file:
+        path: "{{ node_exporter_tls_server_config.cert_file | dirname }}"
+        state: directory
+        owner: root
+        group: root
+    - name: Copy cert and key
+      copy:
+        src: "{{ item.src }}"
+        dest: "{{ item.dest }}"
+      with_items:
+        - src: "/tmp/tls.cert"
+          dest: "{{ node_exporter_tls_server_config.cert_file }}"
+        - src: "/tmp/tls.key"
+          dest: "{{ node_exporter_tls_server_config.key_file }}"
   vars:
     node_exporter_binary_local_dir: "/tmp/node_exporter-linux-amd64"
     node_exporter_textfile_dir: ""
@@ -11,3 +27,11 @@
       - entropy
     node_exporter_disabled_collectors:
       - diskstats
+
+    node_exporter_tls_server_config:
+      cert_file: /etc/node_exporter/tls.cert
+      key_file: /etc/node_exporter/tls.key
+    node_exporter_http_server_config:
+      http2: true
+    node_exporter_basic_auth_users:
+      randomuser: examplepassword
diff --git a/molecule/alternative/prepare.yml b/molecule/alternative/prepare.yml
index 2f75da1..665c8a3 100644
--- a/molecule/alternative/prepare.yml
+++ b/molecule/alternative/prepare.yml
@@ -4,7 +4,7 @@
   gather_facts: false
   vars:
     go_arch: amd64
-    node_exporter_version: 0.18.1
+    node_exporter_version: 1.0.0
   tasks:
     - name: Download node_exporter binary to local folder
       become: false
@@ -35,3 +35,23 @@
         state: link
       run_once: true
       check_mode: false
+
+    - name: install pyOpenSSL for certificate generation
+      pip:
+        name: "pyOpenSSL"
+
+    - name: Create private key
+      openssl_privatekey:
+        path: "/tmp/tls.key"
+
+    - name: Create CSR
+      openssl_csr:
+        path: "/tmp/tls.csr"
+        privatekey_path: "/tmp/tls.key"
+
+    - name: Create certificate
+      openssl_certificate:
+        path: "/tmp/tls.cert"
+        csr_path: "/tmp/tls.csr"
+        privatekey_path: "/tmp/tls.key"
+        provider: selfsigned
diff --git a/tasks/configure.yml b/tasks/configure.yml
index 9e9f9bc..0a26903 100644
--- a/tasks/configure.yml
+++ b/tasks/configure.yml
@@ -1,5 +1,5 @@
 ---
-- name: Copy the Node Exporter systemd service file
+- name: Copy the node_exporter systemd service file
   template:
     src: node_exporter.service.j2
     dest: /etc/systemd/system/node_exporter.service
@@ -8,6 +8,28 @@
     mode: 0644
   notify: restart node_exporter
 
+- block:
+    - name: Create node_exporter config directory
+      file:
+        path: "/etc/node_exporter"
+        state: directory
+        owner: root
+        group: root
+        mode: u+rwX,g+rwX,o=rX
+
+    - name: Copy the node_exporter config file
+      template:
+        src: config.yaml.j2
+        dest: /etc/node_exporter/config.yaml
+        owner: root
+        group: root
+        mode: 0644
+      notify: restart node_exporter
+  when:
+    ( node_exporter_tls_server_config | length > 0 ) or
+    ( node_exporter_http_server_config | length > 0 ) or
+    ( node_exporter_basic_auth_users | length > 0 )
+
 - name: Create textfile collector dir
   file:
     path: "{{ node_exporter_textfile_dir }}"
@@ -18,7 +40,7 @@
     mode: u+rwX,g+rwX,o=rX
   when: node_exporter_textfile_dir | length > 0
 
-- name: Allow Node Exporter port in SELinux on RedHat OS family
+- name: Allow node_exporter port in SELinux on RedHat OS family
   seport:
     ports: "{{ node_exporter_web_listen_address.split(':')[-1] }}"
     proto: tcp
diff --git a/tasks/preflight.yml b/tasks/preflight.yml
index 79fff42..3d9e770 100644
--- a/tasks/preflight.yml
+++ b/tasks/preflight.yml
@@ -27,6 +27,30 @@
       - "item not in node_exporter_enabled_collectors"
   with_items: "{{ node_exporter_disabled_collectors }}"
 
+- block:
+    - name: Assert that TLS key and cert path are set
+      assert:
+        that:
+          - "node_exporter_tls_server_config.cert_file is defined"
+          - "node_exporter_tls_server_config.key_file is defined"
+
+    - name: Check existence of TLS cert file
+      stat:
+        path: "{{ node_exporter_tls_server_config.cert_file }}"
+      register: __node_exporter_cert_file
+
+    - name: Check existence of TLS key file
+      stat:
+        path: "{{ node_exporter_tls_server_config.key_file }}"
+      register: __node_exporter_key_file
+
+    - name: Assert that TLS key and cert are present
+      assert:
+        that:
+          - "{{ __node_exporter_cert_file.stat.exists }}"
+          - "{{ __node_exporter_key_file.stat.exists }}"
+  when: node_exporter_tls_server_config | length > 0
+
 - name: Check if node_exporter is installed
   stat:
     path: "{{ _node_exporter_binary_install_dir }}/node_exporter"
diff --git a/templates/config.yaml.j2 b/templates/config.yaml.j2
new file mode 100644
index 0000000..9013722
--- /dev/null
+++ b/templates/config.yaml.j2
@@ -0,0 +1,18 @@
+---
+{{ ansible_managed | comment }}
+{% if node_exporter_tls_server_config | length > 0 %}
+tls_server_config:
+{{ node_exporter_tls_server_config | to_nice_yaml | indent(2, true) }}
+{% endif %}
+
+{% if node_exporter_http_server_config | length > 0 %}
+http_server_config:
+{{ node_exporter_http_server_config | to_nice_yaml | indent(2, true) }}
+{% endif %}
+
+{% if node_exporter_basic_auth_users | length > 0 %}
+basic_auth_users:
+{% for k, v in node_exporter_basic_auth_users.items() %}
+  {{ k }}: {{ v | password_hash('bcrypt', ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' | shuffle(seed=inventory_hostname) | join)[:22], rounds=9) }}
+{% endfor %}
+{% endif %}
diff --git a/templates/node_exporter.service.j2 b/templates/node_exporter.service.j2
index 0e1b53a..ad60a4f 100644
--- a/templates/node_exporter.service.j2
+++ b/templates/node_exporter.service.j2
@@ -24,6 +24,9 @@ ExecStart={{ _node_exporter_binary_install_dir }}/node_exporter \
 {% for collector in node_exporter_disabled_collectors %}
     --no-collector.{{ collector }} \
 {% endfor %}
+{% if node_exporter_tls_server_config | length > 0 or node_exporter_http_server_config | length > 0 or node_exporter_basic_auth_users | length > 0 %}
+    --web.config=/etc/node_exporter/config.yaml
+{% endif %}
     --web.listen-address={{ node_exporter_web_listen_address }}
 
 SyslogIdentifier=node_exporter
diff --git a/test-requirements.txt b/test-requirements.txt
index 7f3b34f..efa5a5c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -4,3 +4,4 @@ ansible-lint>=3.4.0
 testinfra>=1.7.0
 jmespath
 selinux
+passlib
-- 
GitLab