From b367c8f72c8ff5e5aeb5f02ef0fbf76ac4080d5c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= <valtri@civ.zcu.cz>
Date: Tue, 27 Aug 2024 15:09:06 +0000
Subject: [PATCH] Logging using Fluent Bit

* outputs: GELF, Elasticsearch
* generic options from Vault
* tuned index for Elasticsearch
* TLS support including client certificate
* cluser name and tag in logs
---
 cesnet-central/deploy.sh                      |   2 +
 cesnet-central/playbooks/security-logs.yaml   |   1 +
 .../templates/fluent-bit-secrets.yaml.j2      |   1 +
 .../playbooks/templates/fluent-bit.yaml.j2    |   1 +
 common/playbooks/security-logs.yaml           |  91 ++++++++++++++
 .../templates/fluent-bit-secrets.yaml.j2      |  19 +++
 common/playbooks/templates/fluent-bit.yaml.j2 | 115 ++++++++++++++++++
 staging1/deploy.sh                            |   1 +
 staging1/playbooks/security-logs.yaml         |   1 +
 .../templates/fluent-bit-secrets.yaml.j2      |   1 +
 .../playbooks/templates/fluent-bit.yaml.j2    |   1 +
 11 files changed, 234 insertions(+)
 create mode 120000 cesnet-central/playbooks/security-logs.yaml
 create mode 120000 cesnet-central/playbooks/templates/fluent-bit-secrets.yaml.j2
 create mode 120000 cesnet-central/playbooks/templates/fluent-bit.yaml.j2
 create mode 100644 common/playbooks/security-logs.yaml
 create mode 100644 common/playbooks/templates/fluent-bit-secrets.yaml.j2
 create mode 100644 common/playbooks/templates/fluent-bit.yaml.j2
 create mode 120000 staging1/playbooks/security-logs.yaml
 create mode 120000 staging1/playbooks/templates/fluent-bit-secrets.yaml.j2
 create mode 120000 staging1/playbooks/templates/fluent-bit.yaml.j2

diff --git a/cesnet-central/deploy.sh b/cesnet-central/deploy.sh
index d5606cf..764f9c6 100755
--- a/cesnet-central/deploy.sh
+++ b/cesnet-central/deploy.sh
@@ -27,3 +27,5 @@ ansible-playbook playbooks/cvmfs.yaml
 
 # wait for finish
 while ansible -m command -a 'kubectl get pods --all-namespaces' master | tail -n +3 | grep -v ' Running '; do sleep 5; done
+
+ansible-playbook playbooks/security-logs.yaml
diff --git a/cesnet-central/playbooks/security-logs.yaml b/cesnet-central/playbooks/security-logs.yaml
new file mode 120000
index 0000000..0149b19
--- /dev/null
+++ b/cesnet-central/playbooks/security-logs.yaml
@@ -0,0 +1 @@
+../../common/playbooks/security-logs.yaml
\ No newline at end of file
diff --git a/cesnet-central/playbooks/templates/fluent-bit-secrets.yaml.j2 b/cesnet-central/playbooks/templates/fluent-bit-secrets.yaml.j2
new file mode 120000
index 0000000..c64dcee
--- /dev/null
+++ b/cesnet-central/playbooks/templates/fluent-bit-secrets.yaml.j2
@@ -0,0 +1 @@
+../../../common/playbooks/templates/fluent-bit-secrets.yaml.j2
\ No newline at end of file
diff --git a/cesnet-central/playbooks/templates/fluent-bit.yaml.j2 b/cesnet-central/playbooks/templates/fluent-bit.yaml.j2
new file mode 120000
index 0000000..4ccab19
--- /dev/null
+++ b/cesnet-central/playbooks/templates/fluent-bit.yaml.j2
@@ -0,0 +1 @@
+../../../common/playbooks/templates/fluent-bit.yaml.j2
\ No newline at end of file
diff --git a/common/playbooks/security-logs.yaml b/common/playbooks/security-logs.yaml
new file mode 100644
index 0000000..ee20f2e
--- /dev/null
+++ b/common/playbooks/security-logs.yaml
@@ -0,0 +1,91 @@
+---
+#
+# Secrets in "/{{ site_name }}":
+#
+# * fluent_es_host (optional): enable elasticsearch output
+# * fluent_es_index: Index option (when used, 'node-' or 'kube-' prefix is added)
+# * fluent_es_*: elasticsearch output additional options (tls, http_user, ...)
+#
+# * fluent_gelf_host (optional): enable graylog output
+# * fluent_gelf_mode (optional, "tls", "tcp", or "udp")
+# * fluent_gelf_*: graylog output additional options
+#
+# Secrets in "/{{ site_name }}" related to TLS:
+#
+# * fluent_secrets_ca (optional): propagated to /secrets/fluent.ca
+# * fluent_secrets_crt (optional): propagated to /secrets/fluent.crt
+# * fluent_secrets_key (optional): propagated to /secrets/fluent.key
+# * fluent_*_tls (optional): "On"
+# * fluent_*_tls.ca_file (optional): "/secrets/fluent.ca"
+# * fluent_*_tls.crt_file (optional): "/secrets/fluent.crt"
+# * fluent_*_tls.key_file (optional): "/secrets/fluent.key"
+# * fluent_*_tls.key_password (optional)
+# * fluent_*_tls.verify (optional)
+# * fluent_*_tls.verify_hostname (optional): "On"
+# * fluent_*_tls.vhost (optional)
+#
+# Self-sign certificate HOWTO (for TLS clients): [1]
+#
+# openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout self_signed.key -out self_signed.crt -subj "/CN=test.host.net"
+#
+# [1] https://docs.fluentbit.io/manual/administration/transport-security#tips-and-tricks
+#
+# For GELF: add self_signed.crt to authorized client certificates directory.
+#
+- name: Fluent Bit Configuration
+  hosts: master
+  become: true
+  vars:
+    namespace: fluent-bit
+    version: "0.47.7" # app 3.1.6
+  tasks:
+    - name: Configure helm repo
+      shell: |-
+        helm repo add fluent https://fluent.github.io/helm-charts
+        helm repo update
+      when: "'fluent' not in ansible_local.helm_repos | map(attribute='name') | list"
+    - name: Get Secrets from Vault
+      set_fact:
+        secrets: "{{ lookup('community.hashi_vault.hashi_vault', vault_mount_point + '/site-' + site_name,
+          token_validate=false) }}"
+    - name: Debug Secrets
+      debug:
+        msg: "{{ item.key }} = {{ item.value }}"
+      loop: "{{ secrets | dict2items }}"
+    - name: Set Fluent TLS Fact From Secrets
+      set_fact:
+        fluent_has_tls: "{{ 'fluent_secrets_ca' in secrets or 'fluent_secrets_crt' in secrets or 'fluent_secrets_key' in  secrets }}"
+    - name: Create Fluent TLS Secrets File
+      template:
+        src: templates/fluent-bit-secrets.yaml.j2
+        dest: /tmp/fluent-bit-secrets.yaml
+        mode: 0600
+      when: fluent_has_tls
+    - name: Create Fluent TLS Secrets Object
+      command:
+        cmd: kubectl apply -f /tmp/fluent-bit-secrets.yaml
+      environment:
+        KUBECONFIG: /etc/kubernetes/admin.conf
+        PATH: /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+      when: fluent_has_tls
+    - name: Fluent Bit Configuration
+      template:
+        src: templates/fluent-bit.yaml.j2
+        dest: /tmp/fluent-bit.yaml
+        mode: 0600
+    - name: Deploy/upgrade Fluent Bit
+      shell: |-
+        helm status --namespace {{ namespace }} fluent-bit
+        if [ $? -ne 0 ]; then
+            helm install --create-namespace --namespace {{ namespace }} \
+                -f /tmp/fluent-bit.yaml \
+                fluent-bit fluent/fluent-bit
+        else
+            helm upgrade --namespace {{ namespace }} \
+                -f /tmp/fluent-bit.yaml \
+                fluent-bit fluent/fluent-bit
+        fi
+      environment:
+        KUBECONFIG: /etc/kubernetes/admin.conf
+        PATH: /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+      when: true
diff --git a/common/playbooks/templates/fluent-bit-secrets.yaml.j2 b/common/playbooks/templates/fluent-bit-secrets.yaml.j2
new file mode 100644
index 0000000..6c2dcba
--- /dev/null
+++ b/common/playbooks/templates/fluent-bit-secrets.yaml.j2
@@ -0,0 +1,19 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+  name: fluent-output-tls
+  namespace: {{ namespace }}
+stringData:
+{% if 'fluent_secrets_ca' in secrets %}
+    ca.crt: |
+      {{ secrets['fluent_secrets_ca'] | indent(6) }}
+{%- endif %}
+{% if 'fluent_secrets_crt' in secrets %}
+    fluent.crt: |
+      {{ secrets['fluent_secrets_crt'] | indent(6) }}
+{%- endif %}
+{% if 'fluent_secrets_key' in secrets %}
+    fluent.key: |
+      {{ secrets['fluent_secrets_key'] | indent(6) }}
+{%- endif %}
diff --git a/common/playbooks/templates/fluent-bit.yaml.j2 b/common/playbooks/templates/fluent-bit.yaml.j2
new file mode 100644
index 0000000..e16921d
--- /dev/null
+++ b/common/playbooks/templates/fluent-bit.yaml.j2
@@ -0,0 +1,115 @@
+---
+config:
+  inputs: |
+    [INPUT]
+        Name                     tail
+        Path                     /var/log/containers/*.log
+        DB                       /var/log/fluent-bit.db
+        multiline.parser         docker, cri
+        Tag                      kube.*
+        Mem_Buf_Limit            5MB
+        # Skip_Long_Lines On
+
+    [INPUT]
+        Name systemd
+        Tag host.*
+        Systemd_Filter _SYSTEMD_UNIT=kubelet.service
+        Read_From_Tail On
+
+  filters: |
+    [FILTER]
+        Name                    kubernetes
+        Match                   kube.*
+        Merge_Log_Key           log
+        Merge_Log               On
+        Keep_Log                Off
+        Annotations             Off
+        Labels                  Off
+        K8S-Logging.Parser      On
+        K8S-Logging.Exclude     On
+
+    [FILTER]
+        Name                    modify
+        Match                   *
+        Add                     cluster {{ site_name }}
+        Add                     tag eosc
+
+  outputs: |
+    # [OUTPUT]
+    #     Name                    stdout
+    #     Match                   host.*
+
+    # [OUTPUT]
+    #     Name                    stdout
+    #     Match                   kube.*
+
+{% if 'fluent_es_host' in secrets %}
+    [OUTPUT]
+        Name                    es
+        Match                   kube.*
+{% for key, value in secrets.items() %}
+{% if key | regex_search('^fluent_es_') %}
+{% if key == 'fluent_es_index' %}
+{% set value = ['kube', value | default(omit)] | join('-') %}
+{% endif %}
+        {{ '%-23s' | format(key | regex_replace('^fluent_es_', '') | title) }} {{ value }}
+{% endif %}
+{% endfor %}
+
+    [OUTPUT]
+        Name                    es
+        Match                   node.*
+{% for key, value in secrets.items() %}
+{% if key | regex_search('^fluent_es_') %}
+{% if key == 'fluent_es_index' %}
+{% set value = ['node', value | default(omit)] | join('-') %}
+{% endif %}
+        {{ '%-23s' | format(key | regex_replace('^fluent_es_', '') | title) }} {{ value }}
+{% endif %}
+{% endfor %}
+
+{% endif %}
+{% if 'fluent_gelf_host' in secrets %}
+    [OUTPUT]
+        Name                    gelf
+        Match                   kube.*
+        Gelf_Host_Key           host
+        Gelf_Short_Message_Key  log
+{% for key, value in secrets.items() %}
+{% if key | regex_search('^fluent_gelf_') %}
+        {{ '%-23s' | format(key | regex_replace('^fluent_gelf_', '')) | title }} {{ value }}
+{% endif %}
+{% endfor %}
+
+    [OUTPUT]
+        Name                    gelf
+        Match                   host.*
+        Gelf_Host_Key           _HOSTNAME
+        Gelf_Short_Message_Key  MESSAGE
+{% for key, value in secrets.items() %}
+{% if key | regex_search('^fluent_gelf_') %}
+        {{ '%-23s' | format(key | regex_replace('^fluent_gelf_', '')) | title }} {{ value }}
+{% endif %}
+{% endfor %}
+
+{% endif %}
+{% if fluent_has_tls %}
+extraVolumes:
+  - name: fluent-output-tls
+    secret:
+      secretName: fluent-output-tls
+
+extraVolumeMounts:
+  - name: fluent-output-tls
+    readOnly: true
+    mountPath: "/secrets"
+
+{% endif %}
+# daemonset runnable on control plane nodes
+tolerations:
+  - key: node-role.kubernetes.io/control-plane
+    operator: Exists
+    effect: NoSchedule
+  - key: node-role.kubernetes.io/master
+    operator: Exists
+    effect: NoSchedule
diff --git a/staging1/deploy.sh b/staging1/deploy.sh
index 68cffd8..d509ed3 100755
--- a/staging1/deploy.sh
+++ b/staging1/deploy.sh
@@ -55,3 +55,4 @@ ansible-playbook playbooks/cvmfs.yaml
 while ansible -m command -a 'kubectl get pods --all-namespaces' master | tail -n +3 | grep -v ' Running '; do sleep 5; done
 
 ansible-playbook playbooks/security-assets.yaml
+ansible-playbook playbooks/security-logs.yaml
diff --git a/staging1/playbooks/security-logs.yaml b/staging1/playbooks/security-logs.yaml
new file mode 120000
index 0000000..0149b19
--- /dev/null
+++ b/staging1/playbooks/security-logs.yaml
@@ -0,0 +1 @@
+../../common/playbooks/security-logs.yaml
\ No newline at end of file
diff --git a/staging1/playbooks/templates/fluent-bit-secrets.yaml.j2 b/staging1/playbooks/templates/fluent-bit-secrets.yaml.j2
new file mode 120000
index 0000000..86f2455
--- /dev/null
+++ b/staging1/playbooks/templates/fluent-bit-secrets.yaml.j2
@@ -0,0 +1 @@
+/home/valtri/notebooks-operations.eosc/common/playbooks/templates/fluent-bit-secrets.yaml.j2
\ No newline at end of file
diff --git a/staging1/playbooks/templates/fluent-bit.yaml.j2 b/staging1/playbooks/templates/fluent-bit.yaml.j2
new file mode 120000
index 0000000..4ccab19
--- /dev/null
+++ b/staging1/playbooks/templates/fluent-bit.yaml.j2
@@ -0,0 +1 @@
+../../../common/playbooks/templates/fluent-bit.yaml.j2
\ No newline at end of file
-- 
GitLab