diff --git a/metricbeat/README.md b/metricbeat/README.md index f3f09f727..904e624be 100644 --- a/metricbeat/README.md +++ b/metricbeat/README.md @@ -134,6 +134,7 @@ as a reference. They are also used in the automated testing of this chart. | `priorityClassName` | The name of the [PriorityClass][]. No default is supplied as the PriorityClass must be created first | `""` | | `readinessProbe` | Parameters to pass to readiness [probe][] checks for values such as timeouts and thresholds | see [values.yaml][] | | `replicas` | The replica count for the Metricbeat deployment talking to kube-state-metrics | `1` | +| `secrets` | Allows creating a secret from variables or a file. To add secrets from file, add suffix `.filepath` to the key of the secret key. The value will be encoded to base64. | See [values.yaml][] | | `serviceAccount` | Custom [serviceAccount][] that Metricbeat will use during execution. By default will use the service account created by this chart | `""` | | `serviceAccountAnnotations` | Annotations to be added to the ServiceAccount that is created by this chart. | `{}` | | `terminationGracePeriod` | Termination period (in seconds) to wait before killing Metricbeat pod process on pod shutdown | `30` | diff --git a/metricbeat/templates/secret.yaml b/metricbeat/templates/secret.yaml new file mode 100644 index 000000000..115034f8c --- /dev/null +++ b/metricbeat/templates/secret.yaml @@ -0,0 +1,27 @@ +{{- if .Values.secrets }} +{{- $fullName := include "metricbeat.fullname" . -}} +{{- range .Values.secrets }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-%s" $fullName .name | quote }} + labels: + app: {{ $fullName | quote }} + chart: {{ $.Chart.Name | quote }} + heritage: {{ $.Release.Service | quote }} + release: {{ $.Release.Name | quote }} + {{- range $key, $value := $.Values.labels }} + {{ $key }}: {{ $value | quote }} + {{- end }} +data: +{{- range $key, $val := .value }} + {{- if hasSuffix "filepath" $key }} + {{ $key | replace ".filepath" "" }}: {{ $.Files.Get $val | b64enc | quote }} + {{ else }} + {{ $key }}: {{ $val | b64enc | quote }} + {{- end }} +{{- end }} +type: Opaque +{{- end }} +{{- end }} diff --git a/metricbeat/tests/metricbeat_test.py b/metricbeat/tests/metricbeat_test.py index 7e1d6e5b9..fcc476c4c 100644 --- a/metricbeat/tests/metricbeat_test.py +++ b/metricbeat/tests/metricbeat_test.py @@ -1,5 +1,6 @@ import os import sys +import base64 sys.path.insert(1, os.path.join(sys.path[0], "../../helpers")) from helpers import helm_template @@ -1229,3 +1230,148 @@ def test_custom_kube_stat_metrics_host(): ][1]["value"] == "kube-state-metrics.kube-system:9999" ) + + +def test_adding_a_secret(): + content = "LS1CRUdJTiBgUFJJVkFURSB" + config = """ +secrets: + - name: "env" + value: + ELASTICSEARCH_PASSWORD: {elk_pass} +""".format( + elk_pass=content + ) + content_b64 = base64.b64encode(content.encode("ascii")).decode("ascii") + + r = helm_template(config) + secret_name = name + "-env" + s = r["secret"][secret_name] + assert s["metadata"]["labels"]["app"] == name + assert len(r["secret"]) == 1 + assert len(s["data"]) == 1 + assert s["data"] == {"ELASTICSEARCH_PASSWORD": content_b64} + + +def test_adding_secret_from_file(): + content = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEApCt3ychnqZHsS +DylPFZn55xDaDcWco1oNFdBGzFjw+ +zkuMFMOv7ab+yOFwHeEeAAEkEgy1u +Da1vIscBs1K0kbEFRSqySLuNHWiJp +wK2cI/gJc+S9Qd9Qsn0XGjmjQ6P2p +ot2hvCOtnei998OmDSYORKBq2jiv/ +-----END RSA PRIVATE KEY----- +""" + config = """ +secrets: + - name: "tls" + value: + cert.key.filepath: "secrets/private.key" +""" + content_b64 = base64.b64encode(content.encode("ascii")).decode("ascii") + work_dir = os.path.join(os.path.abspath(os.getcwd()), "secrets") + filename = os.path.join(work_dir, "private.key") + os.makedirs(os.path.dirname(filename), exist_ok=True) + with open(filename, "w") as f: + f.write(content) + + with open(filename, "r") as f: + data = f.read() + assert data == content + + r = helm_template(config) + secret_name = name + "-tls" + s = r["secret"][secret_name] + assert s["metadata"]["labels"]["app"] == name + assert len(r["secret"]) == 1 + assert len(s["data"]) == 1 + assert s["data"] == { + "cert.key": content_b64, + } + + os.remove(filename) + os.rmdir(work_dir) + + +def test_adding_multiple_data_secret(): + content = { + "elk_pass": "LS1CRUdJTiBgUFJJVkFURSB", + "api_key": "ui2CsdUadTiBasRJRkl9tvNnw", + } + config = """ +secrets: + - name: "env" + value: + ELASTICSEARCH_PASSWORD: {elk_pass} + api_key: {api_key} +""".format( + elk_pass=content["elk_pass"], api_key=content["api_key"] + ) + content_b64 = { + "elk_pass": base64.b64encode(content["elk_pass"].encode("ascii")).decode( + "ascii" + ), + "api_key": base64.b64encode(content["api_key"].encode("ascii")).decode("ascii"), + } + + r = helm_template(config) + secret_name = name + "-env" + s = r["secret"][secret_name] + assert s["metadata"]["labels"]["app"] == name + assert len(r["secret"]) == 1 + assert len(s["data"]) == 2 + assert s["data"] == { + "ELASTICSEARCH_PASSWORD": content_b64["elk_pass"], + "api_key": content_b64["api_key"], + } + + +def test_adding_multiple_secrets(): + content = { + "elk_pass": "LS1CRUdJTiBgUFJJVkFURSB", + "cert_crt": "LS0tLS1CRUdJTiBlRJRALKJDDQVRFLS0tLS0K", + "cert_key": "LS0tLS1CRUdJTiBgUFJJVkFURSBLRVktLS0tLQo", + } + config = """ +secrets: + - name: "env" + value: + ELASTICSEARCH_PASSWORD: {elk_pass} + - name: "tls" + value: + cert.crt: {cert_crt} + cert.key: {cert_key} + +""".format( + elk_pass=content["elk_pass"], + cert_crt=content["cert_crt"], + cert_key=content["cert_key"], + ) + content_b64 = { + "elk_pass": base64.b64encode(content["elk_pass"].encode("ascii")).decode( + "ascii" + ), + "cert_crt": base64.b64encode(content["cert_crt"].encode("ascii")).decode( + "ascii" + ), + "cert_key": base64.b64encode(content["cert_key"].encode("ascii")).decode( + "ascii" + ), + } + + r = helm_template(config) + secret_names = {"env": name + "-env", "tls": name + "-tls"} + s_env = r["secret"][secret_names["env"]] + s_tls = r["secret"][secret_names["tls"]] + assert len(r["secret"]) == 2 + assert len(s_env["data"]) == 1 + assert s_env["data"] == { + "ELASTICSEARCH_PASSWORD": content_b64["elk_pass"], + } + assert len(s_tls["data"]) == 2 + assert s_tls["data"] == { + "cert.crt": content_b64["cert_crt"], + "cert.key": content_b64["cert_key"], + } diff --git a/metricbeat/values.yaml b/metricbeat/values.yaml index d5e7bce98..f53d542a6 100755 --- a/metricbeat/values.yaml +++ b/metricbeat/values.yaml @@ -255,6 +255,22 @@ kube_state_metrics: # host is used only when kube_state_metrics.enabled: false host: "" +# Add sensitive data to k8s secrets +secrets: [] +# - name: "env" +# value: +# ELASTICSEARCH_PASSWORD: "LS1CRUdJTiBgUFJJVkFURSB" +# api_key: ui2CsdUadTiBasRJRkl9tvNnw +# - name: "tls" +# value: +# ca.crt: | +# LS0tLS1CRUdJT0K +# LS0tLS1CRUdJT0K +# LS0tLS1CRUdJT0K +# LS0tLS1CRUdJT0K +# cert.crt: "LS0tLS1CRUdJTiBlRJRklDQVRFLS0tLS0K" +# cert.key.filepath: "secrets.crt" # The path to file should be relative to the `values.yaml` file. + # DEPRECATED affinity: {} envFrom: []