Skip to content

K8SPS-409 | Encrypted backups#1243

Draft
mayankshah1607 wants to merge 3 commits intomainfrom
K8SPS-409
Draft

K8SPS-409 | Encrypted backups#1243
mayankshah1607 wants to merge 3 commits intomainfrom
K8SPS-409

Conversation

@mayankshah1607
Copy link
Copy Markdown
Member

CHANGE DESCRIPTION

Problem:
Short explanation of the problem.

Cause:
Short explanation of the root cause of the issue if applicable.

Solution:
Short explanation of the solution we are providing with this PR.

CHECKLIST

Jira

  • Is the Jira ticket created and referenced properly?
  • Does the Jira ticket have the proper statuses for documentation (Needs Doc) and QA (Needs QA)?
  • Does the Jira ticket link to the proper milestone (Fix Version field)?

Tests

  • Is an E2E test/test case added for the new feature/change?
  • Are unit tests added where appropriate?

Config/Logging/Testability

  • Are all needed new/changed options added to default YAML files?
  • Are all needed new/changed options added to the Helm Chart?
  • Did we add proper logging messages for operator actions?
  • Did we ensure compatibility with the previous version or cluster upgrade process?
  • Does the change support oldest and newest supported PS version?
  • Does the change support oldest and newest supported Kubernetes version?

Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
Copilot AI review requested due to automatic review settings March 18, 2026 04:26
@it-percona-cla
Copy link
Copy Markdown

it-percona-cla commented Mar 18, 2026

CLA assistant check
All committers have signed the CLA.

@pull-request-size pull-request-size bot added the size/L 100-499 lines label Mar 18, 2026

rm -rf "${tmpdir}/lost+found"

if [[ -n "${ENCRYPTION_KEY_FILE}" ]]; then
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[shfmt] reported by reviewdog 🐶

Suggested change
if [[ -n "${ENCRYPTION_KEY_FILE}" ]]; then
if [[ -n ${ENCRYPTION_KEY_FILE} ]]; then

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for encrypted backups/restores by introducing an encryption spec in backup storage configuration, wiring it through the backup sidecar, and updating restore flow to decrypt before prepare/move-back.

Changes:

  • Add encryption configuration to Backup/Storage specs and propagate it into the sidecar backup request/config.
  • Implement encryption key handling in the sidecar (read key from Secret, write temp key file, pass --encrypt-* flags to xtrabackup).
  • Introduce MySQL pod RBAC/ServiceAccount resources to allow in-pod Secret reads, and update CRDs/bundles/examples accordingly.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
