From 6340b67c3a9aee686e78846ceaff4541b51c1596 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 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 --- 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 04cae54c3..143c1fe3d 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 000000000..f91d7e8bf --- /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 88c7b614c..740965d10 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 000000000..1de0a66bf --- /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 4dd740abb..d6e051471 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 c13a374fa..c88b77b84 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) }