Skip to content

Commit fcbc472

Browse files
leonardocearmrumnencia
authored
feat: support custom CA certificates (#198)
This patch enables the use of custom CA certificates when connecting to the object store in the barman-cloud plugin. The certificates are injected into the sidecar via a projected volume and used by the barman-cloud tool suite. If the barman object name or the key name changes, users must trigger a Pod rollout to apply the new values. Signed-off-by: Leonardo Cecchi <[email protected]> Signed-off-by: Armando Ruocco <[email protected]> Signed-off-by: Marco Nenciarini <[email protected]> Co-authored-by: Armando Ruocco <[email protected]> Co-authored-by: Marco Nenciarini <[email protected]>
1 parent 0872cf2 commit fcbc472

File tree

12 files changed

+265
-89
lines changed

12 files changed

+265
-89
lines changed

docs/examples/minio-store.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ metadata:
44
name: minio-store
55
spec:
66
configuration:
7+
endpointCA:
8+
name: minio-server-tls
9+
key: tls.crt
710
destinationPath: s3://backups/
8-
endpointURL: http://minio:9000
11+
endpointURL: https://minio:9000
912
s3Credentials:
1013
accessKeyId:
1114
name: minio

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ toolchain go1.24.1
77
require (
88
github.com/cert-manager/cert-manager v1.17.1
99
github.com/cloudnative-pg/api v1.25.1
10-
github.com/cloudnative-pg/barman-cloud v0.1.0
10+
github.com/cloudnative-pg/barman-cloud v0.2.0
1111
github.com/cloudnative-pg/cloudnative-pg v1.25.1
1212
github.com/cloudnative-pg/cnpg-i v0.1.0
1313
github.com/cloudnative-pg/cnpg-i-machinery v0.1.2
1414
github.com/cloudnative-pg/machinery v0.1.0
15-
github.com/onsi/ginkgo/v2 v2.22.2
15+
github.com/onsi/ginkgo/v2 v2.23.0
1616
github.com/onsi/gomega v1.36.2
1717
github.com/spf13/cobra v1.9.1
1818
github.com/spf13/viper v1.19.0
@@ -118,7 +118,7 @@ require (
118118
golang.org/x/term v0.29.0 // indirect
119119
golang.org/x/text v0.22.0 // indirect
120120
golang.org/x/time v0.9.0 // indirect
121-
golang.org/x/tools v0.28.0 // indirect
121+
golang.org/x/tools v0.30.0 // indirect
122122
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
123123
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
124124
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect

go.sum

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
2020
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
2121
github.com/cloudnative-pg/api v1.25.1 h1:uNjKiB0MIspUeH9l651SnFDcuflr1crB3t6LjxUCafQ=
2222
github.com/cloudnative-pg/api v1.25.1/go.mod h1:fwF5g4XkuNZqYXIeRR3AJvUfWlqWig+r2DXc5bEmw6U=
23-
github.com/cloudnative-pg/barman-cloud v0.1.0 h1:e/z52CehMBIh1LjZqNBJnncWJbS+1JYvRMBR8Js6Uiw=
24-
github.com/cloudnative-pg/barman-cloud v0.1.0/go.mod h1:rJUJO/f1yNckLZiVxHAyRmKY+4EPJkYRJsGbTZRJQSY=
23+
github.com/cloudnative-pg/barman-cloud v0.2.0 h1:KMwJPKjytDqljNNOounBGojsGTXiowztH0WQrVB8/DQ=
24+
github.com/cloudnative-pg/barman-cloud v0.2.0/go.mod h1:kNIUU+fpnYjkr25YwHnteROLhbs6rqpjDiB8XW1+sug=
2525
github.com/cloudnative-pg/cloudnative-pg v1.25.1 h1:Yc6T7ikQ1AiWXBQht+6C3DoihrIpUN2OkM1dIwqadTo=
2626
github.com/cloudnative-pg/cloudnative-pg v1.25.1/go.mod h1:96b9bRFLSr3uFWHjhytPdcvKIKwy9H6AG7cH0O6jefs=
2727
github.com/cloudnative-pg/cnpg-i v0.1.0 h1:QH2xTsrODMhEEc6B25GbOYe7ZIttDmSkYvXotfU5dfs=
@@ -137,8 +137,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
137137
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
138138
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
139139
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
140-
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
141-
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
140+
github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ=
141+
github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM=
142142
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
143143
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
144144
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
@@ -271,8 +271,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
271271
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
272272
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
273273
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
274-
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
275-
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
274+
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
275+
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
276276
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
277277
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
278278
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/cnpgi/common/common.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package common
22

33
import (
44
"fmt"
5+
"path"
56
"strings"
67

78
barmanapi "github.com/cloudnative-pg/barman-cloud/pkg/api"
9+
10+
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
811
)
912

1013
// TODO: refactor.
@@ -70,3 +73,8 @@ func MergeEnv(env []string, incomingEnv []string) []string {
7073

7174
return result
7275
}
76+
77+
// BuildCertificateFilePath builds the path to the barman objectStore certificate
78+
func BuildCertificateFilePath(objectStoreName string) string {
79+
return path.Join(metadata.BarmanCertificatesPath, objectStoreName, metadata.BarmanCertificatesFileName)
80+
}

internal/cnpgi/common/wal.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,12 +77,14 @@ func (w WALServiceImplementation) Archive(
7777
return nil, err
7878
}
7979

80-
envArchive, err := barmanCredentials.EnvSetBackupCloudCredentials(
80+
envArchive, err := barmanCredentials.EnvSetCloudCredentialsAndCertificates(
8181
ctx,
8282
w.Client,
8383
objectStore.Namespace,
8484
&objectStore.Spec.Configuration,
85-
os.Environ())
85+
os.Environ(),
86+
BuildCertificateFilePath(objectStore.Name),
87+
)
8688
if err != nil {
8789
if apierrors.IsForbidden(err) {
8890
return nil, errors.New("backup credentials don't yet have access permissions. Will retry reconciliation loop")
@@ -191,12 +193,13 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
191193
barmanConfiguration := &objectStore.Spec.Configuration
192194

193195
env := GetRestoreCABundleEnv(barmanConfiguration)
194-
credentialsEnv, err := barmanCredentials.EnvSetBackupCloudCredentials(
196+
credentialsEnv, err := barmanCredentials.EnvSetCloudCredentialsAndCertificates(
195197
ctx,
196198
w.Client,
197199
objectStore.Namespace,
198200
&objectStore.Spec.Configuration,
199201
os.Environ(),
202+
BuildCertificateFilePath(objectStore.Name),
200203
)
201204
if err != nil {
202205
return fmt.Errorf("while getting recover credentials: %w", err)

internal/cnpgi/instance/backup.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ func (b BackupServiceImplementation) Backup(
9898
// PGHOST (and the like) to be available
9999
osEnvironment := os.Environ()
100100
caBundleEnvironment := common.GetRestoreCABundleEnv(&objectStore.Spec.Configuration)
101-
env, err := barmanCredentials.EnvSetBackupCloudCredentials(
101+
env, err := barmanCredentials.EnvSetCloudCredentialsAndCertificates(
102102
ctx,
103103
b.Client,
104104
objectStore.Namespace,
105105
&objectStore.Spec.Configuration,
106-
common.MergeEnv(osEnvironment, caBundleEnvironment))
106+
common.MergeEnv(osEnvironment, caBundleEnvironment),
107+
common.BuildCertificateFilePath(objectStore.Name),
108+
)
107109
if err != nil {
108110
contextLogger.Error(err, "while setting backup cloud credentials")
109111
return nil, err

internal/cnpgi/metadata/constants.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ const (
1111
// if present, requires the WAL archiver to check that the backup object
1212
// store is empty.
1313
CheckEmptyWalArchiveFile = ".check-empty-wal-archive"
14+
15+
// BarmanCertificatesPath is the path where the Barman
16+
// certificates will be installed
17+
BarmanCertificatesPath = "/barman-certificates"
18+
19+
// BarmanCertificatesFileName is the path where the Barman
20+
// certificates will be used
21+
BarmanCertificatesFileName = "barman-ca.crt"
1422
)
1523

1624
// Data is the metadata of this plugin.

internal/cnpgi/operator/lifecycle.go

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,9 @@ import (
1414
"github.com/spf13/viper"
1515
batchv1 "k8s.io/api/batch/v1"
1616
corev1 "k8s.io/api/core/v1"
17-
"k8s.io/apimachinery/pkg/types"
1817
"k8s.io/utils/ptr"
1918
"sigs.k8s.io/controller-runtime/pkg/client"
2019

21-
barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1"
2220
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata"
2321
"github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config"
2422
)
@@ -107,56 +105,6 @@ func (impl LifecycleImplementation) LifecycleHook(
107105
}
108106
}
109107

110-
func (impl LifecycleImplementation) collectAdditionalEnvs(
111-
ctx context.Context,
112-
namespace string,
113-
pluginConfiguration *config.PluginConfiguration,
114-
) ([]corev1.EnvVar, error) {
115-
var result []corev1.EnvVar
116-
117-
if len(pluginConfiguration.BarmanObjectName) > 0 {
118-
envs, err := impl.collectObjectStoreEnvs(
119-
ctx,
120-
types.NamespacedName{
121-
Name: pluginConfiguration.BarmanObjectName,
122-
Namespace: namespace,
123-
},
124-
)
125-
if err != nil {
126-
return nil, err
127-
}
128-
result = append(result, envs...)
129-
}
130-
131-
if len(pluginConfiguration.RecoveryBarmanObjectName) > 0 {
132-
envs, err := impl.collectObjectStoreEnvs(
133-
ctx,
134-
types.NamespacedName{
135-
Name: pluginConfiguration.RecoveryBarmanObjectName,
136-
Namespace: namespace,
137-
},
138-
)
139-
if err != nil {
140-
return nil, err
141-
}
142-
result = append(result, envs...)
143-
}
144-
145-
return result, nil
146-
}
147-
148-
func (impl LifecycleImplementation) collectObjectStoreEnvs(
149-
ctx context.Context,
150-
barmanObjectKey types.NamespacedName,
151-
) ([]corev1.EnvVar, error) {
152-
var objectStore barmancloudv1.ObjectStore
153-
if err := impl.Client.Get(ctx, barmanObjectKey, &objectStore); err != nil {
154-
return nil, err
155-
}
156-
157-
return objectStore.Spec.InstanceSidecarConfiguration.Env, nil
158-
}
159-
160108
func (impl LifecycleImplementation) reconcileJob(
161109
ctx context.Context,
162110
cluster *cnpgv1.Cluster,
@@ -165,17 +113,23 @@ func (impl LifecycleImplementation) reconcileJob(
165113
) (*lifecycle.OperatorLifecycleResponse, error) {
166114
env, err := impl.collectAdditionalEnvs(ctx, cluster.Namespace, pluginConfiguration)
167115
if err != nil {
168-
return nil, nil
116+
return nil, err
169117
}
170118

171-
return reconcileJob(ctx, cluster, request, env)
119+
certificates, err := impl.collectAdditionalCertificates(ctx, cluster.Namespace, pluginConfiguration)
120+
if err != nil {
121+
return nil, err
122+
}
123+
124+
return reconcileJob(ctx, cluster, request, env, certificates)
172125
}
173126

174127
func reconcileJob(
175128
ctx context.Context,
176129
cluster *cnpgv1.Cluster,
177130
request *lifecycle.OperatorLifecycleRequest,
178131
env []corev1.EnvVar,
132+
certificates []corev1.VolumeProjection,
179133
) (*lifecycle.OperatorLifecycleResponse, error) {
180134
contextLogger := log.FromContext(ctx).WithName("lifecycle")
181135
if pluginConfig := cluster.GetRecoverySourcePlugin(); pluginConfig == nil || pluginConfig.Name != metadata.PluginName {
@@ -212,6 +166,7 @@ func reconcileJob(
212166
Args: []string{"restore"},
213167
},
214168
env,
169+
certificates,
215170
); err != nil {
216171
return nil, fmt.Errorf("while reconciling pod spec for job: %w", err)
217172
}
@@ -235,10 +190,15 @@ func (impl LifecycleImplementation) reconcilePod(
235190
) (*lifecycle.OperatorLifecycleResponse, error) {
236191
env, err := impl.collectAdditionalEnvs(ctx, cluster.Namespace, pluginConfiguration)
237192
if err != nil {
238-
return nil, nil
193+
return nil, err
194+
}
195+
196+
certificates, err := impl.collectAdditionalCertificates(ctx, cluster.Namespace, pluginConfiguration)
197+
if err != nil {
198+
return nil, err
239199
}
240200

241-
return reconcilePod(ctx, cluster, request, pluginConfiguration, env)
201+
return reconcilePod(ctx, cluster, request, pluginConfiguration, env, certificates)
242202
}
243203

244204
func reconcilePod(
@@ -247,6 +207,7 @@ func reconcilePod(
247207
request *lifecycle.OperatorLifecycleRequest,
248208
pluginConfiguration *config.PluginConfiguration,
249209
env []corev1.EnvVar,
210+
certificates []corev1.VolumeProjection,
250211
) (*lifecycle.OperatorLifecycleResponse, error) {
251212
pod, err := decoder.DecodePodJSON(request.GetObjectDefinition())
252213
if err != nil {
@@ -267,6 +228,7 @@ func reconcilePod(
267228
Args: []string{"instance"},
268229
},
269230
env,
231+
certificates,
270232
); err != nil {
271233
return nil, fmt.Errorf("while reconciling pod spec for pod: %w", err)
272234
}
@@ -291,6 +253,7 @@ func reconcilePodSpec(
291253
mainContainerName string,
292254
sidecarConfig corev1.Container,
293255
additionalEnvs []corev1.EnvVar,
256+
certificates []corev1.VolumeProjection,
294257
) error {
295258
envs := []corev1.EnvVar{
296259
{
@@ -360,10 +323,22 @@ func reconcilePodSpec(
360323
}
361324
}
362325

363-
if err := InjectPluginSidecarPodSpec(spec, &sidecarConfig, mainContainerName, true); err != nil {
326+
if err := injectPluginSidecarPodSpec(spec, &sidecarConfig, mainContainerName); err != nil {
364327
return err
365328
}
366329

330+
// inject the volume containing the certificates if needed
331+
if !volumeListHasVolume(spec.Volumes, barmanCertificatesVolumeName) {
332+
spec.Volumes = append(spec.Volumes, corev1.Volume{
333+
Name: barmanCertificatesVolumeName,
334+
VolumeSource: corev1.VolumeSource{
335+
Projected: &corev1.ProjectedVolumeSource{
336+
Sources: certificates,
337+
},
338+
},
339+
})
340+
}
341+
367342
return nil
368343
}
369344

@@ -407,16 +382,15 @@ func InjectPluginVolumePodSpec(spec *corev1.PodSpec, mainContainerName string) {
407382
}
408383
}
409384

410-
// InjectPluginSidecarPodSpec injects a plugin sidecar into a CNPG Pod spec.
385+
// injectPluginSidecarPodSpec injects a plugin sidecar into a CNPG Pod spec.
411386
//
412387
// If the "injectMainContainerVolumes" flag is true, this will append all the volume
413388
// mounts that are used in the instance manager Pod to the passed sidecar
414389
// container, granting it superuser access to the PostgreSQL instance.
415-
func InjectPluginSidecarPodSpec(
390+
func injectPluginSidecarPodSpec(
416391
spec *corev1.PodSpec,
417392
sidecar *corev1.Container,
418393
mainContainerName string,
419-
injectMainContainerVolumes bool,
420394
) error {
421395
sidecar = sidecar.DeepCopy()
422396
InjectPluginVolumePodSpec(spec, mainContainerName)
@@ -447,11 +421,27 @@ func InjectPluginSidecarPodSpec(
447421
}
448422

449423
// Do not modify the passed sidecar definition
450-
if injectMainContainerVolumes {
451-
sidecar.VolumeMounts = append(sidecar.VolumeMounts, volumeMounts...)
452-
}
424+
sidecar.VolumeMounts = append(
425+
sidecar.VolumeMounts,
426+
corev1.VolumeMount{
427+
Name: barmanCertificatesVolumeName,
428+
MountPath: metadata.BarmanCertificatesPath,
429+
})
430+
sidecar.VolumeMounts = append(sidecar.VolumeMounts, volumeMounts...)
453431
sidecar.RestartPolicy = ptr.To(corev1.ContainerRestartPolicyAlways)
454432
spec.InitContainers = append(spec.InitContainers, *sidecar)
455433

456434
return nil
457435
}
436+
437+
// volumeListHasVolume check if a volume with a known name exists
438+
// in the volume list
439+
func volumeListHasVolume(volumes []corev1.Volume, name string) bool {
440+
for i := range volumes {
441+
if volumes[i].Name == name {
442+
return true
443+
}
444+
}
445+
446+
return false
447+
}

0 commit comments

Comments
 (0)