From 8688568ffcbf3c81a33d38e1a85f9288457c3e7d Mon Sep 17 00:00:00 2001 From: Tiger Kaovilai Date: Fri, 12 Dec 2025 01:21:11 +0700 Subject: [PATCH] feat: Resolve caCertRef in object store getter for plugin compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change enables BSL validation to work when using caCertRef (Secret-based CA certificate) by resolving the certificate from the Secret in velero core before passing it to the object store plugin as 'caCert' in the config map. This approach requires no changes to provider plugins since they already understand the 'caCert' config key. Changes: - Add SecretStore to objectBackupStoreGetter struct - Add NewObjectBackupStoreGetterWithSecretStore constructor - Update Get method to resolve caCertRef from Secret - Update server.go to use new constructor with SecretStore - Add CACertRef builder method and unit tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 Signed-off-by: Tiger Kaovilai --- .../backup_storage_location_builder.go | 9 ++++++ pkg/cmd/server/server.go | 2 +- pkg/persistence/object_store.go | 21 +++++++++++++- pkg/persistence/object_store_test.go | 26 +++++++++++++++++ pkg/test/fake_credential_file_store.go | 28 +++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) diff --git a/pkg/builder/backup_storage_location_builder.go b/pkg/builder/backup_storage_location_builder.go index 1c25d5a60..33becd2a7 100644 --- a/pkg/builder/backup_storage_location_builder.go +++ b/pkg/builder/backup_storage_location_builder.go @@ -93,6 +93,15 @@ func (b *BackupStorageLocationBuilder) CACert(val []byte) *BackupStorageLocation return b } +// CACertRef sets the BackupStorageLocation's object storage CACertRef (Secret reference). +func (b *BackupStorageLocationBuilder) CACertRef(selector *corev1api.SecretKeySelector) *BackupStorageLocationBuilder { + if b.object.Spec.StorageType.ObjectStorage == nil { + b.object.Spec.StorageType.ObjectStorage = new(velerov1api.ObjectStorageLocation) + } + b.object.Spec.ObjectStorage.CACertRef = selector + return b +} + // Default sets the BackupStorageLocation's is default or not func (b *BackupStorageLocationBuilder) Default(isDefault bool) *BackupStorageLocationBuilder { b.object.Spec.Default = isDefault diff --git a/pkg/cmd/server/server.go b/pkg/cmd/server/server.go index 053cee077..e744013e8 100644 --- a/pkg/cmd/server/server.go +++ b/pkg/cmd/server/server.go @@ -558,7 +558,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string return clientmgmt.NewManager(logger, s.logLevel, s.pluginRegistry) } - backupStoreGetter := persistence.NewObjectBackupStoreGetter(s.credentialFileStore) + backupStoreGetter := persistence.NewObjectBackupStoreGetterWithSecretStore(s.credentialFileStore, s.credentialSecretStore) backupTracker := controller.NewBackupTracker() diff --git a/pkg/persistence/object_store.go b/pkg/persistence/object_store.go index 44ad96118..e3da1f362 100644 --- a/pkg/persistence/object_store.go +++ b/pkg/persistence/object_store.go @@ -116,6 +116,7 @@ type ObjectBackupStoreGetter interface { type objectBackupStoreGetter struct { credentialStore credentials.FileStore + secretStore credentials.SecretStore } // NewObjectBackupStoreGetter returns a ObjectBackupStoreGetter that can get a velero.BackupStore. @@ -123,6 +124,15 @@ func NewObjectBackupStoreGetter(credentialStore credentials.FileStore) ObjectBac return &objectBackupStoreGetter{credentialStore: credentialStore} } +// NewObjectBackupStoreGetterWithSecretStore returns an ObjectBackupStoreGetter with SecretStore +// support for resolving caCertRef from Kubernetes Secrets. +func NewObjectBackupStoreGetterWithSecretStore(credentialStore credentials.FileStore, secretStore credentials.SecretStore) ObjectBackupStoreGetter { + return &objectBackupStoreGetter{ + credentialStore: credentialStore, + secretStore: secretStore, + } +} + func (b *objectBackupStoreGetter) Get(location *velerov1api.BackupStorageLocation, objectStoreGetter ObjectStoreGetter, logger logrus.FieldLogger) (BackupStore, error) { if location.Spec.ObjectStorage == nil { return nil, errors.New("backup storage location does not use object storage") @@ -160,7 +170,16 @@ func (b *objectBackupStoreGetter) Get(location *velerov1api.BackupStorageLocatio objectStoreConfig["prefix"] = prefix // Only include a CACert if it's specified in order to maintain compatibility with plugins that don't expect it. - if location.Spec.ObjectStorage.CACert != nil { + // Prefer caCertRef (from Secret) over inline caCert (deprecated). + if location.Spec.ObjectStorage.CACertRef != nil { + if b.secretStore != nil { + caCertString, err := b.secretStore.Get(location.Spec.ObjectStorage.CACertRef) + if err != nil { + return nil, errors.Wrap(err, "error getting CA certificate from secret") + } + objectStoreConfig["caCert"] = caCertString + } + } else if location.Spec.ObjectStorage.CACert != nil { objectStoreConfig["caCert"] = string(location.Spec.ObjectStorage.CACert) } diff --git a/pkg/persistence/object_store_test.go b/pkg/persistence/object_store_test.go index 0314f78c3..fac2f8d97 100644 --- a/pkg/persistence/object_store_test.go +++ b/pkg/persistence/object_store_test.go @@ -1017,6 +1017,32 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) { "credentialsFile": "/tmp/credentials/secret-file", }, }, + { + name: "location with CACertRef is initialized with caCert from secret", + location: builder.ForBackupStorageLocation("", "").Provider(provider).Bucket(bucket).CACertRef( + builder.ForSecretKeySelector("cacert-secret", "ca.crt").Result(), + ).Result(), + getter: NewObjectBackupStoreGetterWithSecretStore( + velerotest.NewFakeCredentialsFileStore("", nil), + velerotest.NewFakeCredentialsSecretStore("cacert-from-secret", nil), + ), + wantConfig: map[string]string{ + "bucket": "bucket", + "prefix": "", + "caCert": "cacert-from-secret", + }, + }, + { + name: "location with CACertRef and no SecretStore uses no caCert", + location: builder.ForBackupStorageLocation("", "").Provider(provider).Bucket(bucket).CACertRef( + builder.ForSecretKeySelector("cacert-secret", "ca.crt").Result(), + ).Result(), + getter: NewObjectBackupStoreGetter(velerotest.NewFakeCredentialsFileStore("", nil)), + wantConfig: map[string]string{ + "bucket": "bucket", + "prefix": "", + }, + }, } for _, tc := range tests { diff --git a/pkg/test/fake_credential_file_store.go b/pkg/test/fake_credential_file_store.go index af6618521..d1580441e 100644 --- a/pkg/test/fake_credential_file_store.go +++ b/pkg/test/fake_credential_file_store.go @@ -47,3 +47,31 @@ func NewFakeCredentialsFileStore(path string, err error) FileStore { err: err, } } + +// SecretStore defines operations for interacting with credentials +// that are stored in Secret. +type SecretStore interface { + // Get returns the secret key defined by the given selector + Get(selector *corev1api.SecretKeySelector) (string, error) +} + +type fakeCredentialsSecretStore struct { + data string + err error +} + +// Get returns the secret data. +func (f *fakeCredentialsSecretStore) Get(*corev1api.SecretKeySelector) (string, error) { + return f.data, f.err +} + +// NewFakeCredentialsSecretStore creates a SecretStore which will return the given data +// and error when Get is called. +// data is the secret value to return (e.g., certificate content). +// err is the error to return, if any. +func NewFakeCredentialsSecretStore(data string, err error) SecretStore { + return &fakeCredentialsSecretStore{ + data: data, + err: err, + } +}