Skip to content

feat: add support for S3 SSE-C (customer-provided encryption keys) - #284

Open
schaurian wants to merge 6 commits into
cloudnative-pg:mainfrom
schaurian:feat/s3-sse-c
Open

schaurian wants to merge 6 commits into
cloudnative-pg:mainfrom
schaurian:feat/s3-sse-c

Conversation

@schaurian

@schaurian schaurian commented Jul 19, 2026 •

Copy link
Copy Markdown

Summary

Adds support for S3 Server-Side Encryption with Customer-provided keys (SSE-C) to the barman-cloud library.

A new optional sseCustomerKey field on S3Credentials references a secret holding a base64-encoded 256-bit AES key. When set, the key is materialized to a file and passed to every barman-cloud command via the --sse-customer-key file:// option.

This is the library-side change needed to close cloudnative-pg/plugin-barman-cloud#646 — S3-compatible providers such as Hetzner Object Storage only support SSE-C for encryption at rest, so the existing bucket-managed encryption field (SSE-S3 / SSE-KMS) cannot be used with them.

What changed

File Change
pkg/api/config.go New SSECustomerKey *SecretKeySelector field on S3Credentials (+ generated deepcopy)
pkg/utils/constants.go SSECustomerKeyFileLocation — the on-disk path for the materialized key
pkg/credentials/env.go Materialize/remove the key file from the referenced secret (same pattern as the Google credentials file)
pkg/command/commandbuilder.go Inject --sse-customer-key file://… in the shared appendCloudProviderOptions
pkg/command/commandbuilder_test.go Unit tests for the AWS SSE-C option
pkg/api/webhooks/config.go Reject sseCustomerKey combined with data.encryption / wal.encryption (by @andrein, with tests)

Design notes

  • Single injection point. The flag is added in appendCloudProviderOptions, through which every command builder already routes (barman-cloud-backup, -wal-archive, -wal-restore, -restore, -backup-list, -backup-delete, -check-wal-archive). SSE-C requires the key on every read and write, so covering the shared chokepoint avoids the classic "backups succeed but restores fail because a read path was missed" footgun.
  • Key materialization mirrors the existing Google credentials handling (reconcileGoogleCredentials → a /controller/... file written atomically with 0600), so no new volume-mount plumbing is needed in consumers; the same code path serves both the operator and the plugin sidecar.
  • Mutually exclusive with encryption. barman-cloud rejects --sse-customer-key together with --encryption at run time, so ValidateBackupConfiguration now reports one error per offending field (data.encryption, wal.encryption) when sseCustomerKey is set, instead of letting the first backup fail.
  • Auth-method independent. The key is materialized before the auth branching in envSetAWSCredentials, so it works with explicit keys, session tokens, and inheritFromIAMRole.

Dependency / rollout

Runtime use requires Barman ≥ 3.20.0, which ships the --sse-customer-key option (EnterpriseDB/barman#973, released 2026-08-27). The plugin-barman-cloud sidecar pins Barman 3.20.0 since v0.15.0. The plugin side of this feature is cloudnative-pg/plugin-barman-cloud#1017.

Testing

  • Full task ci run locally (commitlint, spellcheck, golangci-lint v2.13.2, go test ./..., uncommitted-drift check): all green.
  • Field-tested by @andrein against a Ceph RGW store that supports only SSE-C, with CloudNativePG 1.30.0 and plugin-barman-cloud#1017 (WAL archiving, base backup, backup-list, retention via backup-delete, and recovery into a new Cluster all working with Encryption: SSE-C objects) — see the comments below.

🤖 Generated with Claude Code

@schaurian
schaurian requested a review from a team July 19, 2026 02:35
@schaurian
schaurian requested a review from a team as a code owner August 9, 2026 13:26
schaurian added a commit to schaurian/plugin-barman-cloud that referenced this pull request Aug 9, 2026
Surface the new `sseCustomerKey` field on `s3Credentials` through the
ObjectStore CRD so users can enable Server-Side Encryption with
Customer-provided keys (SSE-C). This is required by S3-compatible
providers that only support SSE-C for encryption at rest, such as
Hetzner Object Storage.

The field flows through the embedded BarmanObjectStoreConfiguration
from the barman-cloud library, so this change is limited to bumping the
dependency, regenerating the CRD and the consolidated manifest, and
documenting usage in the object stores guide.

Depends on cloudnative-pg/barman-cloud#284 (temporarily pinned via a
replace directive until that change is released).

