Repository: mariadb-operator/mariadb-operator

Issue:
[Feature] S3 encryption via SSE-C
**Is your feature request related to a problem? Please describe.**

While backup encryption is still an open issue (#652), a separate desire would be to provide data at rest encryption for externally managed storage as offered via S3-compatible interfaces.

**Describe the solution you'd like**

Implement SSE-C.

**Describe alternatives you've considered**

Backup encryption, which is still a work in progress and less trivial to implement than adding a few request headers for security improvement, depending on provider trust.

**Additional context**

[SSE-C](https://min.io/docs/minio/linux/administration/server-side-encryption/server-side-encryption-sse-c.html) is supported by MinIO and AWS, and it really revolves around adding a few request headers and then trusting the opposing party to play ball and treat the key as ephemeral. [Developer documentation in the AWS SDK](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ServerSideEncryptionCustomerKeys.html)

Discussion:

Solution patch:
diff --git a/api/v1alpha1/base_types.go b/api/v1alpha1/base_types.go
index e8cd4f9b..6808ef64 100644
--- a/api/v1alpha1/base_types.go
+++ b/api/v1alpha1/base_types.go
@@ -763,6 +763,21 @@ type S3 struct {
 	// +optional
 	// +operator-sdk:csv:customresourcedefinitions:type=spec
 	TLS *TLSS3 `json:"tls,omitempty"`
+	// SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+	// The secret must contain a 32-byte key (256 bits) in the specified key.
+	// This enables server-side encryption where you provide and manage the encryption key.
+	// +optional
+	// +operator-sdk:csv:customresourcedefinitions:type=spec,xDescriptors={"urn:alm:descriptor:com.tectonic.ui:advanced"}
+	SSEC *SSECConfig `json:"ssec,omitempty"`
+}
+
+// SSECConfig defines the configuration for SSE-C (Server-Side Encryption with Customer-Provided Keys).
+type SSECConfig struct {
+	// CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+	// The key must be a 32-byte (256-bit) key encoded in base64.
+	// +kubebuilder:validation:Required
+	// +operator-sdk:csv:customresourcedefinitions:type=spec
+	CustomerKeySecretKeyRef SecretKeySelector `json:"customerKeySecretKeyRef"`
 }
 
 // Metadata defines the metadata to added to resources.
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 6e3318fe..8b8684a5 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -4275,6 +4275,11 @@ func (in *S3) DeepCopyInto(out *S3) {
 		*out = new(TLSS3)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.SSEC != nil {
+		in, out := &in.SSEC, &out.SSEC
+		*out = new(SSECConfig)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new S3.
@@ -4317,6 +4322,22 @@ func (in *SQLTemplate) DeepCopy() *SQLTemplate {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SSECConfig) DeepCopyInto(out *SSECConfig) {
+	*out = *in
+	out.CustomerKeySecretKeyRef = in.CustomerKeySecretKeyRef
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSECConfig.
+func (in *SSECConfig) DeepCopy() *SSECConfig {
+	if in == nil {
+		return nil
+	}
+	out := new(SSECConfig)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Schedule) DeepCopyInto(out *Schedule) {
 	*out = *in
diff --git a/cmd/backup/main.go b/cmd/backup/main.go
index 3a255df7..6609469f 100644
--- a/cmd/backup/main.go
+++ b/cmd/backup/main.go
@@ -16,6 +16,10 @@ import (
 	ctrl "sigs.k8s.io/controller-runtime"
 )
 
+const (
+	s3SSECCustomerKeyEnv = "MARIADB_OPERATOR_S3_SSEC_CUSTOMER_KEY"
+)
+
 var (
 	logger = ctrl.Log
 
@@ -175,16 +179,23 @@ func getBackupProcessor() (backup.BackupProcessor, error) {
 func getBackupStorage(processor backup.BackupProcessor) (backup.BackupStorage, error) {
 	if s3 {
 		logger.Info("configuring S3 backup storage")
+		opts := []backup.S3BackupStorageOpt{
+			backup.WithTLS(s3TLS),
+			backup.WithCACertPath(s3CACertPath),
+			backup.WithRegion(s3Region),
+			backup.WithPrefix(s3Prefix),
+		}
+		if ssecKey := os.Getenv(s3SSECCustomerKeyEnv); ssecKey != "" {
+			logger.Info("configuring S3 SSE-C encryption")
+			opts = append(opts, backup.WithSSECCustomerKey(ssecKey))
+		}
 		return backup.NewS3BackupStorage(
 			path,
 			s3Bucket,
 			s3Endpoint,
 			processor,
 			logger.WithName("s3-storage"),
-			backup.WithTLS(s3TLS),
-			backup.WithCACertPath(s3CACertPath),
-			backup.WithRegion(s3Region),
-			backup.WithPrefix(s3Prefix),
+			opts...,
 		)
 	}
 	logger.Info("configuring filesystem backup storage")
diff --git a/config/crd/bases/k8s.mariadb.com_backups.yaml b/config/crd/bases/k8s.mariadb.com_backups.yaml
index 260aea9e..0904f520 100644
--- a/config/crd/bases/k8s.mariadb.com_backups.yaml
+++ b/config/crd/bases/k8s.mariadb.com_backups.yaml
@@ -894,6 +894,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
diff --git a/config/crd/bases/k8s.mariadb.com_mariadbs.yaml b/config/crd/bases/k8s.mariadb.com_mariadbs.yaml
index 7f782d8c..524abcde 100644
--- a/config/crd/bases/k8s.mariadb.com_mariadbs.yaml
+++ b/config/crd/bases/k8s.mariadb.com_mariadbs.yaml
@@ -706,6 +706,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
diff --git a/config/crd/bases/k8s.mariadb.com_physicalbackups.yaml b/config/crd/bases/k8s.mariadb.com_physicalbackups.yaml
index 13276167..c7dca2be 100644
--- a/config/crd/bases/k8s.mariadb.com_physicalbackups.yaml
+++ b/config/crd/bases/k8s.mariadb.com_physicalbackups.yaml
@@ -648,6 +648,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
diff --git a/config/crd/bases/k8s.mariadb.com_restores.yaml b/config/crd/bases/k8s.mariadb.com_restores.yaml
index c612e7c4..f4d49e14 100644
--- a/config/crd/bases/k8s.mariadb.com_restores.yaml
+++ b/config/crd/bases/k8s.mariadb.com_restores.yaml
@@ -559,6 +559,29 @@ spec:
                     - key
                     type: object
                     x-kubernetes-map-type: atomic
+                  ssec:
+                    description: |-
+                      SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                      The secret must contain a 32-byte key (256 bits) in the specified key.
+                      This enables server-side encryption where you provide and manage the encryption key.
+                    properties:
+                      customerKeySecretKeyRef:
+                        description: |-
+                          CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                          The key must be a 32-byte (256-bit) key encoded in base64.
+                        properties:
+                          key:
+                            type: string
+                          name:
+                            default: ""
+                            type: string
+                        required:
+                        - key
+                        type: object
+                        x-kubernetes-map-type: atomic
+                    required:
+                    - customerKeySecretKeyRef
+                    type: object
                   tls:
                     description: TLS provides the configuration required to establish
                       TLS connections with S3.
diff --git a/deploy/charts/mariadb-operator-crds/templates/crds.yaml b/deploy/charts/mariadb-operator-crds/templates/crds.yaml
index 66a3a953..a2175a5c 100644
--- a/deploy/charts/mariadb-operator-crds/templates/crds.yaml
+++ b/deploy/charts/mariadb-operator-crds/templates/crds.yaml
@@ -893,6 +893,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -2820,6 +2843,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -12734,6 +12780,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -13557,6 +13626,29 @@ spec:
                     - key
                     type: object
                     x-kubernetes-map-type: atomic
+                  ssec:
+                    description: |-
+                      SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                      The secret must contain a 32-byte key (256 bits) in the specified key.
+                      This enables server-side encryption where you provide and manage the encryption key.
+                    properties:
+                      customerKeySecretKeyRef:
+                        description: |-
+                          CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                          The key must be a 32-byte (256-bit) key encoded in base64.
+                        properties:
+                          key:
+                            type: string
+                          name:
+                            default: ""
+                            type: string
+                        required:
+                        - key
+                        type: object
+                        x-kubernetes-map-type: atomic
+                    required:
+                    - customerKeySecretKeyRef
+                    type: object
                   tls:
                     description: TLS provides the configuration required to establish
                       TLS connections with S3.
diff --git a/deploy/crds/crds.yaml b/deploy/crds/crds.yaml
index 66a3a953..a2175a5c 100644
--- a/deploy/crds/crds.yaml
+++ b/deploy/crds/crds.yaml
@@ -893,6 +893,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -2820,6 +2843,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -12734,6 +12780,29 @@ spec:
                         - key
                         type: object
                         x-kubernetes-map-type: atomic
+                      ssec:
+                        description: |-
+                          SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                          The secret must contain a 32-byte key (256 bits) in the specified key.
+                          This enables server-side encryption where you provide and manage the encryption key.
+                        properties:
+                          customerKeySecretKeyRef:
+                            description: |-
+                              CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                              The key must be a 32-byte (256-bit) key encoded in base64.
+                            properties:
+                              key:
+                                type: string
+                              name:
+                                default: ""
+                                type: string
+                            required:
+                            - key
+                            type: object
+                            x-kubernetes-map-type: atomic
+                        required:
+                        - customerKeySecretKeyRef
+                        type: object
                       tls:
                         description: TLS provides the configuration required to establish
                           TLS connections with S3.
@@ -13557,6 +13626,29 @@ spec:
                     - key
                     type: object
                     x-kubernetes-map-type: atomic
+                  ssec:
+                    description: |-
+                      SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.
+                      The secret must contain a 32-byte key (256 bits) in the specified key.
+                      This enables server-side encryption where you provide and manage the encryption key.
+                    properties:
+                      customerKeySecretKeyRef:
+                        description: |-
+                          CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.
+                          The key must be a 32-byte (256-bit) key encoded in base64.
+                        properties:
+                          key:
+                            type: string
+                          name:
+                            default: ""
+                            type: string
+                        required:
+                        - key
+                        type: object
+                        x-kubernetes-map-type: atomic
+                    required:
+                    - customerKeySecretKeyRef
+                    type: object
                   tls:
                     description: TLS provides the configuration required to establish
                       TLS connections with S3.
diff --git a/docs/api_reference.md b/docs/api_reference.md
index 59d6b756..f5b17be5 100644
--- a/docs/api_reference.md
+++ b/docs/api_reference.md
@@ -2499,6 +2499,7 @@ _Appears in:_
 | `secretAccessKeySecretKeyRef` _[SecretKeySelector](#secretkeyselector)_ | AccessKeyIdSecretKeyRef is a reference to a Secret key containing the S3 secret key. |  |  |
 | `sessionTokenSecretKeyRef` _[SecretKeySelector](#secretkeyselector)_ | SessionTokenSecretKeyRef is a reference to a Secret key containing the S3 session token. |  |  |
 | `tls` _[TLSS3](#tlss3)_ | TLS provides the configuration required to establish TLS connections with S3. |  |  |
+| `ssec` _[SSECConfig](#ssecconfig)_ | SSEC is a reference to a Secret containing the SSE-C (Server-Side Encryption with Customer-Provided Keys) key.<br />The secret must contain a 32-byte key (256 bits) in the specified key.<br />This enables server-side encryption where you provide and manage the encryption key. |  |  |
 
 
 #### SQLTemplate
@@ -2521,6 +2522,22 @@ _Appears in:_
 | `cleanupPolicy` _[CleanupPolicy](#cleanuppolicy)_ | CleanupPolicy defines the behavior for cleaning up a SQL resource. |  | Enum: [Skip Delete] <br /> |
 
 
+#### SSECConfig
+
+
+
+SSECConfig defines the configuration for SSE-C (Server-Side Encryption with Customer-Provided Keys).
+
+
+
+_Appears in:_
+- [S3](#s3)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `customerKeySecretKeyRef` _[SecretKeySelector](#secretkeyselector)_ | CustomerKeySecretKeyRef is a reference to a Secret key containing the SSE-C customer-provided encryption key.<br />The key must be a 32-byte (256-bit) key encoded in base64. |  | Required: \{\} <br /> |
+
+
 #### SST
 
 _Underlying type:_ _string_
@@ -2575,6 +2592,7 @@ _Appears in:_
 - [MariaDBSpec](#mariadbspec)
 - [PasswordPlugin](#passwordplugin)
 - [S3](#s3)
+- [SSECConfig](#ssecconfig)
 - [SqlJobSpec](#sqljobspec)
 - [TLSS3](#tlss3)
 - [UserSpec](#userspec)
diff --git a/docs/logical_backup.md b/docs/logical_backup.md
index e7d073d6..505333da 100644
--- a/docs/logical_backup.md
+++ b/docs/logical_backup.md
@@ -5,6 +5,7 @@
   - [What is a logical backup?](#what-is-a-logical-backup)
   - [Storage types](#storage-types)
   - [`Backup` CR](#backup-cr)
+  - [Server-Side Encryption with Customer-Provided Keys (SSE-C)](#server-side-encryption-with-customer-provided-keys-sse-c)
   - [`Restore` CR](#restore-cr)
   - [Bootstrap new `MariaDB` instances](#bootstrap-new-mariadb-instances)
   - [Backup and restore specific databases](#backup-and-restore-specific-databases)
@@ -84,6 +85,58 @@ spec:
 ```
 By providing the authentication details and the TLS configuration via references to `Secret` keys, this example will store the backups in a local Minio instance.
 
+#### Server-Side Encryption with Customer-Provided Keys (SSE-C)
+
+You can enable server-side encryption using your own encryption key (SSE-C) by providing a reference to a `Secret` containing a 32-byte (256-bit) key encoded in base64:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ssec-key
+type: Opaque
+stringData:
+  # 32-byte key encoded in base64 (use: openssl rand -base64 32)
+  customer-key: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
+```
+
+```yaml
+apiVersion: k8s.mariadb.com/v1alpha1
+kind: Backup
+metadata:
+  name: backup
+spec:
+  mariaDbRef:
+    name: mariadb
+  storage:
+    s3:
+      bucket: backups
+      prefix: mariadb
+      endpoint: minio.minio.svc.cluster.local:9000
+      region: us-east-1
+      accessKeyIdSecretKeyRef:
+        name: minio
+        key: access-key-id
+      secretAccessKeySecretKeyRef:
+        name: minio
+        key: secret-access-key
+      tls:
+        enabled: true
+        caSecretKeyRef:
+          name: minio-ca
+          key: tls.crt
+      ssec:
+        customerKeySecretKeyRef:
+          name: ssec-key
+          key: customer-key
+```
+
+> [!IMPORTANT]
+> When using SSE-C, you are responsible for managing and securely storing the encryption key. If you lose the key, you will not be able to decrypt your backups. Ensure you have proper key management procedures in place.
+
+> [!NOTE]
+> When restoring from SSE-C encrypted backups, the same key must be provided in the `Restore` CR or `bootstrapFrom` configuration.
+
 Alternatively you can use dynamic credentials from an EKS Service Account using EKS Pod Identity or IRSA:
 
 ```yaml
diff --git a/docs/physical_backup.md b/docs/physical_backup.md
index d7115008..22610638 100644
--- a/docs/physical_backup.md
+++ b/docs/physical_backup.md
@@ -14,6 +14,7 @@
 - [Timeout](#timeout)
 - [Extra options](#extra-options)
 - [S3 credentials](#s3-credentials)
+- [Server-Side Encryption with Customer-Provided Keys (SSE-C)](#server-side-encryption-with-customer-provided-keys-sse-c)
 - [Staging area](#staging-area)
 - [VolumeSnapshots](#volumesnapshots)
 - [Important considerations and limitations](#important-considerations-and-limitations)
@@ -318,6 +319,56 @@ spec:
 
 By leaving out the `accessKeyIdSecretKeyRef` and `secretAccessKeySecretKeyRef` credentials and pointing to the correct `serviceAccountName`, the backup `Job` will use the dynamic credentials from EKS.
 
+### Server-Side Encryption with Customer-Provided Keys (SSE-C)
+
+You can enable server-side encryption using your own encryption key (SSE-C) by providing a reference to a `Secret` containing a 32-byte (256-bit) key encoded in base64:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: ssec-key
+type: Opaque
+stringData:
+  # 32-byte key encoded in base64 (use: openssl rand -base64 32)
+  customer-key: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
+```
+
+```yaml
+apiVersion: k8s.mariadb.com/v1alpha1
+kind: PhysicalBackup
+metadata:
+  name: physicalbackup
+spec:
+  mariaDbRef:
+    name: mariadb
+  storage:
+    s3:
+      bucket: physicalbackups
+      endpoint: minio.minio.svc.cluster.local:9000
+      accessKeyIdSecretKeyRef:
+        name: minio
+        key: access-key-id
+      secretAccessKeySecretKeyRef:
+        name: minio
+        key: secret-access-key
+      tls:
+        enabled: true
+        caSecretKeyRef:
+          name: minio-ca
+          key: ca.crt
+      ssec:
+        customerKeySecretKeyRef:
+          name: ssec-key
+          key: customer-key
+```
+
+> [!IMPORTANT]
+> When using SSE-C, you are responsible for managing and securely storing the encryption key. If you lose the key, you will not be able to decrypt your backups. Ensure you have proper key management procedures in place.
+
+> [!NOTE]
+> When restoring from SSE-C encrypted backups via `bootstrapFrom`, the same key must be provided in the S3 configuration.
+
 ## Staging area
 
 > [!NOTE]  
diff --git a/pkg/backup/storage.go b/pkg/backup/storage.go
index b94df1e5..74b1d8e0 100644
--- a/pkg/backup/storage.go
+++ b/pkg/backup/storage.go
@@ -2,6 +2,7 @@ package backup
 
 import (
 	"context"
+	"encoding/base64"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -10,6 +11,7 @@ import (
 	"github.com/go-logr/logr"
 	mariadbminio "github.com/mariadb-operator/mariadb-operator/v25/pkg/minio"
 	"github.com/minio/minio-go/v7"
+	"github.com/minio/minio-go/v7/pkg/encrypt"
 )
 
 type BackupStorage interface {
@@ -71,10 +73,11 @@ func (f *FileSystemBackupStorage) shouldProcessBackupFile(fileName string, logge
 }
 
 type S3BackupStorageOpts struct {
-	TLS        bool
-	CACertPath string
-	Region     string
-	Prefix     string
+	TLS             bool
+	CACertPath      string
+	Region          string
+	Prefix          string
+	SSECCustomerKey string
 }
 
 type S3BackupStorageOpt func(s *S3BackupStorageOpts)
@@ -103,6 +106,12 @@ func WithPrefix(prefix string) S3BackupStorageOpt {
 	}
 }
 
+func WithSSECCustomerKey(ssecCustomerKey string) S3BackupStorageOpt {
+	return func(s *S3BackupStorageOpts) {
+		s.SSECCustomerKey = ssecCustomerKey
+	}
+}
+
 type S3BackupStorage struct {
 	S3BackupStorageOpts
 	basePath  string
@@ -158,14 +167,26 @@ func (s *S3BackupStorage) List(ctx context.Context) ([]string, error) {
 func (s *S3BackupStorage) Push(ctx context.Context, fileName string) error {
 	s3FilePath := s.prefixedFileName(fileName)
 	filePath := GetFilePath(s.basePath, fileName)
-	_, err := s.client.FPutObject(ctx, s.bucket, s3FilePath, filePath, minio.PutObjectOptions{})
+	putOpts := minio.PutObjectOptions{}
+	if sse, err := s.getSSEC(); err != nil {
+		return fmt.Errorf("error creating SSE-C encryption: %v", err)
+	} else if sse != nil {
+		putOpts.ServerSideEncryption = sse
+	}
+	_, err := s.client.FPutObject(ctx, s.bucket, s3FilePath, filePath, putOpts)
 	return err
 }
 
 func (s *S3BackupStorage) Pull(ctx context.Context, fileName string) error {
 	s3FilePath := s.prefixedFileName(fileName)
 	filePath := GetFilePath(s.basePath, fileName)
-	return s.client.FGetObject(ctx, s.bucket, s3FilePath, filePath, minio.GetObjectOptions{})
+	getOpts := minio.GetObjectOptions{}
+	if sse, err := s.getSSEC(); err != nil {
+		return fmt.Errorf("error creating SSE-C encryption: %v", err)
+	} else if sse != nil {
+		getOpts.ServerSideEncryption = sse
+	}
+	return s.client.FGetObject(ctx, s.bucket, s3FilePath, filePath, getOpts)
 }
 
 func (s *S3BackupStorage) Delete(ctx context.Context, fileName string) error {
@@ -199,3 +220,20 @@ func (s *S3BackupStorage) getPrefix() string {
 	}
 	return s.Prefix
 }
+
+// getSSEC returns the SSE-C encryption object if SSECCustomerKey is configured.
+// The key is expected to be base64 encoded and must be 32 bytes (256 bits) when decoded.
+func (s *S3BackupStorage) getSSEC() (encrypt.ServerSide, error) {
+	if s.SSECCustomerKey == "" {
+		return nil, nil
+	}
+	key, err := base64.StdEncoding.DecodeString(s.SSECCustomerKey)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding SSE-C key from base64: %v", err)
+	}
+	sse, err := encrypt.NewSSEC(key)
+	if err != nil {
+		return nil, fmt.Errorf("error creating SSE-C encryption: %v", err)
+	}
+	return sse, nil
+}
diff --git a/pkg/builder/batch_builder.go b/pkg/builder/batch_builder.go
index 46fd4194..970fc01e 100644
--- a/pkg/builder/batch_builder.go
+++ b/pkg/builder/batch_builder.go
@@ -37,6 +37,7 @@ const (
 	batchS3AccessKeyId     = "AWS_ACCESS_KEY_ID"
 	batchS3SecretAccessKey = "AWS_SECRET_ACCESS_KEY"
 	batchS3SessionTokenKey = "AWS_SESSION_TOKEN"
+	batchS3SSECCustomerKey = "MARIADB_OPERATOR_S3_SSEC_CUSTOMER_KEY"
 )
 
 var (
diff --git a/pkg/builder/batch_container_builder.go b/pkg/builder/batch_container_builder.go
index 86d5617d..e452c93b 100644
--- a/pkg/builder/batch_container_builder.go
+++ b/pkg/builder/batch_container_builder.go
@@ -153,6 +153,14 @@ func jobS3Env(s3 *mariadbv1alpha1.S3) []corev1.EnvVar {
 			},
 		})
 	}
+	if s3.SSEC != nil {
+		env = append(env, corev1.EnvVar{
+			Name: batchS3SSECCustomerKey,
+			ValueFrom: &corev1.EnvVarSource{
+				SecretKeyRef: ptr.To(s3.SSEC.CustomerKeySecretKeyRef.ToKubernetesType()),
+			},
+		})
+	}
 	return env
 }
 



Test patch:
diff --git a/pkg/backup/storage_test.go b/pkg/backup/storage_test.go
index 701459e5..e0977489 100644
--- a/pkg/backup/storage_test.go
+++ b/pkg/backup/storage_test.go
@@ -1,6 +1,9 @@
 package backup
 
-import "testing"
+import (
+	"encoding/base64"
+	"testing"
+)
 
 func TestS3PrefixedFile(t *testing.T) {
 	tests := []struct {
@@ -251,3 +254,98 @@ func TestS3Prefix(t *testing.T) {
 		})
 	}
 }
+
+func TestS3GetSSEC(t *testing.T) {
+	// Valid 32-byte key for AES-256
+	validKey := make([]byte, 32)
+	for i := range validKey {
+		validKey[i] = byte(i)
+	}
+	validKeyBase64 := base64.StdEncoding.EncodeToString(validKey)
+
+	// Invalid key (not 32 bytes)
+	invalidKey := make([]byte, 16)
+	invalidKeyBase64 := base64.StdEncoding.EncodeToString(invalidKey)
+
+	tests := []struct {
+		name          string
+		backupStorage *S3BackupStorage
+		wantNil       bool
+		wantErr       bool
+	}{
+		{
+			name:          "no SSE-C key",
+			backupStorage: &S3BackupStorage{},
+			wantNil:       true,
+			wantErr:       false,
+		},
+		{
+			name: "empty SSE-C key",
+			backupStorage: &S3BackupStorage{
+				S3BackupStorageOpts: S3BackupStorageOpts{
+					SSECCustomerKey: "",
+				},
+			},
+			wantNil: true,
+			wantErr: false,
+		},
+		{
+			name: "valid SSE-C key",
+			backupStorage: &S3BackupStorage{
+				S3BackupStorageOpts: S3BackupStorageOpts{
+					SSECCustomerKey: validKeyBase64,
+				},
+			},
+			wantNil: false,
+			wantErr: false,
+		},
+		{
+			name: "invalid base64",
+			backupStorage: &S3BackupStorage{
+				S3BackupStorageOpts: S3BackupStorageOpts{
+					SSECCustomerKey: "not-valid-base64!!!",
+				},
+			},
+			wantNil: true,
+			wantErr: true,
+		},
+		{
+			name: "invalid key length (not 32 bytes)",
+			backupStorage: &S3BackupStorage{
+				S3BackupStorageOpts: S3BackupStorageOpts{
+					SSECCustomerKey: invalidKeyBase64,
+				},
+			},
+			wantNil: true,
+			wantErr: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			sse, err := tt.backupStorage.getSSEC()
+
+			if tt.wantErr {
+				if err == nil {
+					t.Error("expected error, got nil")
+				}
+				return
+			}
+
+			if err != nil {
+				t.Errorf("unexpected error: %v", err)
+				return
+			}
+
+			if tt.wantNil {
+				if sse != nil {
+					t.Error("expected nil SSE-C, got non-nil")
+				}
+			} else {
+				if sse == nil {
+					t.Error("expected non-nil SSE-C, got nil")
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/builder/batch_container_builder_test.go b/pkg/builder/batch_container_builder_test.go
index 1a6a8578..542190a4 100644
--- a/pkg/builder/batch_container_builder_test.go
+++ b/pkg/builder/batch_container_builder_test.go
@@ -1,6 +1,7 @@
 package builder
 
 import (
+	"slices"
 	"testing"
 
 	mariadbv1alpha1 "github.com/mariadb-operator/mariadb-operator/v25/api/v1alpha1"
@@ -65,3 +66,152 @@ func TestJobContainerSecurityContext(t *testing.T) {
 		t.Error("expected SecurityContext to be nil")
 	}
 }
+
+func TestJobS3Env(t *testing.T) {
+	tests := []struct {
+		name        string
+		s3          *mariadbv1alpha1.S3
+		expectedEnv []string
+	}{
+		{
+			name:        "nil S3",
+			s3:          nil,
+			expectedEnv: nil,
+		},
+		{
+			name: "S3 with access key only",
+			s3: &mariadbv1alpha1.S3{
+				Bucket:   "test-bucket",
+				Endpoint: "s3.amazonaws.com",
+				AccessKeyIdSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "access-key-id",
+				},
+				SecretAccessKeySecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "secret-access-key",
+				},
+			},
+			expectedEnv: []string{batchS3AccessKeyId, batchS3SecretAccessKey},
+		},
+		{
+			name: "S3 with session token",
+			s3: &mariadbv1alpha1.S3{
+				Bucket:   "test-bucket",
+				Endpoint: "s3.amazonaws.com",
+				AccessKeyIdSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "access-key-id",
+				},
+				SecretAccessKeySecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "secret-access-key",
+				},
+				SessionTokenSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "session-token",
+				},
+			},
+			expectedEnv: []string{batchS3AccessKeyId, batchS3SecretAccessKey, batchS3SessionTokenKey},
+		},
+		{
+			name: "S3 with SSE-C",
+			s3: &mariadbv1alpha1.S3{
+				Bucket:   "test-bucket",
+				Endpoint: "s3.amazonaws.com",
+				AccessKeyIdSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "access-key-id",
+				},
+				SecretAccessKeySecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "secret-access-key",
+				},
+				SSEC: &mariadbv1alpha1.SSECConfig{
+					CustomerKeySecretKeyRef: mariadbv1alpha1.SecretKeySelector{
+						LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+							Name: "ssec-key",
+						},
+						Key: "customer-key",
+					},
+				},
+			},
+			expectedEnv: []string{batchS3AccessKeyId, batchS3SecretAccessKey, batchS3SSECCustomerKey},
+		},
+		{
+			name: "S3 with all options",
+			s3: &mariadbv1alpha1.S3{
+				Bucket:   "test-bucket",
+				Endpoint: "s3.amazonaws.com",
+				AccessKeyIdSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "access-key-id",
+				},
+				SecretAccessKeySecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "secret-access-key",
+				},
+				SessionTokenSecretKeyRef: &mariadbv1alpha1.SecretKeySelector{
+					LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+						Name: "s3-credentials",
+					},
+					Key: "session-token",
+				},
+				SSEC: &mariadbv1alpha1.SSECConfig{
+					CustomerKeySecretKeyRef: mariadbv1alpha1.SecretKeySelector{
+						LocalObjectReference: mariadbv1alpha1.LocalObjectReference{
+							Name: "ssec-key",
+						},
+						Key: "customer-key",
+					},
+				},
+			},
+			expectedEnv: []string{batchS3AccessKeyId, batchS3SecretAccessKey, batchS3SessionTokenKey, batchS3SSECCustomerKey},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			env := jobS3Env(tt.s3)
+
+			if tt.expectedEnv == nil {
+				if env != nil {
+					t.Errorf("expected nil env, got: %v", env)
+				}
+				return
+			}
+
+			if len(env) != len(tt.expectedEnv) {
+				t.Errorf("expected %d env vars, got: %d", len(tt.expectedEnv), len(env))
+				return
+			}
+
+			for _, expectedName := range tt.expectedEnv {
+				found := slices.ContainsFunc(env, func(e corev1.EnvVar) bool {
+					return e.Name == expectedName
+				})
+				if !found {
+					t.Errorf("expected env var %s not found in %v", expectedName, env)
+				}
+			}
+		})
+	}
+}