diff --git a/helm/README.md b/helm/README.md index 40084a97..9247f90a 100644 --- a/helm/README.md +++ b/helm/README.md @@ -150,7 +150,7 @@ Below is a summary of these settings and how they are used: | cloudAccountId | string | `nil` | Account ID in AWS, subscription ID in Azure, or project number in GCP. Auto-detected via IMDS when available; required if IMDS is blocked. | | clusterName | string | `nil` | Name of the cluster (RFC 1123). Mandatory on EKS and AKS; auto-detected on GKE; required on any provider if IMDS is blocked. | | host | string | `"api.cloudzero.com"` | CloudZero host to send metrics to. Override only for non-production or custom environments. | -| apiKey | string | `nil` | CloudZero API key used for exporting metrics. Required unless `existingSecretName` is set. | +| apiKey | string | `nil` | CloudZero API key used for exporting metrics. Required unless `existingSecretName` or `components.apiKey` is configured. | | existingSecretName | string | `nil` | Name of the Secret that contains the CloudZero API key. Required when not providing the API key via `apiKey`. | | region | string | `nil` | Cloud provider region (e.g., `us-east-1`, `eastus`). Auto-detected via IMDS; required if IMDS is blocked or you want to override the detected value. | @@ -255,6 +255,22 @@ kubectl create secret -n example-namespace generic example-secret-name --from-li The secret can then be used with `existingSecretName`. +#### CSI-based Secret Delivery + +For environments using the [Secrets Store CSI Driver](https://secrets-store-csi-driver.sigs.k8s.io/), you can mount the API key directly from an external vault (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, etc.) without creating a Kubernetes Secret object. + +1. Install the Secrets Store CSI Driver and the appropriate cloud provider plugin on your cluster. +2. Create a `SecretProviderClass` in the same namespace as the CloudZero Agent that fetches your API key and produces a file matching `serverConfig.containerSecretFileName` (default: `value`). +3. Reference it in your values: + +```yaml +components: + apiKey: + secretProviderClass: "your-secret-provider-class-name" +``` + +When `components.apiKey.secretProviderClass` is set and both `apiKey` and `existingSecretName` are null, the chart mounts the API key via the CSI driver. The top-level `apiKey` and `existingSecretName` properties take priority when set. + ### Update Helm Chart If you are updating an existing installation, pull the latest chart information: diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 799d5238..f5facc53 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -53,6 +53,23 @@ Returns: string (e.g., "my-release-api-key" or custom existing secret name) {{ .Values.existingSecretName | default (printf "%s-api-key" .Release.Name) }} {{- end}} +{{/* +Determine the API key provisioning mode. + +Returns one of: "inline", "secret", "csi", "none" +*/}} +{{- define "cloudzero-agent.apiKey.mode" -}} +{{- if .Values.apiKey -}} + {{- print "inline" -}} +{{- else if .Values.existingSecretName -}} + {{- print "secret" -}} +{{- else if and .Values.components .Values.components.apiKey .Values.components.apiKey.secretProviderClass -}} + {{- print "csi" -}} +{{- else -}} + {{- print "none" -}} +{{- end -}} +{{- end -}} + {{/* Define the path and filename on the container filesystem which holds the CloudZero API key. @@ -772,10 +789,12 @@ Name for the secret holding TLS certificates {{- end }} {{/* -Volume mount for the API key +Volume mount for the API key. +Renders for inline, secret, and csi modes. No output for "none". */}} {{- define "cloudzero-agent.apiKeyVolumeMount" -}} -{{- if or .Values.existingSecretName .Values.apiKey -}} +{{- $mode := include "cloudzero-agent.apiKey.mode" . -}} +{{- if ne $mode "none" -}} - name: cloudzero-api-key mountPath: {{ .Values.serverConfig.containerSecretFilePath }} subPath: "" @@ -783,6 +802,28 @@ Volume mount for the API key {{- end }} {{- end }} +{{/* +Volume definition for the API key. +- inline/secret: Kubernetes Secret volume +- csi: Secrets Store CSI Driver volume +- none: no output +*/}} +{{- define "cloudzero-agent.apiKeyVolume" -}} +{{- $mode := include "cloudzero-agent.apiKey.mode" . -}} +{{- if or (eq $mode "inline") (eq $mode "secret") -}} +- name: cloudzero-api-key + secret: + secretName: {{ include "cloudzero-agent.secretName" . }} +{{- else if eq $mode "csi" -}} +- name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: {{ .Values.components.apiKey.secretProviderClass }} +{{- end }} +{{- end }} + {{/* Return the URL for the agent and insights controller to send metrics to. diff --git a/helm/templates/agent-daemonset.yaml b/helm/templates/agent-daemonset.yaml index f4756107..c40a438b 100644 --- a/helm/templates/agent-daemonset.yaml +++ b/helm/templates/agent-daemonset.yaml @@ -183,7 +183,7 @@ spec: - name: cloudzero-agent-storage-volume mountPath: /data subPath: "" - {{- include "cloudzero-agent.apiKeyVolumeMount" . | nindent 12 }} + {{- include "cloudzero-agent.apiKeyVolumeMount" . | nindent 12 }} {{- include "cloudzero-agent.generatePodSecurityContext" (mergeOverwrite (.Values.defaults.securityContext | default (dict)) (.Values.components.agent.federatedNode.securityContext | default (dict)) @@ -201,11 +201,7 @@ spec: name: {{ .Release.Name }}-daemonset-cm - name: processed-config-volume emptyDir: {} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" . }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" . | nindent 8 }} - name: cloudzero-agent-storage-volume emptyDir: {{- if .Values.server.emptyDir.sizeLimit }} diff --git a/helm/templates/agent-deploy.yaml b/helm/templates/agent-deploy.yaml index 2f137083..35dde278 100644 --- a/helm/templates/agent-deploy.yaml +++ b/helm/templates/agent-deploy.yaml @@ -234,7 +234,7 @@ spec: - name: validator-config-volume mountPath: /checks/app/config/ {{- include "cloudzero-agent.apiKeyVolumeMount" . | nindent 12 }} - {{- end }}{{/* End Prometheus container */}} + {{- end }}{{/* End Prometheus container */}} {{- if eq (include "cloudzero-agent.Values.components.agent.mode" .) "clustered" }} # CloudZero Alloy container (binary embedded in agent image) - name: {{ template "cloudzero-agent.name" . }}-alloy @@ -315,7 +315,7 @@ spec: - name: alloy-tmp mountPath: /tmp {{- include "cloudzero-agent.apiKeyVolumeMount" . | nindent 12 }} - {{- end }}{{/* End Alloy container */}} + {{- end }}{{/* End Alloy container */}} {{- include "cloudzero-agent.generatePodSecurityContext" (mergeOverwrite (.Values.defaults.securityContext | default (dict)) (.Values.components.agent.securityContext | default (dict)) @@ -343,11 +343,7 @@ spec: - name: alloy-tmp emptyDir: {} {{- end }} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" . }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" . | nindent 8 }} - name: cloudzero-agent-storage-volume {{- if .Values.server.persistentVolume.enabled }} persistentVolumeClaim: diff --git a/helm/templates/aggregator-deploy.yaml b/helm/templates/aggregator-deploy.yaml index 4f5041ed..38d8a8ca 100644 --- a/helm/templates/aggregator-deploy.yaml +++ b/helm/templates/aggregator-deploy.yaml @@ -186,11 +186,7 @@ spec: name: {{ template "cloudzero-agent.validatorConfigMapName" . }} - name: lifecycle-volume emptyDir: {} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" . }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" . | nindent 8 }} - name: aggregator-config-volume configMap: name: {{ include "cloudzero-agent.aggregator.name" . }} diff --git a/helm/templates/backfill-job.yaml b/helm/templates/backfill-job.yaml index 32ea53c2..e6bfa08a 100644 --- a/helm/templates/backfill-job.yaml +++ b/helm/templates/backfill-job.yaml @@ -166,18 +166,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: {{ include "cloudzero-agent.insightsController.configurationMountPath" $ }} + {{- include "cloudzero-agent.apiKeyVolumeMount" $ | nindent 16 }} {{- if or $.Values.insightsController.volumeMounts $.Values.insightsController.tls.enabled }} - {{- if or $.Values.existingSecretName $.Values.apiKey }} - - name: cloudzero-api-key - mountPath: {{ $.Values.serverConfig.containerSecretFilePath }} - subPath: "" - readOnly: true - {{- end }} {{- with $.Values.insightsController.volumeMounts }} {{- toYaml . | nindent 16 }} {{- end }} {{- end }} - {{- if or $.Values.insightsController.volumes $.Values.insightsController.tls.enabled }} + {{- if or $.Values.insightsController.volumes $.Values.insightsController.tls.enabled (ne (include "cloudzero-agent.apiKey.mode" $) "none") }} volumes: - name: insights-server-config configMap: @@ -187,11 +182,7 @@ spec: secret: secretName: {{ include "cloudzero-agent.tlsSecretName" $ }} {{- end }} - {{- if or $.Values.existingSecretName $.Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" $ }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" $ | nindent 12 }} {{- with $.Values.insightsController.volumes }} {{- toYaml . | nindent 12 }} {{- end }} diff --git a/helm/templates/config-loader-job.yaml b/helm/templates/config-loader-job.yaml index 00c0c4c9..294ae905 100644 --- a/helm/templates/config-loader-job.yaml +++ b/helm/templates/config-loader-job.yaml @@ -122,11 +122,7 @@ spec: - name: config-aggregator configMap: name: {{ include "cloudzero-agent.aggregator.name" . }} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" . }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" . | nindent 8 }} {{- with .Values.insightsController.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/helm/templates/webhook-deploy.yaml b/helm/templates/webhook-deploy.yaml index 0afd024b..5b2cc881 100644 --- a/helm/templates/webhook-deploy.yaml +++ b/helm/templates/webhook-deploy.yaml @@ -131,18 +131,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: {{ include "cloudzero-agent.insightsController.configurationMountPath" . }} + {{- include "cloudzero-agent.apiKeyVolumeMount" . | nindent 12 }} {{- if or .Values.insightsController.volumeMounts .Values.insightsController.tls.enabled }} {{- if .Values.insightsController.tls.enabled }} - name: tls-certs mountPath: {{ .Values.insightsController.tls.mountPath }} readOnly: true {{- end }} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - mountPath: {{ .Values.serverConfig.containerSecretFilePath }} - subPath: "" - readOnly: true - {{- end }} {{- with .Values.insightsController.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -169,7 +164,7 @@ spec: successThreshold: {{ .Values.insightsController.server.healthCheck.successThreshold }} failureThreshold: {{ .Values.insightsController.server.healthCheck.failureThreshold }} {{- end }} - {{- if or .Values.insightsController.volumes .Values.insightsController.tls.enabled }} + {{- if or .Values.insightsController.volumes .Values.insightsController.tls.enabled (ne (include "cloudzero-agent.apiKey.mode" .) "none") }} volumes: - name: insights-server-config configMap: @@ -179,11 +174,7 @@ spec: secret: secretName: {{ include "cloudzero-agent.tlsSecretName" . }} {{- end }} - {{- if or .Values.existingSecretName .Values.apiKey }} - - name: cloudzero-api-key - secret: - secretName: {{ include "cloudzero-agent.secretName" . }} - {{- end }} + {{- include "cloudzero-agent.apiKeyVolume" . | nindent 8 }} {{- with .Values.insightsController.volumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/helm/tests/apikey_csi_volume_test.yaml b/helm/tests/apikey_csi_volume_test.yaml new file mode 100644 index 00000000..dd802587 --- /dev/null +++ b/helm/tests/apikey_csi_volume_test.yaml @@ -0,0 +1,169 @@ +suite: test CSI volume rendering for secretProviderClass +templates: + - templates/aggregator-deploy.yaml + - templates/agent-deploy.yaml + - templates/agent-daemonset.yaml + - templates/config-loader-job.yaml + - templates/webhook-deploy.yaml + - templates/backfill-job.yaml +tests: + - it: should render CSI volume in aggregator when secretProviderClass is set + set: + apiKey: null + existingSecretName: null + components: + apiKey: + secretProviderClass: "my-secret-provider" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/aggregator-deploy.yaml + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: cloudzero-api-key + mountPath: /etc/config/secrets/ + subPath: "" + readOnly: true + template: templates/aggregator-deploy.yaml + + - it: should render CSI volume in agent deployment when secretProviderClass is set + set: + apiKey: null + existingSecretName: null + components: + apiKey: + secretProviderClass: "my-secret-provider" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/agent-deploy.yaml + + - it: should render CSI volume in webhook deployment when secretProviderClass is set + set: + apiKey: null + existingSecretName: null + components: + apiKey: + secretProviderClass: "my-secret-provider" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/webhook-deploy.yaml + + - it: should render CSI volume in config-loader job when secretProviderClass is set + set: + apiKey: null + existingSecretName: null + components: + apiKey: + secretProviderClass: "my-secret-provider" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/config-loader-job.yaml + + - it: should render secret volume (not CSI) when top-level apiKey is set + set: + apiKey: "test-key-123" + existingSecretName: null + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + secret: + secretName: RELEASE-NAME-api-key + template: templates/aggregator-deploy.yaml + - notContains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/aggregator-deploy.yaml + + - it: should render secret volume when top-level existingSecretName is set + set: + apiKey: null + existingSecretName: "my-custom-secret" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + secret: + secretName: my-custom-secret + template: templates/aggregator-deploy.yaml + + - it: should use top-level apiKey over components.apiKey.secretProviderClass + set: + apiKey: "top-level-key" + existingSecretName: null + components: + apiKey: + secretProviderClass: "should-be-ignored" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + secret: + secretName: RELEASE-NAME-api-key + template: templates/aggregator-deploy.yaml + + - it: should render CSI volume in daemonset when secretProviderClass is set in federated mode + set: + apiKey: null + existingSecretName: null + configmapReload: + prometheus: + enabled: false + defaults: + federation: + enabled: true + components: + apiKey: + secretProviderClass: "my-secret-provider" + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: cloudzero-api-key + csi: + driver: secrets-store.csi.k8s.io + readOnly: true + volumeAttributes: + secretProviderClass: my-secret-provider + template: templates/agent-daemonset.yaml diff --git a/helm/tests/apikey_secret_reference_test.yaml b/helm/tests/apikey_secret_reference_test.yaml index 5f66ff33..acf938ad 100644 --- a/helm/tests/apikey_secret_reference_test.yaml +++ b/helm/tests/apikey_secret_reference_test.yaml @@ -2,8 +2,6 @@ suite: test apiKey secret name references in manifests templates: - aggregator-deploy.yaml tests: - # Test that existingSecretName is correctly referenced in volume definitions - - it: should reference custom existingSecretName in aggregator deployment volume template: aggregator-deploy.yaml set: @@ -17,8 +15,6 @@ tests: secret: secretName: my-custom-secret - # Test that default generated secret name is used when only apiKey is set - - it: should reference default secret name in aggregator deployment when apiKey is set template: aggregator-deploy.yaml set: @@ -31,3 +27,4 @@ tests: name: cloudzero-api-key secret: secretName: RELEASE-NAME-api-key + diff --git a/helm/tests/apikey_secret_validation_test.yaml b/helm/tests/apikey_secret_validation_test.yaml index 7f6eaf96..bbaffa8a 100644 --- a/helm/tests/apikey_secret_validation_test.yaml +++ b/helm/tests/apikey_secret_validation_test.yaml @@ -2,18 +2,18 @@ suite: test apiKey and existingSecretName validation templates: - aggregator-secret.yaml tests: - # Schema validation failure tests - these test the anyOf + oneOf constraints + # ===== Schema validation: top-level (legacy) ===== - - it: should fail when neither apiKey nor existingSecretName is set + - it: should fail when no API key source is configured anywhere values: - - ../values.yaml # Use empty base values + - ../values.yaml set: apiKey: null existingSecretName: null host: api.cloudzero.com asserts: - failedTemplate: - errorPattern: "anyOf" + errorPattern: "secretProviderClass" - it: should fail when both apiKey and existingSecretName are set set: @@ -23,7 +23,7 @@ tests: - failedTemplate: errorPattern: "oneOf" - # Pattern validation tests + # ===== Pattern validation ===== - it: should fail with invalid apiKey characters (spaces) set: @@ -57,7 +57,7 @@ tests: - failedTemplate: errorPattern: "does not match pattern" - # Successful template rendering tests + # ===== Top-level: successful rendering ===== - it: should create secret when only apiKey is set (with existingSecretName null) set: @@ -75,7 +75,7 @@ tests: - it: should create secret when only apiKey is set (existingSecretName omitted) values: - - ../values.yaml # Use empty base values + - ../values.yaml set: apiKey: "test-key-xyz" host: api.cloudzero.com @@ -143,3 +143,43 @@ tests: asserts: - isKind: of: Secret + + # ===== components.apiKey.secretProviderClass ===== + + - it: should not create secret when secretProviderClass is set + set: + apiKey: null + existingSecretName: null + components: + apiKey: + secretProviderClass: "my-spc" + asserts: + - hasDocuments: + count: 0 + + # ===== Precedence: top-level wins over components.apiKey ===== + + - it: should use top-level apiKey even when secretProviderClass is set + set: + apiKey: "top-level-key" + existingSecretName: null + components: + apiKey: + secretProviderClass: "should-be-ignored" + asserts: + - isKind: + of: Secret + - equal: + path: data.value + value: dG9wLWxldmVsLWtleQ== # base64 of "top-level-key" + + - it: should use top-level existingSecretName even when secretProviderClass is set + set: + apiKey: null + existingSecretName: "my-existing-secret" + components: + apiKey: + secretProviderClass: "should-be-ignored" + asserts: + - hasDocuments: + count: 0 diff --git a/helm/values.schema.json b/helm/values.schema.json index 0f8afc47..021961a5 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -5443,6 +5443,60 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "allOf": [ + { + "else": { + "oneOf": [ + { + "properties": { + "apiKey": { + "pattern": "^[a-zA-Z0-9-_.~!*'();]+$", + "type": "string" + }, + "existingSecretName": { + "type": "null" + } + } + }, + { + "properties": { + "apiKey": { + "type": "null" + }, + "existingSecretName": { + "type": "string" + } + } + } + ] + }, + "if": { + "properties": { + "apiKey": { + "type": "null" + }, + "existingSecretName": { + "type": "null" + } + } + }, + "then": { + "properties": { + "components": { + "properties": { + "apiKey": { + "properties": { + "secretProviderClass": { + "minLength": 1, + "type": "string" + } + }, + "required": ["secretProviderClass"] + } + } + } + } + } + }, { "if": { "allOf": [ @@ -5529,37 +5583,6 @@ } } ], - "anyOf": [ - { - "required": ["apiKey"] - }, - { - "required": ["existingSecretName"] - } - ], - "oneOf": [ - { - "properties": { - "apiKey": { - "pattern": "^[a-zA-Z0-9-_.~!*'();]+$", - "type": "string" - }, - "existingSecretName": { - "type": "null" - } - } - }, - { - "properties": { - "apiKey": { - "type": "null" - }, - "existingSecretName": { - "type": "string" - } - } - } - ], "properties": { "aggregator": { "additionalProperties": false, @@ -6186,6 +6209,15 @@ }, "type": "object" }, + "apiKey": { + "additionalProperties": false, + "properties": { + "secretProviderClass": { + "type": ["string", "null"] + } + }, + "type": "object" + }, "kubectl": { "additionalProperties": false, "deprecated": true, diff --git a/helm/values.schema.yaml b/helm/values.schema.yaml index 4e1ee8b4..aa547b0f 100644 --- a/helm/values.schema.yaml +++ b/helm/values.schema.yaml @@ -302,23 +302,7 @@ additionalProperties: false required: - host -# Enforce that exactly one of apiKey or existingSecretName must be set -# anyOf ensures at least one is required, oneOf ensures only one has a non-null value -anyOf: - - required: [apiKey] - - required: [existingSecretName] -oneOf: - - properties: - apiKey: - type: string - pattern: "^[a-zA-Z0-9-_.~!*'();]+$" - existingSecretName: - type: "null" - - properties: - apiKey: - type: "null" - existingSecretName: - type: string +# Root-level cross-field validations live in allOf below. properties: cloudAccountId: @@ -516,6 +500,23 @@ properties: additionalProperties: false type: object properties: + apiKey: + description: | + API key CSI configuration. Takes effect only when both top-level + apiKey and existingSecretName are null. + type: object + additionalProperties: false + properties: + secretProviderClass: + description: | + Name of a Kubernetes SecretProviderClass resource. When set, the + chart mounts the API key via the Secrets Store CSI Driver instead + of a Kubernetes Secret volume. The SecretProviderClass must be + created separately and configured to produce a file named after + serverConfig.containerSecretFileName at the mount path. + type: + - string + - "null" agent: description: | The CloudZero Agent. @@ -3065,6 +3066,41 @@ properties: # Root-level conditional validations that span multiple properties allOf: + # Conditional: API key source validation + # + # Top-level apiKey/existingSecretName take priority when set. If both are + # null, the chart falls back to components.apiKey where exactly one of + # value, name, or secretProviderClass must be non-null. + - if: + properties: + apiKey: + type: "null" + existingSecretName: + type: "null" + then: + properties: + components: + properties: + apiKey: + properties: + secretProviderClass: + type: string + minLength: 1 + required: [secretProviderClass] + else: + oneOf: + - properties: + apiKey: + type: string + pattern: "^[a-zA-Z0-9-_.~!*'();]+$" + existingSecretName: + type: "null" + - properties: + apiKey: + type: "null" + existingSecretName: + type: string + # Conditional: Federation mode constraint # # If defaults.federation.enabled is true, then components.agent.mode must be diff --git a/helm/values.yaml b/helm/values.yaml index 47e1ad8b..33049640 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -280,6 +280,13 @@ integrations: # Component-specific configuration settings. components: + # API key configuration for CSI-driven secret mounts. + apiKey: + # -- Name of a SecretProviderClass resource. When set (and both top-level + # -- apiKey and existingSecretName are null), the chart mounts the API key + # -- via the Secrets Store CSI Driver instead of a Kubernetes Secret volume. + secretProviderClass: null + # The agent here refers to the CloudZero Agent, which is the component that # collects metrics from the cluster and sends them to the aggregator. agent: diff --git a/tests/helm/template/alloy.yaml b/tests/helm/template/alloy.yaml index 8ed6edc7..ad07779c 100644 --- a/tests/helm/template/alloy.yaml +++ b/tests/helm/template/alloy.yaml @@ -1060,6 +1060,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -3040,13 +3042,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS diff --git a/tests/helm/template/cert-manager.yaml b/tests/helm/template/cert-manager.yaml index ded942b3..2b5fd9ed 100644 --- a/tests/helm/template/cert-manager.yaml +++ b/tests/helm/template/cert-manager.yaml @@ -975,6 +975,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -2934,13 +2936,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS diff --git a/tests/helm/template/federated.yaml b/tests/helm/template/federated.yaml index 3bf4a278..39819080 100644 --- a/tests/helm/template/federated.yaml +++ b/tests/helm/template/federated.yaml @@ -1063,6 +1063,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -3217,13 +3219,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS diff --git a/tests/helm/template/istio.yaml b/tests/helm/template/istio.yaml index df4965bf..2f93b2b8 100644 --- a/tests/helm/template/istio.yaml +++ b/tests/helm/template/istio.yaml @@ -990,6 +990,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -2950,13 +2952,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS diff --git a/tests/helm/template/kubestate.yaml b/tests/helm/template/kubestate.yaml index 57b6e7ba..c20f4ab4 100644 --- a/tests/helm/template/kubestate.yaml +++ b/tests/helm/template/kubestate.yaml @@ -1027,6 +1027,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -2490,13 +2492,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS diff --git a/tests/helm/template/manifest.yaml b/tests/helm/template/manifest.yaml index 16ef82b4..a43d2d6a 100644 --- a/tests/helm/template/manifest.yaml +++ b/tests/helm/template/manifest.yaml @@ -990,6 +990,8 @@ data: memory: 64Mi securityContext: {} tolerations: [] + apiKey: + secretProviderClass: null miscellaneous: configLoader: annotations: {} @@ -2949,13 +2951,13 @@ spec: volumeMounts: - name: insights-server-config mountPath: /etc/cloudzero-agent-insights - - name: tls-certs - mountPath: /etc/certs - readOnly: true - name: cloudzero-api-key mountPath: /etc/config/secrets/ subPath: "" readOnly: true + - name: tls-certs + mountPath: /etc/certs + readOnly: true livenessProbe: httpGet: scheme: HTTPS