Skip to content
Open
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
6 changes: 6 additions & 0 deletions .wordlist.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
AES
AKS
AccessDenied
AdditionalContainerArgs
Expand All @@ -21,11 +22,13 @@ EnvVar
GCP
GKE
Gi
Hetzner
IAM
IRSA
IfNotPresent
InstanceSidecarConfiguration
JSON
KMS
Kustomize
Lifecycle
Linode
Expand Down Expand Up @@ -95,6 +98,7 @@ creds
csi
customresourcedefinition
declaratively
decrypt
deps
desc
devel
Expand Down Expand Up @@ -131,6 +135,7 @@ namespaces
nonResourceURLs
objectstore
objectstores
openssl
pluginConfiguration
podName
postgres
Expand All @@ -155,6 +160,7 @@ serverName
serviceaccount
sha
sig
sse
storageClass
subcommand
tfddg
Expand Down
2 changes: 2 additions & 0 deletions api/v1/objectstore_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type InstanceSidecarConfiguration struct {
type ObjectStoreSpec struct {
// The configuration for the barman-cloud tool suite
// +kubebuilder:validation:XValidation:rule="!has(self.serverName)",fieldPath=".serverName",reason="FieldValueForbidden",message="use the 'serverName' plugin parameter in the Cluster resource"
// +kubebuilder:validation:XValidation:rule="!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey) || !has(self.data) || !has(self.data.encryption)",fieldPath=".data.encryption",reason="FieldValueForbidden",message="cannot be used together with s3Credentials.sseCustomerKey"
// +kubebuilder:validation:XValidation:rule="!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey) || !has(self.wal) || !has(self.wal.encryption)",fieldPath=".wal.encryption",reason="FieldValueForbidden",message="cannot be used together with s3Credentials.sseCustomerKey"
Configuration barmanapi.BarmanObjectStoreConfiguration `json:"configuration"`

// RetentionPolicy is the retention policy to be used for backups
Expand Down
35 changes: 35 additions & 0 deletions config/crd/bases/barmancloud.cnpg.io_objectstores.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Comment on lines +322 to +324

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Following the same suggestion I left in: https://github.com/cloudnative-pg/barman-cloud/pull/284/changes#r4197823691

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).```

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: |-
Expand Down Expand Up @@ -412,6 +437,16 @@ spec:
message: use the 'serverName' plugin parameter in the Cluster resource
reason: FieldValueForbidden
rule: '!has(self.serverName)'
- fieldPath: .data.encryption
message: cannot be used together with s3Credentials.sseCustomerKey
reason: FieldValueForbidden
rule: '!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey)
|| !has(self.data) || !has(self.data.encryption)'
- fieldPath: .wal.encryption
message: cannot be used together with s3Credentials.sseCustomerKey
reason: FieldValueForbidden
rule: '!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey)
|| !has(self.wal) || !has(self.wal.encryption)'
instanceSidecarConfiguration:
description: The configuration for the sidecar that runs in the instance
pods
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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.20260930125700-96d274df9e0b
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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.1 h1:d1jxp0jQi7/3zrEhsv9ycD+G5ssmkNvDvTmogaGccCw=
github.com/cloudnative-pg/cloudnative-pg v1.30.1/go.mod h1:k931EKEiGsGwHid+Lwo3vt4ZvnmnaIUYS0SJ29Bpivw=
github.com/cloudnative-pg/cnpg-i v0.6.0 h1:LA//DLkFOLIjU0ASOpFkydZhGir9IAIDfgSsTTX9IpU=
Expand Down Expand Up @@ -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.20260930125700-96d274df9e0b h1:a0l65CAtN5JKSyc6qyd6UCsu24mr8r2WcRSXNJO1Fek=
github.com/schaurian/barman-cloud v0.5.2-0.20260930125700-96d274df9e0b/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=
Expand Down
1 change: 1 addition & 0 deletions internal/cnpgi/operator/specs/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions internal/cnpgi/operator/specs/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand Down
35 changes: 35 additions & 0 deletions manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Comment on lines +321 to +323

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Following the same suggestion I left in: https://github.com/cloudnative-pg/barman-cloud/pull/284/changes#r4197823691

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).

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: |-
Expand Down Expand Up @@ -411,6 +436,16 @@ spec:
message: use the 'serverName' plugin parameter in the Cluster resource
reason: FieldValueForbidden
rule: '!has(self.serverName)'
- fieldPath: .data.encryption
message: cannot be used together with s3Credentials.sseCustomerKey
reason: FieldValueForbidden
rule: '!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey)
|| !has(self.data) || !has(self.data.encryption)'
- fieldPath: .wal.encryption
message: cannot be used together with s3Credentials.sseCustomerKey
reason: FieldValueForbidden
rule: '!has(self.s3Credentials) || !has(self.s3Credentials.sseCustomerKey)
|| !has(self.wal) || !has(self.wal.encryption)'
instanceSidecarConfiguration:
description: The configuration for the sidecar that runs in the instance
pods
Expand Down
7 changes: 7 additions & 0 deletions test/e2e/internal/objectstore/objectstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type Resources struct {
Service *corev1.Service
Secret *corev1.Secret
PVC *corev1.PersistentVolumeClaim
// SSECustomerKey is the secret holding the SSE-C key of the object store, if any
SSECustomerKey *corev1.Secret
}

// Create creates the object store resources.
Expand All @@ -53,6 +55,11 @@ func (osr Resources) Create(ctx context.Context, cl client.Client) error {
return fmt.Errorf("failed to create secret: %w", err)
}
}
if osr.SSECustomerKey != nil {
if err := cl.Create(ctx, osr.SSECustomerKey); err != nil {
return fmt.Errorf("failed to create SSE-C key secret: %w", err)
}
}
if osr.Deployment != nil {
if err := cl.Create(ctx, osr.Deployment); err != nil {
return fmt.Errorf("failed to create deployment: %w", err)
Expand Down
39 changes: 37 additions & 2 deletions test/e2e/internal/objectstore/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ SPDX-License-Identifier: Apache-2.0
package objectstore

import (
"crypto/rand"
"encoding/base64"
"fmt"
"net"

Expand Down Expand Up @@ -243,8 +245,8 @@ func newS3Secret(namespace, name string) *corev1.Secret {
Namespace: namespace,
},
Data: map[string][]byte{
"ACCESS_KEY_ID": []byte("s3accesskey"),
"ACCESS_SECRET_KEY": []byte("s3secretkey123"),
"ACCESS_KEY_ID": []byte(S3AccessKeyID),
"ACCESS_SECRET_KEY": []byte(S3SecretAccessKey),
},
}
}
Expand Down Expand Up @@ -272,6 +274,39 @@ func newS3PVC(namespace, name string) *corev1.PersistentVolumeClaim {
}
}

// S3AccessKeyID and S3SecretAccessKey are the credentials of the S3-compatible
// object store created by NewS3ObjectStoreResources.
const (
S3AccessKeyID = "s3accesskey"
S3SecretAccessKey = "s3secretkey123"
)

// SSECustomerKeySecretKey is the key of the SSE-C key in the secret created by
// NewSSECustomerKeySecret.
const SSECustomerKeySecretKey = "key"

// NewSSECustomerKeySecret creates a secret holding a random base64-encoded
// 256-bit SSE-C key, and returns it together with the encoded key.
func NewSSECustomerKeySecret(namespace, name string) (*corev1.Secret, string) {
raw := make([]byte, 32)
_, _ = rand.Read(raw)
key := base64.StdEncoding.EncodeToString(raw)

return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Data: map[string][]byte{
SSECustomerKeySecretKey: []byte(key),
},
}, key
}

// NewS3ObjectStore creates a new ObjectStore pointing at the S3-compatible
// object store created by NewS3ObjectStoreResources with the given name.
func NewS3ObjectStore(namespace, name, s3Name string) *pluginBarmanCloudV1.ObjectStore {
Expand Down
34 changes: 34 additions & 0 deletions test/e2e/internal/tests/replicacluster/fixtures.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package replicacluster

import (
cloudnativepgv1 "github.com/cloudnative-pg/api/pkg/api/v1"
machineryapi "github.com/cloudnative-pg/machinery/pkg/api"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
Expand Down Expand Up @@ -58,6 +59,9 @@ type replicaClusterTestResources struct {
ReplicaObjectStore *pluginBarmanCloudV1.ObjectStore
ReplicaCluster *cloudnativepgv1.Cluster
ReplicaBackup *cloudnativepgv1.Backup
// SSECustomerKeys maps the S3 endpoint of each object store to the
// base64-encoded SSE-C key its objects must be encrypted with
SSECustomerKeys map[string]string
}

type s3ReplicaClusterFactory struct{}
Expand All @@ -77,6 +81,36 @@ func (f s3ReplicaClusterFactory) createReplicaClusterTestResources(namespace str
return result
}

// s3SSECReplicaClusterFactory encrypts each object store with its own SSE-C
// key: the designated primary of the replica cluster archives WAL to one
// object store while restoring it from the other, in the same sidecar.
type s3SSECReplicaClusterFactory struct{}

func (f s3SSECReplicaClusterFactory) createReplicaClusterTestResources(
namespace string,
) replicaClusterTestResources {
result := s3ReplicaClusterFactory{}.createReplicaClusterTestResources(namespace)
result.SSECustomerKeys = map[string]string{}

for _, store := range []struct {
resources *objectstore.Resources
objectStore *pluginBarmanCloudV1.ObjectStore
}{
{result.SrcObjectStoreResources, result.SrcObjectStore},
{result.ReplicaObjectStoreResources, result.ReplicaObjectStore},
} {
secret, key := objectstore.NewSSECustomerKeySecret(namespace, store.objectStore.Name+"-sse-c")
store.resources.SSECustomerKey = secret
store.objectStore.Spec.Configuration.AWS.SSECustomerKey = &machineryapi.SecretKeySelector{
LocalObjectReference: machineryapi.LocalObjectReference{Name: secret.Name},
Key: objectstore.SSECustomerKeySecretKey,
}
result.SSECustomerKeys[store.objectStore.Spec.Configuration.EndpointURL] = key
}

return result
}

type gcsReplicaClusterFactory struct{}

func (f gcsReplicaClusterFactory) createReplicaClusterTestResources(namespace string) replicaClusterTestResources {
Expand Down
Loading
Loading