diff --git a/operations/helm/charts/alloy/CHANGELOG.md b/operations/helm/charts/alloy/CHANGELOG.md index 750fd6884f..03c8b1429c 100644 --- a/operations/helm/charts/alloy/CHANGELOG.md +++ b/operations/helm/charts/alloy/CHANGELOG.md @@ -10,6 +10,10 @@ internal API changes are not present. Unreleased ---------- +### Enhancements + +- Add support for mounting configuration from Kubernetes Secrets via `alloy.secret` values. (@claude-code) + ### Bug fixes - Correct `extraEnv` indentation in container template (@orkhan-huseyn) diff --git a/operations/helm/charts/alloy/README.md b/operations/helm/charts/alloy/README.md index c0621a09d8..9c2ec117b0 100644 --- a/operations/helm/charts/alloy/README.md +++ b/operations/helm/charts/alloy/README.md @@ -40,6 +40,10 @@ useful if just using the default DaemonSet isn't sufficient. | alloy.configMap.create | bool | `true` | Create a new ConfigMap for the config file. | | alloy.configMap.key | string | `nil` | Key in ConfigMap to get config from. | | alloy.configMap.name | string | `nil` | Name of existing ConfigMap to use. Used when create is false. | +| alloy.secret.content | string | `""` | Content to assign to the new Secret. This is passed into `tpl` allowing for templating from values. | +| alloy.secret.create | bool | `false` | Create a new Secret for the config file. Takes precedence over configMap if both are configured. | +| alloy.secret.key | string | `nil` | Key in Secret to get config from. | +| alloy.secret.name | string | `nil` | Name of existing Secret to use. Used when create is false. | | alloy.enableHttpServerPort | bool | `true` | Enables Grafana Alloy container's http server port. | | alloy.enableReporting | bool | `true` | Enables sending Grafana Labs anonymous usage stats to help improve Grafana Alloy. | | alloy.envFrom | list | `[]` | Maps all the keys on a ConfigMap or Secret as environment variables. https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#envfromsource-v1-core | @@ -254,6 +258,21 @@ used. When provided, `alloy.configMap.content` must hold a valid Alloy configura [default-config]: ./config/example.alloy +### alloy.secret + +You can use `alloy.secret` to mount configuration from a Kubernetes Secret instead of a ConfigMap. +This provides better security for configurations containing sensitive credentials. + +When `alloy.secret.create` is set to `true` or `alloy.secret.name` is specified, the Secret takes +precedence over the ConfigMap configuration. The ConfigMap will not be created in this case. + +`alloy.secret.content` holds the Grafana Alloy configuration to store in the Secret. +When provided, it must hold a valid Alloy configuration file and is passed through `tpl` for templating. + +You can reference an existing Secret by setting `alloy.secret.create` to `false` and specifying +the Secret name in `alloy.secret.name`. Optionally set `alloy.secret.key` if the configuration +is stored under a different key than the default `config.alloy`. + ### alloy.securityContext `alloy.securityContext` sets the securityContext passed to the Grafana diff --git a/operations/helm/charts/alloy/README.md.gotmpl b/operations/helm/charts/alloy/README.md.gotmpl index 538c753669..f215ac9597 100644 --- a/operations/helm/charts/alloy/README.md.gotmpl +++ b/operations/helm/charts/alloy/README.md.gotmpl @@ -94,6 +94,21 @@ used. When provided, `alloy.configMap.content` must hold a valid Alloy configura [default-config]: ./config/example.alloy +### alloy.secret + +You can use `alloy.secret` to mount configuration from a Kubernetes Secret instead of a ConfigMap. +This provides better security for configurations containing sensitive credentials. + +When `alloy.secret.create` is set to `true` or `alloy.secret.name` is specified, the Secret takes +precedence over the ConfigMap configuration. The ConfigMap will not be created in this case. + +`alloy.secret.content` holds the Grafana Alloy configuration to store in the Secret. +When provided, it must hold a valid Alloy configuration file and is passed through `tpl` for templating. + +You can reference an existing Secret by setting `alloy.secret.create` to `false` and specifying +the Secret name in `alloy.secret.name`. Optionally set `alloy.secret.key` if the configuration +is stored under a different key than the default `config.alloy`. + ### alloy.securityContext `alloy.securityContext` sets the securityContext passed to the Grafana diff --git a/operations/helm/charts/alloy/ci/custom-secret-config-values.yaml b/operations/helm/charts/alloy/ci/custom-secret-config-values.yaml new file mode 100644 index 0000000000..b79d25e320 --- /dev/null +++ b/operations/helm/charts/alloy/ci/custom-secret-config-values.yaml @@ -0,0 +1,37 @@ +alloy: + secret: + create: true + content: | + logging { + level = "info" + format = "logfmt" + } + + discovery.kubernetes "pods" { + role = "pod" + } + + discovery.relabel "pods" { + targets = discovery.kubernetes.pods.targets + + rule { + action = "labelmap" + regex = "__meta_kubernetes_pod_label_(.+)" + replacement = "k8s_pod_label_$1" + } + } + + prometheus.scrape "pods" { + targets = discovery.relabel.pods.output + forward_to = [prometheus.remote_write.default.receiver] + } + + prometheus.remote_write "default" { + endpoint { + url = "https://prometheus.example.com/api/v1/write" + basic_auth { + username = "my-username" + password = "my-secret-password" + } + } + } diff --git a/operations/helm/charts/alloy/ci/existing-secret-values.yaml b/operations/helm/charts/alloy/ci/existing-secret-values.yaml new file mode 100644 index 0000000000..e6f3f43e3b --- /dev/null +++ b/operations/helm/charts/alloy/ci/existing-secret-values.yaml @@ -0,0 +1,5 @@ +alloy: + secret: + create: false + name: existing-secret + key: my-config.alloy diff --git a/operations/helm/charts/alloy/ci/rbac-empty-rules-values.yaml b/operations/helm/charts/alloy/ci/rbac-empty-rules-values.yaml new file mode 100644 index 0000000000..752651ac5b --- /dev/null +++ b/operations/helm/charts/alloy/ci/rbac-empty-rules-values.yaml @@ -0,0 +1,5 @@ +# Test case for empty RBAC rules arrays +rbac: + create: true + rules: [] + clusterRules: [] diff --git a/operations/helm/charts/alloy/templates/_config.tpl b/operations/helm/charts/alloy/templates/_config.tpl index 54b8225db4..583e393d04 100644 --- a/operations/helm/charts/alloy/templates/_config.tpl +++ b/operations/helm/charts/alloy/templates/_config.tpl @@ -23,3 +23,63 @@ ConfigMap. config.alloy {{- end }} {{- end }} + +{{/* +Retrieve Secret name from the name of the chart or the Secret the user +specified. +*/}} +{{- define "alloy.secret.name" -}} +{{- $values := (mustMergeOverwrite .Values.alloy (or .Values.agent dict)) -}} +{{- if $values.secret.name -}} +{{- $values.secret.name }} +{{- else -}} +{{- include "alloy.fullname" . }} +{{- end }} +{{- end }} + +{{/* +The name of the config file is the default or the key the user specified in the +Secret. +*/}} +{{- define "alloy.secret.key" -}} +{{- $values := (mustMergeOverwrite .Values.alloy (or .Values.agent dict)) -}} +{{- if $values.secret.key -}} +{{- $values.secret.key }} +{{- else -}} +config.alloy +{{- end }} +{{- end }} + +{{/* +Determine if using Secret for config (Secret takes precedence over ConfigMap). +*/}} +{{- define "alloy.config-source.use-secret" -}} +{{- $values := (mustMergeOverwrite .Values.alloy (or .Values.agent dict)) -}} +{{- if or $values.secret.create $values.secret.name -}} +true +{{- else -}} +false +{{- end }} +{{- end }} + +{{/* +Get the config source name (Secret or ConfigMap). +*/}} +{{- define "alloy.config-source.name" -}} +{{- if eq (include "alloy.config-source.use-secret" .) "true" -}} +{{- include "alloy.secret.name" . }} +{{- else -}} +{{- include "alloy.config-map.name" . }} +{{- end }} +{{- end }} + +{{/* +Get the config source key (Secret or ConfigMap). +*/}} +{{- define "alloy.config-source.key" -}} +{{- if eq (include "alloy.config-source.use-secret" .) "true" -}} +{{- include "alloy.secret.key" . }} +{{- else -}} +{{- include "alloy.config-map.key" . }} +{{- end }} +{{- end }} diff --git a/operations/helm/charts/alloy/templates/configmap.yaml b/operations/helm/charts/alloy/templates/configmap.yaml index 1acadefde9..e5b0acf7fb 100644 --- a/operations/helm/charts/alloy/templates/configmap.yaml +++ b/operations/helm/charts/alloy/templates/configmap.yaml @@ -1,5 +1,5 @@ {{- $values := (mustMergeOverwrite .Values.alloy (or .Values.agent dict)) -}} -{{- if $values.configMap.create }} +{{- if and $values.configMap.create (not (or $values.secret.create $values.secret.name)) }} apiVersion: v1 kind: ConfigMap metadata: diff --git a/operations/helm/charts/alloy/templates/containers/_agent.yaml b/operations/helm/charts/alloy/templates/containers/_agent.yaml index 84f0fcda79..70a1e09005 100644 --- a/operations/helm/charts/alloy/templates/containers/_agent.yaml +++ b/operations/helm/charts/alloy/templates/containers/_agent.yaml @@ -5,7 +5,7 @@ imagePullPolicy: {{ .Values.image.pullPolicy }} args: - run - - /etc/alloy/{{ include "alloy.config-map.key" . }} + - /etc/alloy/{{ include "alloy.config-source.key" . }} - --storage.path={{ $values.storagePath }} - --server.http.listen-addr={{ $values.listenAddr }}:{{ $values.listenPort }} - --server.http.ui-path-prefix={{ $values.uiPathPrefix }} diff --git a/operations/helm/charts/alloy/templates/controllers/_pod.yaml b/operations/helm/charts/alloy/templates/controllers/_pod.yaml index 3eff63fa92..87b411e219 100644 --- a/operations/helm/charts/alloy/templates/controllers/_pod.yaml +++ b/operations/helm/charts/alloy/templates/controllers/_pod.yaml @@ -3,9 +3,13 @@ metadata: annotations: kubectl.kubernetes.io/default-container: alloy - {{- if and (not .Values.configReloader.enabled) $values.configMap.create $values.configMap.content }} + {{- if not .Values.configReloader.enabled }} + {{- if and $values.secret.create $values.secret.content }} + checksum/config: {{ (tpl $values.secret.content .) | sha256sum | trunc 63 }} + {{- else if and $values.configMap.create $values.configMap.content }} checksum/config: {{ (tpl $values.configMap.content .) | sha256sum | trunc 63 }} {{- end }} + {{- end }} {{- with .Values.controller.podAnnotations }} {{- toYaml . | nindent 4 }} {{- end }} @@ -71,8 +75,13 @@ spec: {{- end }} volumes: - name: config +{{- if eq (include "alloy.config-source.use-secret" .) "true" }} + secret: + secretName: {{ include "alloy.secret.name" . }} +{{- else }} configMap: name: {{ include "alloy.config-map.name" . }} +{{- end }} {{- if $values.mounts.varlog }} - name: varlog hostPath: diff --git a/operations/helm/charts/alloy/templates/rbac.yaml b/operations/helm/charts/alloy/templates/rbac.yaml index 6f5e394fd2..e5ba9468bd 100644 --- a/operations/helm/charts/alloy/templates/rbac.yaml +++ b/operations/helm/charts/alloy/templates/rbac.yaml @@ -11,7 +11,11 @@ metadata: {{- include "alloy.labels" $ | nindent 4 }} app.kubernetes.io/component: rbac rules: + {{- if $.Values.rbac.rules }} {{- $.Values.rbac.rules | toYaml | nindent 2 }} + {{- else }} + [] + {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -40,8 +44,12 @@ metadata: {{- include "alloy.labels" . | nindent 4 }} app.kubernetes.io/component: rbac rules: + {{- if or .Values.rbac.rules .Values.rbac.clusterRules }} {{- .Values.rbac.rules | toYaml | nindent 2 }} {{- .Values.rbac.clusterRules | toYaml | nindent 2 }} + {{- else }} + [] + {{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/operations/helm/charts/alloy/templates/secret.yaml b/operations/helm/charts/alloy/templates/secret.yaml new file mode 100644 index 0000000000..8c0d385795 --- /dev/null +++ b/operations/helm/charts/alloy/templates/secret.yaml @@ -0,0 +1,18 @@ +{{- $values := (mustMergeOverwrite .Values.alloy (or .Values.agent dict)) -}} +{{- if $values.secret.create }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "alloy.secret.name" . }} + namespace: {{ include "alloy.namespace" . }} + labels: + {{- include "alloy.labels" . | nindent 4 }} + app.kubernetes.io/component: config +type: Opaque +stringData: + {{- if $values.secret.content }} + {{ include "alloy.secret.key" . }}: |- {{- (tpl $values.secret.content .) | nindent 4 }} + {{- else }} + {{ include "alloy.secret.key" . }}: |- {{- .Files.Get "config/example.alloy" | trim | nindent 4 }} + {{- end }} +{{- end }} diff --git a/operations/helm/charts/alloy/values.yaml b/operations/helm/charts/alloy/values.yaml index 2e08eb7ee6..ecbc2ec9c0 100644 --- a/operations/helm/charts/alloy/values.yaml +++ b/operations/helm/charts/alloy/values.yaml @@ -40,6 +40,17 @@ alloy: # -- Key in ConfigMap to get config from. key: null + secret: + # -- Create a new Secret for the config file. Takes precedence over configMap if both are configured. + create: false + # -- Content to assign to the new Secret. This is passed into `tpl` allowing for templating from values. + content: '' + + # -- Name of existing Secret to use. Used when create is false. + name: null + # -- Key in Secret to get config from. + key: null + clustering: # -- Deploy Alloy in a cluster to allow for load distribution. enabled: false diff --git a/operations/helm/tests/rbac-empty-rules/alloy/templates/configmap.yaml b/operations/helm/tests/rbac-empty-rules/alloy/templates/configmap.yaml new file mode 100644 index 0000000000..62d8c8642f --- /dev/null +++ b/operations/helm/tests/rbac-empty-rules/alloy/templates/configmap.yaml @@ -0,0 +1,44 @@ +--- +# Source: alloy/templates/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: alloy + namespace: default + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: config +data: + config.alloy: |- + logging { + level = "info" + format = "logfmt" + } + + discovery.kubernetes "pods" { + role = "pod" + } + + discovery.kubernetes "nodes" { + role = "node" + } + + discovery.kubernetes "services" { + role = "service" + } + + discovery.kubernetes "endpoints" { + role = "endpoints" + } + + discovery.kubernetes "endpointslices" { + role = "endpointslice" + } + + discovery.kubernetes "ingresses" { + role = "ingress" + } diff --git a/operations/helm/tests/rbac-empty-rules/alloy/templates/controllers/daemonset.yaml b/operations/helm/tests/rbac-empty-rules/alloy/templates/controllers/daemonset.yaml new file mode 100644 index 0000000000..0e117afe78 --- /dev/null +++ b/operations/helm/tests/rbac-empty-rules/alloy/templates/controllers/daemonset.yaml @@ -0,0 +1,76 @@ +--- +# Source: alloy/templates/controllers/daemonset.yaml +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: alloy + namespace: default + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm +spec: + minReadySeconds: 10 + selector: + matchLabels: + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: alloy + labels: + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + spec: + serviceAccountName: alloy + containers: + - name: alloy + image: docker.io/grafana/alloy:v1.11.3 + imagePullPolicy: IfNotPresent + args: + - run + - /etc/alloy/config.alloy + - --storage.path=/tmp/alloy + - --server.http.listen-addr=0.0.0.0:12345 + - --server.http.ui-path-prefix=/ + - --stability.level=generally-available + env: + - name: ALLOY_DEPLOY_MODE + value: "helm" + - name: HOSTNAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + ports: + - containerPort: 12345 + name: http-metrics + readinessProbe: + httpGet: + path: /-/ready + port: 12345 + scheme: HTTP + initialDelaySeconds: 10 + timeoutSeconds: 1 + volumeMounts: + - name: config + mountPath: /etc/alloy + - name: config-reloader + image: quay.io/prometheus-operator/prometheus-config-reloader:v0.81.0 + args: + - --watched-dir=/etc/alloy + - --reload-url=http://localhost:12345/-/reload + volumeMounts: + - name: config + mountPath: /etc/alloy + resources: + requests: + cpu: 10m + memory: 50Mi + dnsPolicy: ClusterFirst + volumes: + - name: config + configMap: + name: alloy diff --git a/operations/helm/tests/rbac-empty-rules/alloy/templates/rbac.yaml b/operations/helm/tests/rbac-empty-rules/alloy/templates/rbac.yaml new file mode 100644 index 0000000000..2c6fbdf066 --- /dev/null +++ b/operations/helm/tests/rbac-empty-rules/alloy/templates/rbac.yaml @@ -0,0 +1,36 @@ +--- +# Source: alloy/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: alloy + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: rbac +rules: + [] +--- +# Source: alloy/templates/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: alloy + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: rbac +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: alloy +subjects: + - kind: ServiceAccount + name: alloy + namespace: default diff --git a/operations/helm/tests/rbac-empty-rules/alloy/templates/service.yaml b/operations/helm/tests/rbac-empty-rules/alloy/templates/service.yaml new file mode 100644 index 0000000000..8780c6ce89 --- /dev/null +++ b/operations/helm/tests/rbac-empty-rules/alloy/templates/service.yaml @@ -0,0 +1,25 @@ +--- +# Source: alloy/templates/service.yaml +apiVersion: v1 +kind: Service +metadata: + name: alloy + namespace: default + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: networking +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + internalTrafficPolicy: Cluster + ports: + - name: http-metrics + port: 12345 + targetPort: 12345 + protocol: "TCP" diff --git a/operations/helm/tests/rbac-empty-rules/alloy/templates/serviceaccount.yaml b/operations/helm/tests/rbac-empty-rules/alloy/templates/serviceaccount.yaml new file mode 100644 index 0000000000..eef45c7520 --- /dev/null +++ b/operations/helm/tests/rbac-empty-rules/alloy/templates/serviceaccount.yaml @@ -0,0 +1,15 @@ +--- +# Source: alloy/templates/serviceaccount.yaml +apiVersion: v1 +kind: ServiceAccount +automountServiceAccountToken: true +metadata: + name: alloy + namespace: default + labels: + helm.sh/chart: alloy + app.kubernetes.io/name: alloy + app.kubernetes.io/instance: alloy + app.kubernetes.io/version: "vX.Y.Z" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/component: rbac