Closes cloudnative-pg#646

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Florian Schauer <florian@schauer.to>
@fkammer

fkammer commented Sep 2, 2026

Copy link
Copy Markdown

FYI: barman release is out: https://github.com/EnterpriseDB/barman/releases/tag/release%2F3.20.0

@andrein

andrein commented Sep 8, 2026

Copy link
Copy Markdown

Tested at a300841 against a Ceph RGW object store that supports only SSE-C
(no SSE-S3 or SSE-KMS), with CloudNativePG 1.30.0 and the Barman Cloud plugin
from cloudnative-pg/plugin-barman-cloud#1017 with main merged in for
Barman 3.20.0.

  • WAL archiving continuous; objects under wals/ report Encryption: SSE-C
    and return 400 when read without the key
  • base backup completed; Backup.status populated by barman-cloud-backup-list
    reading the SSE-C backup.info
  • ObjectStore.status.serverRecoveryWindow populated
  • retention policy applied on the primary via barman-cloud-backup-delete
  • recovery into a new Cluster through externalClusters[].plugin reproduced
    the source database: same row counts, table count and size

--sse-customer-key together with --encryption is rejected by Barman at run
time; schaurian#1 against this branch adds the corresponding check
to ValidateBackupConfiguration.

schaurian added a commit to schaurian/plugin-barman-cloud that referenced this pull request Sep 8, 2026
Surface the new `sseCustomerKey` field on `s3Credentials` through the
ObjectStore CRD so users can enable Server-Side Encryption with
Customer-provided keys (SSE-C). This is required by S3-compatible
providers that only support SSE-C for encryption at rest, such as
Hetzner Object Storage.

The field flows through the embedded BarmanObjectStoreConfiguration
from the barman-cloud library, so this change is limited to bumping the
dependency, regenerating the CRD and the consolidated manifest, and
documenting usage in the object stores guide.

Depends on cloudnative-pg/barman-cloud#284 (temporarily pinned via a
replace directive until that change is released).

Closes cloudnative-pg#646

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Florian Schauer <florian@schauer.to>
@schaurian

Copy link
Copy Markdown
Author

Thanks for the test run and the validation change, @andrein — merged schaurian#1 into this branch.

Updated the branch (14f89b6):

  • Rebased onto main (v0.6.0); the SSE-C commit and the validation commit are now separate commits on top of it.
  • Reworded the body of the validation commit to start with a capital letter: this repo's commitlint enforces body-case: sentence-case, and that was the one task ci failure on the previous head. Author and sign-off are unchanged.
  • The SSECustomerKey API comment used to say the field is "orthogonal" to encryption; it now states that the two cannot be combined, matching the validation. Description updated accordingly.

Full task ci (commitlint, spellcheck, golangci-lint v2.13.2, go test ./..., uncommitted-drift check) passes locally on this head.

For the record, Barman 3.20.0 with --sse-customer-key is released, and cloudnative-pg/plugin-barman-cloud#1017 is rebased on a main whose sidecar pins it.

@neurodrone

Copy link
Copy Markdown

👋 Is there a timeline on merging this? Looks like the use-cases backed by strong security+encryption requirements (and are not necessarily pointed at AWS) won't be able to use CNPG's backups until this merges.

cc @armru @mnencia

armru added a commit to schaurian/plugin-barman-cloud that referenced this pull request Sep 30, 2026
Point the temporary barman-cloud replace at the head of
cloudnative-pg/barman-cloud#284 that keeps each SSE-C key in its own
file. A replica cluster archives WAL to its own object store while
restoring it from the source one in the same sidecar, and with a single
shared file each operation could run with the key of the other object
store.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
armru added a commit to schaurian/plugin-barman-cloud that referenced this pull request Sep 30, 2026
Also points the temporary barman-cloud replace at the head of cloudnative-pg/barman-cloud#284 that serializes the writes of the SSE-C key files.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
armru added a commit to schaurian/plugin-barman-cloud that referenced this pull request Sep 30, 2026
Point the temporary barman-cloud replace at the head of
cloudnative-pg/barman-cloud#284 that stops passing --sse-customer-key to
barman-cloud-check-wal-archive. That command rejects the option, and it
runs before the first WAL file is archived, so an object store with an
SSE-C key could never start archiving. The SSE-C e2e test caught it.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
@armru

armru commented Sep 30, 2026

