diff --git a/charts/graylog/README.md b/charts/graylog/README.md index dc4cdb1..eec2ee2 100644 --- a/charts/graylog/README.md +++ b/charts/graylog/README.md @@ -432,20 +432,21 @@ helm upgrade --install graylog graylog/graylog --namespace graylog --reuse-value # Hardened Environments -All workloads run with tightened pod and container security contexts by default (non-root where possible, dropped -capabilities, and `seccompProfile: RuntimeDefault`). The Graylog application is compliant with the Kubernetes +All Graylog workloads run with tightened pod and container security contexts by default (non-root, dropped capabilities, +and `seccompProfile: RuntimeDefault`). Both the Graylog application and the DataNode are compliant with the Kubernetes [`restricted` Pod Security Standard](https://kubernetes.io/docs/concepts/security/pod-security-standards/). -Two components cannot yet meet `restricted` and need an exemption if you enforce it: - -- **DataNode** must currently start as root to prepare its data directory before dropping privileges to a non-root - user. We are working on updating the entrypoint upstream to remove this requirement; until then, the DataNode - requires the `baseline` level (not `restricted`) or a namespace exemption. -- **MongoDB**, when provisioned by the MCK operator, runs pods that are not `restricted`-compliant. Either exempt - them, or [bring your own MongoDB](#bring-your-own-mongodb) for hardened environments. +> [!NOTE] +> If you enforce [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) at the +> `restricted` level and let the chart provision MongoDB, you might need to exempt the MongoDB pods (or +> [bring your own MongoDB](#bring-your-own-mongodb)). With an external MongoDB, all chart-managed workloads are +> `restricted`-compliant. -If you enforce [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/), set the -namespace to `baseline` rather than `restricted`, or apply the exemptions above. +> [!NOTE] +> The DataNode runs non-root by relying on `fsGroup` for volume ownership and setting `GDN_RUN_AS_NONROOT`, which +> tells the container entrypoint to skip its root-only privilege-drop path. This requires a DataNode image whose +> entrypoint supports `GDN_RUN_AS_NONROOT` (graylog-datanode `7.1`+). If you pin an older `datanode.image.tag`, +> override `datanode.podSecurityContext` and `datanode.containerSecurityContext` to run the container as root. # Maintenance diff --git a/charts/graylog/templates/config/datanode.yaml b/charts/graylog/templates/config/datanode.yaml index f802c62..3dc20a4 100644 --- a/charts/graylog/templates/config/datanode.yaml +++ b/charts/graylog/templates/config/datanode.yaml @@ -3,6 +3,10 @@ kind: ConfigMap metadata: name: {{ include "graylog.datanode.configmap.name" . }} data: + # Required by the datanode entrypoint to take the non-root startup path + # (mkdir + chmod instead of chown + setpriv). Ignored when the container + # runs as root. See graylog-docker entrypoint.sh (PR #306). + GDN_RUN_AS_NONROOT: "true" JAVA_OPTS: {{ .Values.datanode.config.javaOpts | quote }} GRAYLOG_DATANODE_NODE_ID_FILE: {{ .Values.datanode.config.nodeIdFile | default "/var/lib/graylog-datanode/node-id" | quote }} GRAYLOG_DATANODE_OPENSEARCH_HEAP: {{ .Values.datanode.config.opensearchHeap | quote }} diff --git a/charts/graylog/tests/datanode_statefulset_test.yaml b/charts/graylog/tests/datanode_statefulset_test.yaml index 8189aff..8ddbad1 100644 --- a/charts/graylog/tests/datanode_statefulset_test.yaml +++ b/charts/graylog/tests/datanode_statefulset_test.yaml @@ -17,6 +17,9 @@ tests: - equal: path: spec.template.spec.securityContext value: + runAsUser: 999 + runAsGroup: 999 + runAsNonRoot: true fsGroup: 999 fsGroupChangePolicy: "OnRootMismatch" seccompProfile: @@ -31,7 +34,7 @@ tests: path: spec.template.spec.securityContext template: workload/statefulsets/datanode.yaml - - it: renders containerSecurityContext with the startup capabilities on the datanode container + - it: renders the default non-root containerSecurityContext on the datanode container asserts: - equal: path: spec.template.spec.containers[0].name @@ -40,10 +43,10 @@ tests: - equal: path: spec.template.spec.containers[0].securityContext value: + runAsNonRoot: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"] - add: ["CHOWN", "DAC_OVERRIDE", "FOWNER", "SETUID", "SETGID"] seccompProfile: type: RuntimeDefault template: workload/statefulsets/datanode.yaml @@ -55,3 +58,12 @@ tests: - notExists: path: spec.template.spec.containers[0].securityContext template: workload/statefulsets/datanode.yaml + + # The non-root securityContext above only works if the entrypoint takes its + # non-root path, which it does when GDN_RUN_AS_NONROOT is set (graylog-docker #306). + - it: sets GDN_RUN_AS_NONROOT so the datanode entrypoint takes the non-root path + asserts: + - equal: + path: data.GDN_RUN_AS_NONROOT + value: "true" + template: config/datanode.yaml diff --git a/charts/graylog/values.yaml b/charts/graylog/values.yaml index 9bb445f..a25b070 100644 --- a/charts/graylog/values.yaml +++ b/charts/graylog/values.yaml @@ -286,15 +286,18 @@ datanode: enabled: false minAvailable: 2 podSecurityContext: + runAsUser: 999 + runAsGroup: 999 + runAsNonRoot: true fsGroup: 999 fsGroupChangePolicy: "OnRootMismatch" seccompProfile: type: RuntimeDefault containerSecurityContext: + runAsNonRoot: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"] - add: ["CHOWN", "DAC_OVERRIDE", "FOWNER", "SETUID", "SETGID"] seccompProfile: type: RuntimeDefault podAnnotations: {}