diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38572ef..aeef221 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: - run: git status - run: git diff + - name: "GSM templates render (opt-in)" + run: .github/workflows/scripts/check-gsm-templates.sh + - name: Helm Docs Check run: .github/workflows/scripts/check-helm-docs.sh diff --git a/.github/workflows/scripts/check-gsm-templates.sh b/.github/workflows/scripts/check-gsm-templates.sh new file mode 100755 index 0000000..5fd6717 --- /dev/null +++ b/.github/workflows/scripts/check-gsm-templates.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +# This check ensures the optional GSM templates render when enabled AND when the +# relevant CRDs are present. We simulate CRD presence using --api-versions. + +rendered="$( + helm template release-name charts/bindplane \ + --set gsm.enabled=true \ + --set gsm.projectId=example-project \ + --set gsm.spc.secrets[0].secretName=bindplane-license \ + --set gsm.spc.secrets[0].path=license.txt \ + --set gsm.secretsync.secretObject.data[0].sourcePath=license.txt \ + --set gsm.secretsync.secretObject.data[0].targetKey=license \ + --api-versions secrets-store.csi.x-k8s.io/v1/SecretProviderClass \ + --api-versions secret-sync.gke.io/v1/SecretSync +)" + +echo "$rendered" | grep -q "kind: SecretProviderClass" +echo "$rendered" | grep -q "kind: SecretSync" + diff --git a/README.md b/README.md index d40ddb9..7a444b3 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,8 @@ Helm's [documentation](https://helm.sh/docs) to get started. The Chart can accept a secret for configuring sensitive options. This secret should be managed outside of helm with your preferred secret management solution. Alternatively, you can specify these options using a values file. See the [Chart documentation](./charts/bindplane/README.md). +On GKE, you can optionally sync secrets from Google Secret Manager into a Kubernetes Secret using the chart's opt-in `gsm` settings (SecretProviderClass + SecretSync). This remains disabled by default. + The secret should have the following keys: - `username`: Basic auth username to use for the default admin user - `password`: Basic auth password to use for the default admin user diff --git a/charts/bindplane/README.md b/charts/bindplane/README.md index f1c66f8..4d129d4 100644 --- a/charts/bindplane/README.md +++ b/charts/bindplane/README.md @@ -6,6 +6,15 @@ Bindplane is an observability pipeline. **Homepage:** +## Google Secret Manager (GKE) (optional) + +This chart can optionally render `SecretProviderClass` and `SecretSync` resources to sync secrets from Google Secret Manager (GSM) into a Kubernetes Secret. This is **disabled by default** and will only render when the required CRDs are installed in the cluster. + +Key points: +- Enable with `gsm.enabled=true`. +- The SecretSync name defaults to `config.secret` so the synced Kubernetes Secret matches what the chart reads. +- Use `serviceAccount.annotations` to configure Workload Identity for GKE. + ## Maintainers | Name | Email | Url | @@ -108,6 +117,15 @@ Bindplane is an observability pipeline. | extraPodLabels | object | `{}` | Optional arbitrary labels to add to the Bindplane pod(s). | | extraVolumeMounts | list | `[]` | Optional arbitrary volume mounts to add to the Bindplane pod(s). | | extraVolumes | list | `[]` | Optional arbitrary volumes to add to the Bindplane pod(s). | +| gsm | object | `{"enabled":false,"projectId":"","secretsync":{"enabled":true,"name":"","secretObject":{"data":[],"type":"Opaque"}},"spc":{"name":"","secrets":[]}}` | Optional integration with Google Secret Manager (GSM) on GKE using SecretProviderClass + SecretSync. Disabled by default. | +| gsm.enabled | bool | `false` | Enable rendering of GSM resources (SecretProviderClass / SecretSync). | +| gsm.projectId | string | `""` | Google Cloud project ID containing the secrets. | +| gsm.secretsync.enabled | bool | `true` | Enable rendering of the SecretSync resource. | +| gsm.secretsync.name | string | `""` | Name of the SecretSync resource. By default this should match `config.secret` so the synced Kubernetes Secret is named appropriately for the chart. | +| gsm.secretsync.secretObject.data | list | `[]` | Mapping from mounted secret files to keys in the Kubernetes Secret. Each item must have `sourcePath` and `targetKey`. | +| gsm.secretsync.secretObject.type | string | `"Opaque"` | Kubernetes secret type to create. | +| gsm.spc.name | string | `""` | Name of the SecretProviderClass to create. | +| gsm.spc.secrets | list | `[]` | A list of secrets to fetch from Google Secret Manager. `secretName` is the name of the secret in GSM. `path` is the mount path for the secret; must match SecretSync secretObject.data.sourcePath. | | health.livenessProbe | object | `{"httpGet":{"path":"/health","port":"http"}}` | Full configuration for livenessProbe. Supports all options documented here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/. | | health.readinessProbe | object | `{"httpGet":{"path":"/health","port":"http"}}` | Full configuration for readinessProbe. Supports all options documented here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/. | | health.startupProbe | object | `{"failureThreshold":20,"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":0,"periodSeconds":5,"successThreshold":1,"timeoutSeconds":1}` | Full configuration for startupProbe. Supports all options documented here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/. | @@ -199,5 +217,3 @@ Bindplane is an observability pipeline. | transform_agent.replicas | int | `1` | Number of replicas to use for the transform agent. | | transform_agent.tag | string | `""` | Transform Agent Image tag to use. Defaults to latest. | ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.14.2](https://github.com/norwoodj/helm-docs/releases/v1.14.2) diff --git a/charts/bindplane/README.md.gotmpl b/charts/bindplane/README.md.gotmpl new file mode 100644 index 0000000..bc022f2 --- /dev/null +++ b/charts/bindplane/README.md.gotmpl @@ -0,0 +1,28 @@ +{{- /* +Custom helm-docs template for this chart. + +We keep the default look-and-feel (via built-in helm-docs templates), but add a +short section documenting the optional GKE Google Secret Manager (GSM) +integration. +*/ -}} +{{ template "chart.header" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.homepageLine" . }} + +## Google Secret Manager (GKE) (optional) + +This chart can optionally render `SecretProviderClass` and `SecretSync` resources to sync secrets from Google Secret Manager (GSM) into a Kubernetes Secret. This is **disabled by default** and will only render when the required CRDs are installed in the cluster. + +Key points: +- Enable with `gsm.enabled=true`. +- The SecretSync name defaults to `config.secret` so the synced Kubernetes Secret matches what the chart reads. +- Use `serviceAccount.annotations` to configure Workload Identity for GKE. + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.valuesSection" . }} + diff --git a/charts/bindplane/templates/gsm-secret-provider-class.yaml b/charts/bindplane/templates/gsm-secret-provider-class.yaml new file mode 100644 index 0000000..b609b26 --- /dev/null +++ b/charts/bindplane/templates/gsm-secret-provider-class.yaml @@ -0,0 +1,28 @@ +{{- /* +GKE Google Secret Manager (GSM) integration. + +This resource is optional and only rendered when: +- .Values.gsm.enabled is true +- The SecretProviderClass CRD is available on the cluster +*/ -}} +{{- if and .Values.gsm.enabled (.Capabilities.APIVersions.Has "secrets-store.csi.x-k8s.io/v1/SecretProviderClass") -}} +apiVersion: secrets-store.csi.x-k8s.io/v1 +kind: SecretProviderClass +metadata: + name: {{ default (printf "%s-gsm" (include "bindplane.fullname" .)) .Values.gsm.spc.name }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "bindplane.name" . }} + app.kubernetes.io/stack: bindplane + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + provider: gke + parameters: + secrets: | + {{- range .Values.gsm.spc.secrets }} + - resourceName: "projects/{{ $.Values.gsm.projectId }}/secrets/{{ .secretName }}/versions/latest" + path: "{{ .path }}" + {{- end }} +{{- end }} + diff --git a/charts/bindplane/templates/gsm-secret-sync.yaml b/charts/bindplane/templates/gsm-secret-sync.yaml new file mode 100644 index 0000000..1cf83da --- /dev/null +++ b/charts/bindplane/templates/gsm-secret-sync.yaml @@ -0,0 +1,36 @@ +{{- /* +GKE Google Secret Manager (GSM) integration. + +This resource is optional and only rendered when: +- .Values.gsm.enabled is true +- .Values.gsm.secretsync.enabled is true +- The SecretSync CRD is available on the cluster + +NOTE: By default, this template uses the SecretSync resource name as the name +of the Kubernetes Secret that gets created. The default is set to match +.Values.config.secret so the chart's existing secret wiring continues to work. +*/ -}} +{{- if and .Values.gsm.enabled (.Values.gsm.secretsync.enabled) (.Capabilities.APIVersions.Has "secret-sync.gke.io/v1/SecretSync") -}} +apiVersion: secret-sync.gke.io/v1 +kind: SecretSync +metadata: + name: {{ default .Values.config.secret .Values.gsm.secretsync.name }} + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/name: {{ include "bindplane.name" . }} + app.kubernetes.io/stack: bindplane + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/managed-by: {{ .Release.Service }} +spec: + serviceAccountName: {{ include "bindplane.fullname" . }} + secretProviderClassName: {{ default (printf "%s-gsm" (include "bindplane.fullname" .)) .Values.gsm.spc.name }} + secretObject: + type: {{ .Values.gsm.secretsync.secretObject.type }} + {{- if .Values.gsm.secretsync.secretObject.data }} + data: +{{- toYaml .Values.gsm.secretsync.secretObject.data | nindent 6 }} + {{- else }} + data: [] + {{- end }} +{{- end }} + diff --git a/charts/bindplane/values.schema.json b/charts/bindplane/values.schema.json index ae29d31..edaaee6 100644 --- a/charts/bindplane/values.schema.json +++ b/charts/bindplane/values.schema.json @@ -102,6 +102,77 @@ } }, "additionalProperties": true + }, + "gsm": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable rendering of GSM resources (SecretProviderClass / SecretSync)." + }, + "projectId": { + "type": "string", + "description": "Google Cloud project ID containing the secrets." + }, + "spc": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the SecretProviderClass to create." + }, + "secrets": { + "type": "array", + "description": "List of secrets to fetch from Google Secret Manager.", + "items": { + "type": "object", + "properties": { + "secretName": { "type": "string" }, + "path": { "type": "string" } + }, + "required": ["secretName", "path"], + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "secretsync": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable rendering of the SecretSync resource." + }, + "name": { + "type": "string", + "description": "Name of the SecretSync resource (and by default the target Kubernetes Secret name)." + }, + "secretObject": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "sourcePath": { "type": "string" }, + "targetKey": { "type": "string" } + }, + "required": ["sourcePath", "targetKey"], + "additionalProperties": false + } + } + }, + "required": ["type", "data"], + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } }, "required": ["nats", "eventbus", "auth"], diff --git a/charts/bindplane/values.yaml b/charts/bindplane/values.yaml index 02ba667..1448c94 100644 --- a/charts/bindplane/values.yaml +++ b/charts/bindplane/values.yaml @@ -611,6 +611,40 @@ extraVolumes: [] # -- Optional arbitrary volume mounts to add to the Bindplane pod(s). extraVolumeMounts: [] +# -- Optional integration with Google Secret Manager (GSM) on GKE using +# SecretProviderClass + SecretSync. Disabled by default. +gsm: + # -- Enable rendering of GSM resources (SecretProviderClass / SecretSync). + enabled: false + + # -- Google Cloud project ID containing the secrets. + projectId: "" + + spc: + # -- Name of the SecretProviderClass to create. + name: "" + + # -- A list of secrets to fetch from Google Secret Manager. + # `secretName` is the name of the secret in GSM. + # `path` is the mount path for the secret; must match SecretSync secretObject.data.sourcePath. + secrets: [] + + secretsync: + # -- Enable rendering of the SecretSync resource. + enabled: true + + # -- Name of the SecretSync resource. By default this should match `config.secret` + # so the synced Kubernetes Secret is named appropriately for the chart. + name: "" + + secretObject: + # -- Kubernetes secret type to create. + type: Opaque + + # -- Mapping from mounted secret files to keys in the Kubernetes Secret. + # Each item must have `sourcePath` and `targetKey`. + data: [] + # -- Optional arbitrary labels to add to the Bindplane pod(s). extraPodLabels: {}