pkg/xtrabackup/xtrabackup.go Pass encryption options into backup jobs; mount secret + set ENCRYPTION_KEY_FILE for restore jobs; extend BackupConfig.
pkg/mysql/mysql.go Add RBAC object constructors; conditionally set MySQL StatefulSet serviceAccountName for newer CR versions.
pkg/controller/ps/controller.go Create Role/RoleBinding/ServiceAccount during DB reconcile for newer CR versions.
cmd/sidecar/main.go Switch to constructing a backup handler via backup.NewHandler().
cmd/sidecar/handler/handler.go Remove backup handler factory wrapper.
cmd/sidecar/handler/backup/handler.go Add handler constructor/init with a controller-runtime k8s client.
cmd/sidecar/handler/backup/create.go Create temp encryption key file and pass encryption flags to xtrabackup.
build/run-backup.sh Include encryption in the JSON payload sent to the sidecar.
build/run-restore.sh Decrypt backup contents when ENCRYPTION_KEY_FILE is set.
api/v1/perconaservermysql_types.go Add EncryptionSpec to BackupStorageSpec and implement Secret key reading helper.
api/v1/perconaservermysqlbackup_types.go Add per-backup encryption override + storage fallback getter.
api/v1/zz_generated.deepcopy.go Update deep-copy generation for new encryption types/fields.
config/crd/bases/*.yaml Extend CRD OpenAPI schemas to include encryption blocks.
deploy/.yaml, deploy/backup/.yaml Update shipped CRDs/bundles and example manifests with encryption fields.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +159 to +167
func (b *PerconaServerMySQLBackup) GetEncryption(storage *BackupStorageSpec) *EncryptionSpec {
if b.Spec.Encryption != nil {
return b.Spec.Encryption
}
if storage != nil && storage.Encryption != nil {
return storage.Encryption
}
return nil
}
Comment on lines +152 to +155
if cr.CompareVersion("1.1.0") >= 0 {
_, _, sa := RBAC(cr)
spec.ServiceAccountName = sa.GetName()
}
{
APIGroups: []string{corev1.SchemeGroupVersion.Group},
Resources: []string{"secrets"},
Verbs: []string{"get", "list"},
Comment on lines +51 to +52
k8sClient, err := client.New(config.GetConfigOrDie(), client.Options{})
if err != nil {
http.Error(w, "backup failed", http.StatusInternalServerError)
return
}
defer os.Remove(encryptionKeyFile) //nolint:errcheck
Comment on lines 40 to 47
h.getNamespaceFunc = func() (string, error) {
ns, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
if err != nil {
return "", errors.Wrap(err, "read namespace file")
}

return string(ns), nil
}
Comment on lines +548 to +559
if cr.CompareVersion("1.1.0") >= 0 {
role, binding, sa := mysql.RBAC(cr)
if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, role, r.Scheme); err != nil {
return errors.Wrap(err, "create role")
}
if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, sa, r.Scheme); err != nil {
return errors.Wrap(err, "create service account")
}
if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, binding, r.Scheme); err != nil {
return errors.Wrap(err, "create role binding")
}
}
Comment on lines +252 to +263
tempFile, err := os.CreateTemp(os.TempDir(), "encryption-key-*.key")
if err != nil {
return "", errors.Wrap(err, "create temporary file")
}

if _, err := tempFile.WriteString(key); err != nil {
return "", errors.Wrap(err, "write key to file")
}
if err := tempFile.Close(); err != nil {
return "", errors.Wrap(err, "close file")
}
return tempFile.Name(), nil
Comment on lines +366 to +385
type EncryptionSpec struct {
Enabled bool `json:"enabled,omitempty"`
SecretName string `json:"secretName,omitempty"`
Key string `json:"key,omitempty"`
}

func (e *EncryptionSpec) ReadEncryptionKey(ctx context.Context, cl client.Reader, namespace string) (string, error) {
if !e.Enabled {
return "", fmt.Errorf("encryption is not enabled")
}

secret := &corev1.Secret{}
if err := cl.Get(ctx, types.NamespacedName{Namespace: namespace, Name: e.SecretName}, secret); err != nil {
return "", errors.Wrap(err, "get encryption secret")
}

value := secret.Data[e.Key]
if len(value) == 0 {
return "", fmt.Errorf("encryption key not found in secret")
}
@JNKPercona
Copy link
Copy Markdown
Collaborator

Test Name Result Time
async-ignore-annotations-8-4 passed 00:06:36
async-global-metadata-8-4 passed 00:14:48
async-upgrade-8-0 passed 00:12:39
async-upgrade-8-4 passed 00:12:44
auto-config-8-4 passed 00:28:06
config-8-4 passed 00:16:28
config-router-8-0 passed 00:07:18
config-router-8-4 passed 00:07:24
demand-backup-minio-8-0 passed 00:19:40
demand-backup-minio-8-4 passed 00:20:08
demand-backup-cloud-8-4 passed 00:20:43
demand-backup-retry-8-4 passed 00:19:25
async-data-at-rest-encryption-8-0 passed 00:12:41
async-data-at-rest-encryption-8-4 passed 00:13:01
gr-global-metadata-8-4 passed 00:14:58
gr-data-at-rest-encryption-8-0 passed 00:16:45
gr-data-at-rest-encryption-8-4 passed 00:13:52
gr-demand-backup-minio-8-4 passed 00:14:38
gr-demand-backup-cloud-8-4 passed 00:21:58
gr-demand-backup-haproxy-8-4 passed 00:10:19
gr-finalizer-8-4 passed 00:05:37
gr-haproxy-8-0 passed 00:04:48
gr-haproxy-8-4 passed 00:04:11
gr-ignore-annotations-8-4 passed 00:04:41
gr-init-deploy-8-0 passed 00:10:05
gr-init-deploy-8-4 passed 00:09:20
gr-one-pod-8-4 passed 00:06:06
gr-recreate-8-4 passed 00:16:44
gr-scaling-8-4 passed 00:08:38
gr-scheduled-backup-8-4 failure 00:14:27
gr-security-context-8-4 passed 00:09:48
gr-self-healing-8-4 passed 00:22:19
gr-tls-cert-manager-8-4 passed 00:09:04
gr-users-8-4 passed 00:05:39
gr-upgrade-8-0 passed 00:09:08
gr-upgrade-8-4 passed 00:09:48
haproxy-8-0 passed 00:09:13
haproxy-8-4 passed 00:08:12
init-deploy-8-0 passed 00:06:43
init-deploy-8-4 passed 00:05:45
limits-8-4 passed 00:08:09
monitoring-8-4 passed 00:14:04
one-pod-8-0 passed 00:05:54
one-pod-8-4 passed 00:05:33
operator-self-healing-8-4 passed 00:14:10
pvc-resize-8-4 passed 00:06:45
recreate-8-4 passed 00:12:50
scaling-8-4 passed 00:11:36
scheduled-backup-8-0 failure 00:14:47
scheduled-backup-8-4 failure 00:20:44
service-per-pod-8-4 passed 00:06:33
sidecars-8-4 passed 00:04:36
smart-update-8-4 passed 00:09:13
storage-8-4 passed 00:04:05
telemetry-8-4 passed 00:06:05
tls-cert-manager-8-4 passed 00:11:39
users-8-0 passed 00:08:25
users-8-4 passed 00:07:33
version-service-8-4 passed 00:20:47
Summary Value
Tests Run 59/59
Job Duration 01:59:34
Total Test Time 11:18:24

commit: 27b1d97
image: perconalab/percona-server-mysql-operator:PR-1243-27b1d970

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L 100-499 lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants