Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion charts/pmm-ha/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: pmm-ha
description: A Helm chart for Percona Monitoring and Management (PMM)
type: application
version: 1.6.0
version: 1.6.1
appVersion: "3.9.0"
home: https://github.com/percona/pmm
maintainers:
Expand Down
23 changes: 16 additions & 7 deletions charts/pmm-ha/templates/pmm-secret-init-job.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,30 @@ spec:
- -c
- |
set -e


# An existing key must never be replaced. All PMM replicas share one PostgreSQL
# database, and the credentials already stored in it can only be decrypted with the
# key they were encrypted with. The secret is created here rather than by Helm, so it
# outlives `helm uninstall`: reinstalling over retained database storage has to adopt
# the key that is still there instead of generating a new one.
if kubectl get secret pg-encryption-key --namespace={{ .Release.Namespace }} >/dev/null 2>&1; then
echo "pg-encryption-key already exists, keeping the existing encryption key"
exit 0
fi

echo "Reading generated encryption key..."
SECRET_VALUE=$(cat /shared/secret-value)

if [ -z "$SECRET_VALUE" ]; then
echo "Error: Secret value is empty"
exit 1
fi
echo "Creating/updating pg-encryption-key secret..."

echo "Creating pg-encryption-key secret..."
kubectl create secret generic pg-encryption-key \
--namespace={{ .Release.Namespace }} \
--from-literal=key="$SECRET_VALUE" \
--dry-run=client -o yaml | kubectl apply -f -

--from-literal=key="$SECRET_VALUE"

echo "pg-encryption-key created successfully"
volumeMounts:
- name: shared-data
Expand Down
2 changes: 1 addition & 1 deletion charts/pmm/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: pmm
description: A Helm chart for Percona Monitoring and Management (PMM)
type: application
version: 1.9.0
version: 1.9.1
appVersion: "3.9.0"
home: https://github.com/percona/pmm
maintainers:
Expand Down
22 changes: 22 additions & 0 deletions charts/pmm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,28 @@ It removes all of the resources associated with the last release of the chart as
| `certs` | Optional certificates, if not provided PMM would use generated self-signed certificates, | `{}` |


### PMM encryption key

PMM encrypts the credentials of monitored services with a key stored at `/srv/pmm-encryption.key` on the PMM data volume. By default the chart also keeps a copy of that key in a Kubernetes secret, so the credentials stay readable if the data volume is lost while the database survives, which is possible when PMM is pointed at an external PostgreSQL.

An existing key is never replaced. The key on the data volume takes precedence, and the secret is only used to restore it when the volume has none, so restarts, upgrades and reinstalls over retained data keep working.

The secret is not owned by the Helm release, so it outlives `helm uninstall`. Back it up together with the rest of your PMM configuration:

```sh
kubectl get secret pmm-encryption-key -o yaml > pmm-encryption-key-backup.yaml
```

To supply your own key, create the secret before installing the chart. The value must be a base64-encoded Tink keyset, as produced by `pmm-encryption-rotation --generate-key`; see [PMM data encryption](https://docs.percona.com/percona-monitoring-and-management/3/admin/security/data_encryption.html).

| Name | Description | Value |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `encryptionKey.backupToSecret` | Keep a copy of the PMM encryption key in a Kubernetes secret. Grants the PMM pod's service account get and create on secrets in the release namespace; since `serviceAccount.create` is false by default that is the namespace's `default` service account. | `true` |
| `encryptionKey.secretName` | Name of the secret holding the copy of the encryption key. Defaults to `<fullname>-encryption-key`. | `""` |
| `encryptionKey.image.repository` | Repository for the image used to reconcile the key secret | `alpine/kubectl` |
| `encryptionKey.image.tag` | Tag for the image used to reconcile the key secret | `1.34.1` |


### PMM network configuration

| Name | Description | Value |
Expand Down
7 changes: 7 additions & 0 deletions charts/pmm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sh
{{- end }}
{{- end }}

{{/*
Name of the secret holding a copy of the PMM encryption key.
*/}}
{{- define "pmm.encryptionKeySecretName" -}}
{{- default (printf "%s-encryption-key" (include "pmm.fullname" .)) .Values.encryptionKey.secretName }}
{{- end }}

{{/*
Create password if it does not exist or reuse existing one.
*/}}
Expand Down
34 changes: 34 additions & 0 deletions charts/pmm/templates/encryption-key-rbac.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{{- if .Values.encryptionKey.backupToSecret -}}
{{/*
Namespaced permissions for the encryption-key init container. The chart's ClusterRole already
allows reading secrets; creating one is granted here so the extra permission stays scoped to the
release namespace.
*/}}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "pmm.fullname" . }}-encryption-key
{{- include "pmm.includeNamespace" . | nindent 2 }}
labels:
{{- include "pmm.labels" . | nindent 4 }}
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "pmm.fullname" . }}-encryption-key
{{- include "pmm.includeNamespace" . | nindent 2 }}
labels:
{{- include "pmm.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "pmm.fullname" . }}-encryption-key
subjects:
- kind: ServiceAccount
name: {{ include "pmm.serviceAccountName" . }}
namespace: {{ include "pmm.namespace" . }}
{{- end }}
91 changes: 90 additions & 1 deletion charts/pmm/templates/statefulset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,94 @@ spec:
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.initContainers }}
{{- if or .Values.encryptionKey.backupToSecret .Values.initContainers }}
initContainers:
{{- if .Values.encryptionKey.backupToSecret }}
# Generates a key only when the data volume has none. Which key is actually installed is
# decided by the next init container, which can also see the secret.
- name: encryption-key-generate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/bash
- -c
- |
set -e