Copy link
Copy Markdown
Member

I pushed a few commits on top.

  • barman-cloud-check-wal-archive is the only barman-cloud command without --sse-customer-key, and it rejects the option. It runs before the first WAL file is archived, so an object store with an SSE-C key could never start archiving. The command only lists objects and does not need the key, so it no longer receives it.
  • The key was materialized in a single /controller/.sse-customer-key file shared by every object store served by the process. A replica cluster archives WAL to its own object store while restoring it from the source one in the same sidecar, so each operation could remove the key of the other, or have barman-cloud upload objects encrypted with the key of the other object store. Each key now lives in /controller/.sse-customer-keys/<secret>/<key>, and the command builder points at the same path.
  • The writes of these files are serialized: WriteFileAtomic names its temporary file after the current second, so two concurrent first writes of the same key could collide.

The same shared-file problem exists for the Google credentials, fixed separately in #309.

@armru
armru force-pushed the feat/s3-sse-c branch 2 times, most recently from 7b0cad0 to 9ee8eac Compare October 1, 2026 08:40
schaurian and others added 6 commits October 1, 2026 12:23
Add an `sseCustomerKey` field to the S3 credentials that references a
secret holding a base64-encoded 256-bit AES key. When set, the key is
materialized to a file and passed to every barman-cloud command through
the `--sse-customer-key file://` option, enabling Server-Side Encryption
with Customer-provided keys (SSE-C).

This is required by S3-compatible providers that only support SSE-C for
encryption at rest (e.g. Hetzner Object Storage). The option is injected
in the shared `appendCloudProviderOptions` chokepoint, so it applies to
all barman-cloud-* commands (backup, wal-archive, wal-restore, restore,
backup-list, backup-delete, check-wal-archive), and is orthogonal to the
existing bucket-managed `encryption` (SSE-S3/SSE-KMS) option. The key is
materialized before the auth-method branching so it works with every
authentication method, including inheritFromIAMRole.

Requires a barman release that ships the `--sse-customer-key` option
(EnterpriseDB/barman#973).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Florian Schauer <florian@schauer.to>
The barman-cloud commands refuse --sse-customer-key together with
--encryption, so a configuration setting both fails on the first backup.
Reject it at validation time instead, on the data and wal encryption
fields.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Andrei Nistor <andrei@nistor.tech>
A single process can run barman-cloud commands for different object
stores at the same time. A replica cluster, for instance, archives WAL to
its own object store while restoring it from the source one. With one
shared key file, each command overwrote or removed the key of the other:
a missing file made the command fail, and a different key made
barman-cloud upload objects encrypted with the key of another object
store, which the configured key can no longer decrypt.

Materialize each key in a file whose path depends on the referenced
secret and key, and point the barman-cloud commands at that same path.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
The object stores sharing a key secret share its file, and a replica
cluster can archive and restore WAL at the same time. WriteFileAtomic
names its temporary file after the current second, so two concurrent
writes of the same key could use the same temporary file: one of them
could then fail its rename, or truncate the key file the other had just
moved into place, making barman-cloud read an invalid key.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
The barman-cloud-check-wal-archive command is the only one without
the --sse-customer-key option, and it rejects it. Since the check runs
before the first WAL file is archived, an object store with an SSE-C key
could never start archiving. The command only lists the objects in the
bucket, so it does not need the key.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
The declaration of the mutex had split it from the function, so it documented the mutex instead.

Signed-off-by: Armando Ruocco <armando.ruocco@enterprisedb.com>
@neurodrone

Copy link
Copy Markdown

Great to see the progress here! Do we have an ETA on when this might go in? cc @armru @mnencia.

Comment thread pkg/api/config.go
Comment on lines +114 to +116
// 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).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this reads better, right? The previous statement was kind of saying that objects are encrypted during download.

Suggested change
// 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).
// When set, it is the key barman-cloud uses to encrypt objects uploaded
// to S3 and to decrypt them when they are read back, following the AWS
// SSE-C protocol (the `--sse-customer-key` barman-cloud option).

@gustabowill

Copy link
Copy Markdown

@schaurian @armru I also tested this PR locally and everything went fine. I would just like to ask if you guys see any security concern in not deleting the secret key files. As I understand, if sseCustomerKey is removed, points to another Secret, or the Secret is deleted, the old file remains there forever.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Support for S3 SSE-C - Server-Side Encryption with Customer-provided keys

6 participants