diff --git a/.wordlist.txt b/.wordlist.txt index 3df20daf..54196d98 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -1,3 +1,4 @@ +AES AKS AccessDenied AdditionalContainerArgs @@ -21,11 +22,13 @@ EnvVar GCP GKE Gi +Hetzner IAM IRSA IfNotPresent InstanceSidecarConfiguration JSON +KMS Kustomize Lifecycle Linode @@ -94,6 +97,7 @@ creds csi customresourcedefinition declaratively +decrypt deps desc devel @@ -130,6 +134,7 @@ namespaces nonResourceURLs objectstore objectstores +openssl pluginConfiguration podName postgres @@ -154,6 +159,7 @@ serverName serviceaccount sha sig +sse storageClass subcommand tfddg diff --git a/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml b/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml index bb7db1e6..9e09b452 100644 --- a/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml +++ b/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml @@ -315,6 +315,31 @@ spec: - key - name type: object + sseCustomerKey: + description: |- + The reference to the secret containing the key for + Server-Side Encryption with Customer-provided keys (SSE-C). + When set, every object barman-cloud uploads to and downloads from + S3 is encrypted with this key using the AWS SSE-C protocol + (the `--sse-customer-key` barman-cloud option). + The referenced value must be a base64-encoded 256-bit (32-byte) + AES key. This is meant for S3-compatible providers that only + support customer-provided keys (e.g. Hetzner Object Storage) and + cannot be combined with the bucket-managed `encryption` field + (SSE-S3/SSE-KMS), which barman-cloud rejects together with SSE-C. + It can be combined with any authentication method, including + inheritFromIAMRole. + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object type: object serverName: description: |- diff --git a/go.mod b/go.mod index 84565bc2..dcd466df 100644 --- a/go.mod +++ b/go.mod @@ -136,3 +136,5 @@ require ( sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) + +replace github.com/cloudnative-pg/barman-cloud => github.com/schaurian/barman-cloud v0.5.2-0.20260908142745-14f89b687e74 diff --git a/go.sum b/go.sum index 4607e0c5..e1e1e409 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudnative-pg/api v1.30.0 h1:L8hnvV/tPEQA1xYEi41FUBFA7FUNVGju8+SlgFlDDjI= github.com/cloudnative-pg/api v1.30.0/go.mod h1:XrKBbOWObL33si0FNuwX4uHNf5JShiZyOUqd6LxbJQo= -github.com/cloudnative-pg/barman-cloud v0.6.0 h1:OtBFmCDyVUAcgFa++FIoCCJwPfd5TtqK3PH6DGPcpkA= -github.com/cloudnative-pg/barman-cloud v0.6.0/go.mod h1:eqSPRGz/s8M0Mea8mkqiVQUTkoSquAhvJF49feh+Ks4= github.com/cloudnative-pg/cloudnative-pg v1.30.0 h1:fnhVq44xXx97MNiuvJsPrX1vSjYbgdyBK5MSGfdHdp0= github.com/cloudnative-pg/cloudnative-pg v1.30.0/go.mod h1:QkolwBOWZ+GvAiJt6KpDSymwkpf0K19/p4Q6MQlTM8U= github.com/cloudnative-pg/cnpg-i v0.6.0 h1:LA//DLkFOLIjU0ASOpFkydZhGir9IAIDfgSsTTX9IpU= @@ -187,6 +185,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= +github.com/schaurian/barman-cloud v0.5.2-0.20260908142745-14f89b687e74 h1:H8jjBODttfJJyMLtv0dwvsMRaJr+IvdikaFS4EiDLj4= +github.com/schaurian/barman-cloud v0.5.2-0.20260908142745-14f89b687e74/go.mod h1:eqSPRGz/s8M0Mea8mkqiVQUTkoSquAhvJF49feh+Ks4= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/snorwin/jsonpatch v1.5.0 h1:0m56YSt9cHiJOn8U+OcqdPGcDQZmhPM/zsG7Dv5QQP0= diff --git a/internal/cnpgi/operator/specs/secrets.go b/internal/cnpgi/operator/specs/secrets.go index 89811ad2..32b745f4 100644 --- a/internal/cnpgi/operator/specs/secrets.go +++ b/internal/cnpgi/operator/specs/secrets.go @@ -34,6 +34,7 @@ func CollectSecretNamesFromCredentials(barmanCredentials *barmanapi.BarmanCreden barmanCredentials.AWS.SecretAccessKeyReference, barmanCredentials.AWS.RegionReference, barmanCredentials.AWS.SessionToken, + barmanCredentials.AWS.SSECustomerKey, ) } if barmanCredentials.Azure != nil { diff --git a/internal/cnpgi/operator/specs/secrets_test.go b/internal/cnpgi/operator/specs/secrets_test.go index d6fa706b..d674eb5b 100644 --- a/internal/cnpgi/operator/specs/secrets_test.go +++ b/internal/cnpgi/operator/specs/secrets_test.go @@ -51,6 +51,23 @@ var _ = Describe("CollectSecretNamesFromCredentials", func() { Expect(secrets).To(ContainElement("aws-secret")) }) + It("should include the SSE-C customer key secret", func() { + credentials := &barmanapi.BarmanCredentials{ + AWS: &barmanapi.S3Credentials{ + InheritFromIAMRole: true, + SSECustomerKey: &machineryapi.SecretKeySelector{ + LocalObjectReference: machineryapi.LocalObjectReference{ + Name: "sse-c-key", + }, + Key: "key", + }, + }, + } + + secrets := CollectSecretNamesFromCredentials(credentials) + Expect(secrets).To(ConsistOf("sse-c-key")) + }) + It("should handle nil AWS credentials", func() { credentials := &barmanapi.BarmanCredentials{} diff --git a/manifest.yaml b/manifest.yaml index 118beaf4..226e5043 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -314,6 +314,31 @@ spec: - key - name type: object + sseCustomerKey: + description: |- + The reference to the secret containing the key for + Server-Side Encryption with Customer-provided keys (SSE-C). + When set, every object barman-cloud uploads to and downloads from + S3 is encrypted with this key using the AWS SSE-C protocol + (the `--sse-customer-key` barman-cloud option). + The referenced value must be a base64-encoded 256-bit (32-byte) + AES key. This is meant for S3-compatible providers that only + support customer-provided keys (e.g. Hetzner Object Storage) and + cannot be combined with the bucket-managed `encryption` field + (SSE-S3/SSE-KMS), which barman-cloud rejects together with SSE-C. + It can be combined with any authentication method, including + inheritFromIAMRole. + properties: + key: + description: The key to select + type: string + name: + description: Name of the referent. + type: string + required: + - key + - name + type: object type: object serverName: description: |- diff --git a/web/docs/object_stores.md b/web/docs/object_stores.md index 11b1ff8c..c3fc9682 100644 --- a/web/docs/object_stores.md +++ b/web/docs/object_stores.md @@ -194,6 +194,72 @@ spec: [...] ``` +### Server-Side Encryption with Customer Keys (SSE-C) + +Some S3-compatible providers — most notably **Hetzner Object Storage** — do +not offer bucket-managed server-side encryption (SSE-S3 / SSE-KMS) and instead +only support **Server-Side Encryption with Customer-provided keys (SSE-C)**. +With SSE-C the encryption key never leaves your control: it is supplied with +every request, and the provider uses it to encrypt and decrypt objects without +storing it. + +To enable SSE-C, set the `sseCustomerKey` field in the `s3Credentials` block to +a secret reference holding a **base64-encoded 256-bit (32-byte) AES key**. + +Generate the key and store it in a Kubernetes secret: + +```sh +# Generate a random 256-bit key, base64-encoded +openssl rand 32 | base64 > encryption.key + +kubectl create secret generic aws-sse-c \ + --from-file=key=encryption.key +``` + +:::warning +Keep this key safe and backed up **outside** the object store. If you lose +it, your backups and WAL files become permanently unrecoverable — the +provider cannot decrypt them for you. +::: + +Reference it in your `ObjectStore` definition: + +```yaml +apiVersion: barmancloud.cnpg.io/v1 +kind: ObjectStore +metadata: + name: hetzner-store +spec: + configuration: + destinationPath: "s3://BUCKET_NAME/path/to/folder" + endpointURL: "https://fsn1.your-objectstorage.com" + s3Credentials: + accessKeyId: + name: aws-creds + key: ACCESS_KEY_ID + secretAccessKey: + name: aws-creds + key: ACCESS_SECRET_KEY + sseCustomerKey: + name: aws-sse-c + key: key + [...] +``` + +The same key is applied to **every** operation — base backups, WAL archiving, +WAL restore, and data restore — so it must remain unchanged and available for +the whole lifetime of the backups it protects. `sseCustomerKey` can be combined +with any authentication method, including `inheritFromIAMRole`, but not with +the bucket-managed `encryption` setting (SSE-S3 / SSE-KMS) of the `data` and +`wal` sections: `barman-cloud` rejects `--sse-customer-key` together with +`--encryption`, so an object store that sets both fails at the first backup or +WAL archive. + +:::note +SSE-C relies on the `--sse-customer-key` option introduced in Barman 3.20.0, +which the plugin sidecar image ships starting from version 0.15.0. +::: + ### Using Object Storage with a Private CA For object storage services (e.g., MinIO) that use HTTPS with certificates