diff --git a/.github/spell-ignore-words.txt b/.github/spell-ignore-words.txt index 44ee2c3..2714cda 100644 --- a/.github/spell-ignore-words.txt +++ b/.github/spell-ignore-words.txt @@ -2,3 +2,9 @@ fullname Kubernetes ConfigMap YAML +1h +1m +1s +SecretStore +ExternalSecret +secretKey diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 6068afb..97d66c1 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -68,6 +68,11 @@ jobs: - name: Setup k3s/k3d run: c2cciutils-k8s-install + - name: Install external-secret CRD + run: | + curl https://raw.githubusercontent.com/external-secrets/external-secrets/main/deploy/crds/bundle.yaml --output /tmp/external-secrets-crd.yaml + kubectl apply -f /tmp/external-secrets-crd.yaml + - name: Apply run: kubectl apply -f tests/expected.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a2f9915..07643da 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -56,6 +56,22 @@ repos: - custom - . - tests/expected.yaml + - repo: https://github.com/camptocamp/helm-common + rev: 2.0.1 + hooks: + - id: helm-template-gen + files: |- + (?x)( + ^templates/.*$ + |^values\.yaml$ + |^Chart\.yaml$ + |tests/values-external\.yaml$ + ) + args: + - --values=tests/values-external.yaml + - custom + - . + - tests/expected-external.yaml - repo: https://github.com/codespell-project/codespell rev: v2.3.0 hooks: @@ -64,6 +80,7 @@ repos: rev: v3.1.0 hooks: - id: prettier + exclude: ^tests/expected.*\.yaml$ additional_dependencies: - prettier@3.3.3 # npm - prettier-plugin-sh@0.14.0 # npm diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index 1ec3c74..04bbe31 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -13,3 +13,31 @@ } } {{- end }} + +{{- define "application.secrets.externaldockerregistry" -}} +{ + "auths": { + {{- range $registryName, $conf := . }} + {{- $url := ( default ( printf "{{ .%s-url }}" $registryName ) $conf.url ) }} + {{- $username := ( default ( printf "{{ .%s-username }}" $registryName ) $conf.username ) }} + {{- $password := ( default ( printf "{{ .%s-password }}" $registryName ) $conf.password ) }} + {{- $email := ( default ( printf "{{ .%s-email }}" $registryName ) $conf.email ) }} + {{ $url | quote }}: { + {{- if and ( hasKey $conf "username" ) ( hasKey $conf "password" ) }} + "auth": {{ printf "%s:%s" $conf.username $conf.password | b64enc | quote }}, + {{- else if hasKey $conf "username" }} + "auth": {{ printf "{{ ( printf \"%s:%s\" .%s-password ) | b64enc | quote }}" $conf.username "%s" $registryName }}, + {{- else if hasKey $conf "password" }} + "auth": {{ printf "{{ ( printf \"%s:%s\" .%s-username ) | b64enc | quote }}" "%s" $conf.password $registryName }}, + {{- else }} + "auth": {{ printf "{{ ( printf \"%s:%s\" .%s-username .%s-password ) | b64enc | quote }}" "%s" "%s" $registryName $registryName }}, + {{- end }} + "username": {{ $username | quote }}, + "password": {{ $password | quote }}, + "email": {{ $email | quote }} + }, + {{- end }} + "fix-end-comma": {"auth": ""} + } +} +{{- end }} diff --git a/templates/dockerhub-secret.yaml b/templates/dockerhub-secret.yaml index acbcf06..64fb1ca 100644 --- a/templates/dockerhub-secret.yaml +++ b/templates/dockerhub-secret.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.external }} {{- if .Values.dockerregistry }} apiVersion: v1 kind: Secret @@ -8,3 +9,4 @@ type: kubernetes.io/dockerconfigjson data: .dockerconfigjson: {{ include "secrets.dockerregistry" . | b64enc }} {{- end }} +{{- end }} diff --git a/templates/external-secret-docker-registry.yaml b/templates/external-secret-docker-registry.yaml new file mode 100644 index 0000000..5700c11 --- /dev/null +++ b/templates/external-secret-docker-registry.yaml @@ -0,0 +1,45 @@ +{{- if .Values.external }} +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: {{ include "common.fullname" ( dict "root" $ "service" $.Values ) }}-dockerregistry + {{- include "common.metadata" ( dict "root" $ "service" $.Values ) | nindent 2 }} +spec: + {{- with $.Values.dockerregistryRefreshInterval }} + refreshInterval: {{ . }} + {{- end }} + {{- with $.Values.dockerregistrySecretStoreRef }} + secretStoreRef: {{- toYaml . | nindent 4 }} + {{- end }} + target: + name: {{ include "common.fullname" ( dict "root" $ "service" $.Values ) }}-dockerregistry + template: + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: |- + {{- include "application.secrets.externaldockerregistry" .Values.dockerregistry | nindent 10 }} + data: + {{- range $registryName, $conf := .Values.dockerregistry }} + {{- with $conf.externalUsername }} + - secretKey: {{ $registryName }}-username + remoteRef: + key: {{ $conf.externalUsername }} + {{- end }} + {{- with $conf.externalPassword }} + - secretKey: {{ $registryName }}-password + remoteRef: + key: {{ $conf.externalPassword }} + {{- end }} + {{- with $conf.externalEmail }} + - secretKey: {{ $registryName }}-email + remoteRef: + key: {{ $conf.externalEmail }} + {{- end }} + {{- with $conf.externalUrl }} + - secretKey: {{ $registryName }}-url + remoteRef: + key: {{ $conf.externalUrl }} + {{- end }} + {{- end }} +{{- end }} diff --git a/templates/external-secret.yaml b/templates/external-secret.yaml new file mode 100644 index 0000000..2340a19 --- /dev/null +++ b/templates/external-secret.yaml @@ -0,0 +1,27 @@ +{{- if .Values.external }} +{{- with .Values.externalSecrets }} +{{- if ( or .data .dataFrom ) }} +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: {{ include "common.fullname" ( dict "root" $ "service" $.Values ) }} + {{- include "common.metadata" ( dict "root" $ "service" $.Values ) | nindent 2 }} +spec: + {{- with .refreshInterval }} + refreshInterval: {{ . }} + {{- end }} + {{- with .secretStoreRef }} + secretStoreRef: {{- toYaml . | nindent 4 }} + {{- end }} + target: + name: {{ include "common.fullname" ( dict "root" $ "service" $.Values ) }} + {{- with .dataFrom }} + dataFrom: {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .data }} + data: {{- include "common.dictToList" ( dict "keyName" "secretKey" "contents" . ) | nindent 4 }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/templates/secret.yaml b/templates/secret.yaml index 43a4ac4..bc396d1 100644 --- a/templates/secret.yaml +++ b/templates/secret.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.external }} {{- with .Values.secrets }} --- apiVersion: v1 @@ -17,3 +18,4 @@ data: {{- end }} {{- end }} {{- end }} +{{- end }} diff --git a/tests/expected-external.yaml b/tests/expected-external.yaml new file mode 100644 index 0000000..1919c6a --- /dev/null +++ b/tests/expected-external.yaml @@ -0,0 +1,94 @@ +--- +# Source: secrets/templates/external-secret-docker-registry.yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: custom-secrets-dockerregistry + labels: + helm.sh/chart: secrets + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: secrets + app.kubernetes.io/instance: custom + app.kubernetes.io/component: main + annotations: + testAnnotation: annotation value +spec: + refreshInterval: 1h + secretStoreRef: + kind: SecretStore + name: my-secret-store + target: + name: custom-secrets-dockerregistry + template: + type: kubernetes.io/dockerconfigjson + data: + .dockerconfigjson: |- + { + "auths": { + "{{ .docker.io-url }}": { + "auth": {{ ( printf "%s:%s" .docker.io-username .docker.io-password ) | b64enc | quote }}, + "username": "{{ .docker.io-username }}", + "password": "{{ .docker.io-password }}", + "email": "{{ .docker.io-email }}" + }, + "ghcr.io": { + "auth": "YzJjLWJvdC1naXMtY2k6MTIzNA==", + "username": "c2c-bot-gis-ci", + "password": "1234", + "email": "geospatial-bot@camptocamp.com" + }, + "https://index.docker.io/v1/": { + "auth": "YzJjZ2lzYm90OjEyMzQ=", + "username": "c2cgisbot", + "password": "1234", + "email": "docker-hub@camptocamp.com" + }, + "fix-end-comma": {"auth": ""} + } + } + data: + - secretKey: docker.io-username + remoteRef: + key: docker-username + - secretKey: docker.io-password + remoteRef: + key: docker-password + - secretKey: docker.io-email + remoteRef: + key: docker-email + - secretKey: docker.io-url + remoteRef: + key: docker-url +--- +# Source: secrets/templates/external-secret.yaml +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: custom-secrets + labels: + helm.sh/chart: secrets + app.kubernetes.io/version: "1.0" + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: secrets + app.kubernetes.io/instance: custom + app.kubernetes.io/component: main + annotations: + testAnnotation: annotation value +spec: + refreshInterval: 1h + secretStoreRef: + kind: SecretStore + name: my-secret-store + target: + name: custom-secrets + data: + - secretKey: test + remoteRef: + key: toto + - secretKey: test2 + remoteRef: + key: toto2 + - secretKey: test3 + remoteRef: + key: toto3 diff --git a/tests/values-external.yaml b/tests/values-external.yaml new file mode 100644 index 0000000..c81f40d --- /dev/null +++ b/tests/values-external.yaml @@ -0,0 +1,43 @@ +annotations: + testAnnotation: annotation value + +external: true +metadata: false + +dockerregistryRefreshInterval: 1h +dockerregistrySecretStoreRef: + name: my-secret-store + kind: SecretStore + +dockerregistry: + https://index.docker.io/v1/: + email: docker-hub@camptocamp.com + username: c2cgisbot + password: '1234' + url: https://index.docker.io/v1/ + docker.io: + externalEmail: docker-email + externalUsername: docker-username + externalPassword: docker-password + externalUrl: docker-url + ghcr.io: + email: geospatial-bot@camptocamp.com + username: c2c-bot-gis-ci + password: '1234' + url: ghcr.io + +externalSecrets: + refreshInterval: 1h + secretStoreRef: + name: my-secret-store + kind: SecretStore + data: + test: + remoteRef: + key: toto + test2: + remoteRef: + key: toto2 + test3: + remoteRef: + key: toto3 diff --git a/values.md b/values.md index a38ae92..c260b36 100644 --- a/values.md +++ b/values.md @@ -12,6 +12,13 @@ - **`labels`**: Refer to _[#/definitions/labels](#definitions/labels)_. - **`annotations`**: Refer to _[#/definitions/annotations](#definitions/annotations)_. - **`metadata`** _(boolean)_: Generate a ConfigMap with some metadata related to the chart. +- **`external`** _(boolean)_: Secrets from external sources. +- **`externalSecrets`** _(object)_: Cannot contain additional properties. + - **`refreshInterval`** _(string)_: The refresh interval like 1h, 1m, 1s. + - **`secretStoreRef`** _(object)_: defines which SecretStore to fetch the ExternalSecret data. + - **`dataFrom`** _(array)_: used to fetch all properties from a specific Provider data. + - **`data`** _(object)_: Data defines the connection between the Kubernetes Secret keys and the Provider data. Can contain additional properties. + - **Additional properties** _(object)_: defines the connection between the Kubernetes Secret key and the Provider data. The map key became the secretKey. - **`secrets`** _(object)_: Secrets configuration. Can contain additional properties. - **Additional properties** - **One of** @@ -22,11 +29,18 @@ - **`type`** _(string)_: Type of the secret. Must be one of: `["basicAuth"]`. - **`user`** _(string)_: Username. - **`password`** _(string)_: Password. +- **`dockerregistryRefreshInterval`** _(string)_: The refresh interval like 1h, 1m, 1s. +- **`dockerregistrySecretStoreRef`** _(object)_: defines which SecretStore to fetch the ExternalSecret data. - **`dockerregistry`** _(object)_: Docker registries authentication. Can contain additional properties. - **Additional properties** _(object)_: Cannot contain additional properties. - - **`username`** _(string, required)_: Username. - - **`password`** _(string, required)_: Password. + - **`username`** _(string)_: Username. + - **`password`** _(string)_: Password. - **`email`** _(string)_: Email. + - **`url`** _(string)_: URL, used only for external secret. + - **`externalUsername`** _(string)_: Key of the external secret for the username. + - **`externalPassword`** _(string)_: Key of the external secret for the password. + - **`externalEmail`** _(string)_: Key of the external secret for the email. + - **`externalUrl`** _(string)_: Key of the external secret for the URL. - **`configMap`** _(object)_: ConfigMap configuration. Can contain additional properties. - **Additional properties** - **One of** diff --git a/values.schema.json b/values.schema.json index 4778a0b..8dd4494 100644 --- a/values.schema.json +++ b/values.schema.json @@ -69,6 +69,36 @@ "type": "boolean", "description": "Generate a ConfigMap with some metadata related to the chart" }, + "external": { + "type": "boolean", + "description": "Secrets from external sources" + }, + "externalSecrets": { + "type": "object", + "additionalProperties": false, + "properties": { + "refreshInterval": { + "type": "string", + "description": "The refresh interval like 1h, 1m, 1s" + }, + "secretStoreRef": { + "type": "object", + "description": "defines which SecretStore to fetch the ExternalSecret data." + }, + "dataFrom": { + "type": "array", + "description": "used to fetch all properties from a specific Provider data." + }, + "data": { + "type": "object", + "description": "Data defines the connection between the Kubernetes Secret keys and the Provider data.", + "additionalProperties": { + "type": "object", + "description": "defines the connection between the Kubernetes Secret key and the Provider data. The map key became the secretKey" + } + } + } + }, "secrets": { "type": "object", "description": "Secrets configuration", @@ -114,6 +144,14 @@ ] } }, + "dockerregistryRefreshInterval": { + "type": "string", + "description": "The refresh interval like 1h, 1m, 1s" + }, + "dockerregistrySecretStoreRef": { + "type": "object", + "description": "defines which SecretStore to fetch the ExternalSecret data." + }, "dockerregistry": { "type": "object", "description": "Docker registries authentication", @@ -132,9 +170,28 @@ "email": { "type": "string", "description": "Email" + }, + "url": { + "type": "string", + "description": "URL, used only for external secret" + }, + "externalUsername": { + "type": "string", + "description": "Key of the external secret for the username" + }, + "externalPassword": { + "type": "string", + "description": "Key of the external secret for the password" + }, + "externalEmail": { + "type": "string", + "description": "Key of the external secret for the email" + }, + "externalUrl": { + "type": "string", + "description": "Key of the external secret for the URL" } - }, - "required": ["username", "password"] + } } }, "configMap": { diff --git a/values.yaml b/values.yaml index 516c4b6..46255b2 100644 --- a/values.yaml +++ b/values.yaml @@ -1,6 +1,8 @@ nameOverride: '' fullnameOverride: '' +external: false + dockerregistry: {} # registry-url: # email: docker-hub@camptocamp.com