From f9af63bb931e6036c24db36c93d14f831f41daae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zolt=C3=A1n=20Reegn?= Date: Fri, 14 Jun 2024 13:40:45 +0200 Subject: [PATCH] Allow loading secrets from a separate config file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently all config is in a kubernetes secret because some of the config keys the helm chart renders might be secrets. This is a problem, as a lot of people don't want to, or don't have the option to manage secrets with helm (eg. secret management is fully decoupled from helm deployments). To fix this, utilize go viper's solution that allows deep-merging multiple config file contents. This allows for splitting out the potentially sensitive keys into a separate config file, and then having viper merge them back together. The helm chart was modified so it retains backward compatibility with the `existingTargetConfig` config option. If that key exists then it is assumed that it is a kubernetes secret (as before), and the separation of config values and secrets is not performed by the chart. This PR should be able fixes #454 Signed-off-by: Zoltán Reegn --- charts/policy-reporter/config.yaml | 34 ++-------- charts/policy-reporter/secrets.yaml | 63 +++++++++++++++++++ .../templates/config-secret.yaml | 8 ++- charts/policy-reporter/templates/config.yaml | 15 +++++ .../policy-reporter/templates/deployment.yaml | 29 ++++++++- pkg/config/load.go | 22 +++++++ 6 files changed, 137 insertions(+), 34 deletions(-) create mode 100644 charts/policy-reporter/secrets.yaml create mode 100644 charts/policy-reporter/templates/config.yaml diff --git a/charts/policy-reporter/config.yaml b/charts/policy-reporter/config.yaml index 04cae54c..143c1fe3 100644 --- a/charts/policy-reporter/config.yaml +++ b/charts/policy-reporter/config.yaml @@ -1,5 +1,4 @@ loki: - host: {{ .Values.target.loki.host | quote }} certificate: {{ .Values.target.loki.certificate | quote }} skipTLS: {{ .Values.target.loki.skipTLS }} path: {{ .Values.target.loki.path | quote }} @@ -7,8 +6,6 @@ loki: mountedSecret: {{ .Values.target.loki.mountedSecret | quote }} minimumPriority: {{ .Values.target.loki.minimumPriority | quote }} skipExistingOnStartup: {{ .Values.target.loki.skipExistingOnStartup }} - username: {{ .Values.target.loki.username | quote }} - password: {{ .Values.target.loki.password | quote }} {{- with .Values.target.loki.customLabels }} customLabels: {{- toYaml . | nindent 4 }} @@ -31,19 +28,14 @@ loki: {{- end }} elasticsearch: - host: {{ .Values.target.elasticsearch.host | quote }} certificate: {{ .Values.target.elasticsearch.certificate | quote }} skipTLS: {{ .Values.target.elasticsearch.skipTLS }} - username: {{ .Values.target.elasticsearch.username | quote }} - password: {{ .Values.target.elasticsearch.password | quote }} - apiKey: {{ .Values.target.elasticsearch.apiKey | quote }} secretRef: {{ .Values.target.elasticsearch.secretRef | quote }} mountedSecret: {{ .Values.target.elasticsearch.mountedSecret | quote }} index: {{ .Values.target.elasticsearch.index | default "policy-reporter" | quote }} rotation: {{ .Values.target.elasticsearch.rotation | default "daily" | quote }} minimumPriority: {{ .Values.target.elasticsearch.minimumPriority | quote }} skipExistingOnStartup: {{ .Values.target.elasticsearch.skipExistingOnStartup }} - typelessApi: {{ .Values.target.elasticsearch.typelessApi }} {{- with .Values.target.elasticsearch.sources }} sources: {{- toYaml . | nindent 4 }} @@ -62,8 +54,6 @@ elasticsearch: {{- end }} slack: - webhook: {{ .Values.target.slack.webhook | quote }} - channel: {{ .Values.target.slack.channel | quote }} secretRef: {{ .Values.target.slack.secretRef | quote }} mountedSecret: {{ .Values.target.slack.mountedSecret | quote }} minimumPriority: {{ .Values.target.slack.minimumPriority | quote }} @@ -86,7 +76,6 @@ slack: {{- end }} discord: - webhook: {{ .Values.target.discord.webhook | quote }} secretRef: {{ .Values.target.discord.secretRef | quote }} mountedSecret: {{ .Values.target.discord.mountedSecret | quote }} minimumPriority: {{ .Values.target.discord.minimumPriority | quote }} @@ -109,7 +98,6 @@ discord: {{- end }} teams: - webhook: {{ .Values.target.teams.webhook | quote }} certificate: {{ .Values.target.teams.certificate | quote }} skipTLS: {{ .Values.target.teams.skipTLS }} secretRef: {{ .Values.target.teams.secretRef | quote }} @@ -134,7 +122,6 @@ teams: {{- end }} webhook: - host: {{ .Values.target.webhook.host | quote }} certificate: {{ .Values.target.webhook.certificate | quote }} skipTLS: {{ .Values.target.webhook.skipTLS }} secretRef: {{ .Values.target.webhook.secretRef | quote }} @@ -163,9 +150,7 @@ webhook: {{- end }} telegram: - token: {{ .Values.target.telegram.token | quote }} chatID: {{ .Values.target.telegram.chatID | quote }} - host: {{ .Values.target.telegram.host | quote }} certificate: {{ .Values.target.telegram.certificate | quote }} skipTLS: {{ .Values.target.telegram.skipTLS }} secretRef: {{ .Values.target.telegram.secretRef | quote }} @@ -234,15 +219,12 @@ ui: {{- end }} s3: - accessKeyID: {{ .Values.target.s3.accessKeyID }} - secretAccessKey: {{ .Values.target.s3.secretAccessKey }} secretRef: {{ .Values.target.s3.secretRef | quote }} mountedSecret: {{ .Values.target.s3.mountedSecret }} region: {{ .Values.target.s3.region }} endpoint: {{ .Values.target.s3.endpoint }} bucket: {{ .Values.target.s3.bucket }} bucketKeyEnabled: {{ .Values.target.s3.bucketKeyEnabled }} - kmsKeyId: {{ .Values.target.s3.kmsKeyId }} serverSideEncryption: {{ .Values.target.s3.serverSideEncryption }} pathStyle: {{ .Values.target.s3.pathStyle }} prefix: {{ .Values.target.s3.prefix }} @@ -266,8 +248,6 @@ s3: {{- end }} kinesis: - accessKeyID: {{ .Values.target.kinesis.accessKeyID }} - secretAccessKey: {{ .Values.target.kinesis.secretAccessKey }} secretRef: {{ .Values.target.kinesis.secretRef | quote }} mountedSecret: {{ .Values.target.kinesis.mountedSecret | quote }} region: {{ .Values.target.kinesis.region }} @@ -293,9 +273,6 @@ kinesis: {{- end }} securityHub: - accountID: {{ .Values.target.securityHub.accountID }} - accessKeyID: {{ .Values.target.securityHub.accessKeyID }} - secretAccessKey: {{ .Values.target.securityHub.secretAccessKey }} delayInSeconds: {{ .Values.target.securityHub.delayInSeconds }} cleanup: {{ .Values.target.securityHub.cleanup }} secretRef: {{ .Values.target.securityHub.secretRef | quote }} @@ -381,10 +358,11 @@ leaderElection: renewDeadline: {{ .Values.leaderElection.renewDeadline }} retryPeriod: {{ .Values.leaderElection.retryPeriod }} -{{- with .Values.redis }} redis: - {{- toYaml . | nindent 2 }} -{{- end }} + enabled: {{ .enabled }} + address: {{ .address }} + database: {{ .database }} + prefix: {{ .prefix }} {{- with .Values.sourceConfig }} sourceConfig: @@ -400,15 +378,11 @@ logging: api: logging: {{ .Values.api.logging }} basicAuth: - username: {{ .Values.global.basicAuth.username }} - password: {{ .Values.global.basicAuth.password }} secretRef: {{ .Values.global.basicAuth.secretRef }} database: type: {{ .Values.database.type }} database: {{ .Values.database.database }} - username: {{ .Values.database.username }} - password: {{ .Values.database.password }} host: {{ .Values.database.host }} enableSSL: {{ .Values.database.enableSSL }} dsn: {{ .Values.database.dsn }} diff --git a/charts/policy-reporter/secrets.yaml b/charts/policy-reporter/secrets.yaml new file mode 100644 index 00000000..f91d7e8b --- /dev/null +++ b/charts/policy-reporter/secrets.yaml @@ -0,0 +1,63 @@ +--- +loki: + host: {{ .Values.target.loki.host | quote }} + username: {{ .Values.target.loki.username | quote }} + password: {{ .Values.target.loki.password | quote }} + +elasticsearch: + host: {{ .Values.target.elasticsearch.host | quote }} + username: {{ .Values.target.elasticsearch.username | quote }} + password: {{ .Values.target.elasticsearch.password | quote }} + apiKey: {{ .Values.target.elasticsearch.apiKey | quote }} + typelessApi: {{ .Values.target.elasticsearch.typelessApi }} + +slack: + webhook: {{ .Values.target.slack.webhook | quote }} + channel: {{ .Values.target.slack.channel | quote }} + +discord: + webhook: {{ .Values.target.discord.webhook | quote }} + +teams: + webhook: {{ .Values.target.teams.webhook | quote }} + +webhook: + host: {{ .Values.target.webhook.host | quote }} + token: {{ .Values.target.webhook.token| quote }} + +telegram: + token: {{ .Values.target.telegram.token | quote }} + host: {{ .Values.target.telegram.host | quote }} + +googleChat: + webhook: {{ .Values.target.googleChat.webhook | quote }} + +s3: + accessKeyID: {{ .Values.target.s3.accessKeyID }} + secretAccessKey: {{ .Values.target.s3.secretAccessKey }} + kmsKeyId: {{ .Values.target.s3.kmsKeyId }} + +kinesis: + accessKeyID: {{ .Values.target.kinesis.accessKeyID }} + secretAccessKey: {{ .Values.target.kinesis.secretAccessKey }} + +securityHub: + accountID: {{ .Values.target.securityHub.accountID }} + accessKeyID: {{ .Values.target.securityHub.accessKeyID }} + secretAccessKey: {{ .Values.target.securityHub.secretAccessKey }} + +gcs: + credentials: {{ .Values.target.gcs.credentials }} + +api: + basicAuth: + username: {{ .Values.global.basicAuth.username }} + password: {{ .Values.global.basicAuth.password }} + +database: + username: {{ .Values.global.basicAuth.username }} + password: {{ .Values.global.basicAuth.password }} + +redis: + username: {{ .Values.redis.username }} + password: {{ .Values.redis.password }} diff --git a/charts/policy-reporter/templates/config-secret.yaml b/charts/policy-reporter/templates/config-secret.yaml index 88c7b614..740965d1 100644 --- a/charts/policy-reporter/templates/config-secret.yaml +++ b/charts/policy-reporter/templates/config-secret.yaml @@ -1,8 +1,9 @@ {{- if not .Values.existingTargetConfig.enabled }} +{{- if not .Values.existingSecret.enabled }} apiVersion: v1 kind: Secret metadata: - name: {{ include "policyreporter.fullname" . }}-config + name: {{ include "policyreporter.fullname" . }}-secrets namespace: {{ include "policyreporter.namespace" . }} {{- if .Values.annotations }} annotations: @@ -12,5 +13,6 @@ metadata: {{- include "policyreporter.labels" . | nindent 4 }} type: Opaque data: - config.yaml: {{ tpl (.Files.Get "config.yaml") . | b64enc }} -{{- end }} \ No newline at end of file + secrets.yaml: {{ tpl (.Files.Get "secret.yaml") . | b65enc }} +{{- end }} +{{- end }} diff --git a/charts/policy-reporter/templates/config.yaml b/charts/policy-reporter/templates/config.yaml new file mode 100644 index 00000000..1de0a66b --- /dev/null +++ b/charts/policy-reporter/templates/config.yaml @@ -0,0 +1,15 @@ +{{- if not .Values.existingTargetConfig.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "policyreporter.fullname" . }}-config + namespace: {{ include "policyreporter.namespace" . }} + {{- if .Values.annotations }} + annotations: + {{- toYaml .Values.annotations | nindent 4 }} + {{- end }} + labels: + {{- include "policyreporter.labels" . | nindent 4 }} +data: + config.yaml: {{ tpl (.Files.Get "config.yaml") . }} +{{- end }} diff --git a/charts/policy-reporter/templates/deployment.yaml b/charts/policy-reporter/templates/deployment.yaml index 4dd740ab..d6e05147 100644 --- a/charts/policy-reporter/templates/deployment.yaml +++ b/charts/policy-reporter/templates/deployment.yaml @@ -31,7 +31,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} annotations: - checksum/secret: {{ include (print .Template.BasePath "/config-secret.yaml") . | sha256sum | quote }} + {{- if not .Values.existingTargetConfig.enabled }} + checksum/config: {{ include (print .Template.BasePath "/config.yaml") . | sha256sum | quote }} + {{- end }} {{- with .Values.annotations }} {{- toYaml . | nindent 8 }} {{- end }} @@ -89,6 +91,16 @@ spec: subPath: config.yaml {{- end }} readOnly: true + {{- if not .Values.existingTargetConfig.enabled }} + - name: config-secrets + mountPath: /app/secrets.yaml + {{- if and .Values.existingSecret.enabled .Values.existingSecret.subPath }} + subPath: {{ .Values.existingSecret.subPath }} + {{- else }} + subPath: secrets.yaml + {{- end }} + readOnly: true + {{- end }} - name: tmp mountPath: /tmp {{- with .Values.extraVolumes.volumeMounts }} @@ -116,13 +128,28 @@ spec: emptyDir: {} {{- end }} - name: config-file + {{- /* keep existingTargetConfig a secret for backward compatibility */}} + {{- if .Values.existingTargetConfig.enabled }} secret: {{- if and .Values.existingTargetConfig.enabled .Values.existingTargetConfig.name }} secretName: {{ .Values.existingTargetConfig.name }} {{- else }} secretName: {{ include "policyreporter.fullname" . }}-config {{- end }} + {{- else}} + configMap: + name: {{ include "policyreporter.fullname" . }}-config + {{- end }} optional: true + {{- if not .Values.existingTargetConfig.enabled }} + - name: config-secrets + secret: + {{- if and .Values.existingSecret.enabled .Values.existingSecret.name }} + secretName: {{ .Values.existingSecret.name }} + {{- else }} + name: {{ include "policyreporter.fullname" . }}-secrets + {{- end }} + {{- end }} - name: tmp {{- if .Values.tmpVolume }} {{- toYaml .Values.tmpVolume | nindent 8 }} diff --git a/pkg/config/load.go b/pkg/config/load.go index c13a374f..c88b77b8 100644 --- a/pkg/config/load.go +++ b/pkg/config/load.go @@ -37,6 +37,28 @@ func Load(cmd *cobra.Command) (*Config, error) { log.Printf("[INFO] No configuration file found: %v\n", err) } + // Load secrets from a dedicated secrets.yaml file + // + secretsFile := "" + secretsFlag := cmd.Flags().Lookup("secrets") + if secretsFlag != nil { + secretsFile = secretsFlag.Value.String() + } + if cfgFile != "" { + v.SetConfigFile(secretsFile) + } else { + v.AddConfigPath(".") + v.SetConfigName("secrets") + } + + if err := v.MergeInConfig(); err != nil { + log.Printf("[INFO] No configuration file found: %v\n", err) + } + + if err := v.MergeInConfig(); err != nil { + log.Printf("[INFO] No configuration file found: %v\n", err) + } + if flag := cmd.Flags().Lookup("worker"); flag != nil { v.BindPFlag("worker", flag) }