if [ -s /srv/pmm-encryption.key ]; then
echo "Encryption key already present on the data volume."
exit 0
fi

echo "Generating a candidate encryption key..."
/usr/sbin/pmm-encryption-rotation --generate-key > /shared/generated-key
volumeMounts:
- name: {{ .Values.storage.name }}
mountPath: /srv
- name: encryption-key-workdir
mountPath: /shared
# Keeps the key on the data volume and the copy in the secret in sync. An existing key is
# never replaced: whichever of the two already exists wins, so restarts, upgrades and
# reinstalls over retained data keep decrypting the credentials already stored.
- name: encryption-key-reconcile
image: "{{ .Values.encryptionKey.image.repository }}:{{ .Values.encryptionKey.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
command:
- /bin/sh
- -c
- |
set -e

SECRET={{ include "pmm.encryptionKeySecretName" . }}
NAMESPACE={{ include "pmm.namespace" . }}
KEY_FILE=/srv/pmm-encryption.key

# A failure to reach the API server must stop the pod. Continuing would let PMM
# generate its own key without re-encrypting anything, leaving credentials that
# were stored under the previous key unreadable.
if kubectl get secret "$SECRET" --namespace="$NAMESPACE" >/dev/null 2>&1; then
SECRET_EXISTS=yes
else
SECRET_EXISTS=no
fi
Comment on lines +105 to +109

if [ -s "$KEY_FILE" ]; then
if [ "$SECRET_EXISTS" = "no" ]; then
echo "Backing up the encryption key from the data volume into secret $SECRET..."
kubectl create secret generic "$SECRET" --namespace="$NAMESPACE" \
--from-file=key="$KEY_FILE"
else
echo "Encryption key present on the data volume and already backed up."
fi
exit 0
fi

if [ "$SECRET_EXISTS" = "yes" ]; then
echo "Restoring the encryption key from secret $SECRET onto the data volume..."
kubectl get secret "$SECRET" --namespace="$NAMESPACE" \
-o jsonpath='{.data.key}' | base64 -d > "$KEY_FILE"
if [ ! -s "$KEY_FILE" ]; then
echo "Error: secret $SECRET holds no key" >&2
exit 1
fi
chmod 644 "$KEY_FILE"
exit 0
fi

echo "No encryption key found, installing the generated one..."
cp /shared/generated-key "$KEY_FILE"
chmod 644 "$KEY_FILE"
kubectl create secret generic "$SECRET" --namespace="$NAMESPACE" \
--from-file=key="$KEY_FILE"
volumeMounts:
- name: {{ .Values.storage.name }}
mountPath: /srv
- name: encryption-key-workdir
mountPath: /shared
{{- end }}
{{- with .Values.initContainers }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
Expand Down Expand Up @@ -145,6 +230,10 @@ spec:
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
{{- if .Values.encryptionKey.backupToSecret }}
- name: encryption-key-workdir
emptyDir: {}
{{- end }}
{{- if .Values.certs.name }}
- name: {{ .Values.certs.name }}
secret:
Expand Down
26 changes: 26 additions & 0 deletions charts/pmm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,32 @@ secret:
# GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET optional secret to integrate Grafana with internal iDP, requires other env defined as well under pmmEnv
# GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET:

## PMM encrypts the credentials of monitored services with a key stored at
## /srv/pmm-encryption.key, which lives on the PMM data volume. When enabled, the chart also
## keeps a copy of that key in a Kubernetes secret, so the credentials remain readable if the
## data volume is lost and PMM is pointed at an external PostgreSQL that survived.
## An existing key is never replaced: the key on the volume wins, and the secret is only used to
## restore it when the volume has none.
## ref: https://docs.percona.com/percona-monitoring-and-management/3/admin/security/data_encryption.html
##
encryptionKey:
## @param encryptionKey.backupToSecret Keep a copy of the PMM encryption key in a Kubernetes secret.
## The chart grants the PMM pod's service account get and create on secrets in the release
## namespace. Because `serviceAccount.create` is false by default, that is the namespace's
## `default` service account; set `serviceAccount.create` to true to scope the grant to a
## service account used only by PMM. Set this to false to keep the key on the data volume only.
##
backupToSecret: true
## @param encryptionKey.secretName Name of the secret holding the copy of the encryption key. Defaults to `<fullname>-encryption-key`.
##
secretName: ""
## @param encryptionKey.image.repository Repository for the image used to reconcile the key secret
## @param encryptionKey.image.tag Tag for the image used to reconcile the key secret
##
image:
repository: alpine/kubectl
tag: "1.34.1"

## @param certs Optional certificates, if not provided PMM would use generated self-signed certificates,
## please provide your own signed ssl certificates like this in base 64 format:
## certs:
Expand Down
Loading