feat: Resolve caCertRef in object store getter for plugin compatibility

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 <noreply@anthropic.com>
Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
This commit is contained in:
Tiger Kaovilai
2025-12-12 01:21:11 +07:00
parent 61bf2ef777
commit 8688568ffc
5 changed files with 84 additions and 2 deletions

View File

@@ -93,6 +93,15 @@ func (b *BackupStorageLocationBuilder) CACert(val []byte) *BackupStorageLocation
return b 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 // Default sets the BackupStorageLocation's is default or not
func (b *BackupStorageLocationBuilder) Default(isDefault bool) *BackupStorageLocationBuilder { func (b *BackupStorageLocationBuilder) Default(isDefault bool) *BackupStorageLocationBuilder {
b.object.Spec.Default = isDefault b.object.Spec.Default = isDefault

View File

@@ -558,7 +558,7 @@ func (s *server) runControllers(defaultVolumeSnapshotLocations map[string]string
return clientmgmt.NewManager(logger, s.logLevel, s.pluginRegistry) return clientmgmt.NewManager(logger, s.logLevel, s.pluginRegistry)
} }
backupStoreGetter := persistence.NewObjectBackupStoreGetter(s.credentialFileStore) backupStoreGetter := persistence.NewObjectBackupStoreGetterWithSecretStore(s.credentialFileStore, s.credentialSecretStore)
backupTracker := controller.NewBackupTracker() backupTracker := controller.NewBackupTracker()

View File

@@ -116,6 +116,7 @@ type ObjectBackupStoreGetter interface {
type objectBackupStoreGetter struct { type objectBackupStoreGetter struct {
credentialStore credentials.FileStore credentialStore credentials.FileStore
secretStore credentials.SecretStore
} }
// NewObjectBackupStoreGetter returns a ObjectBackupStoreGetter that can get a velero.BackupStore. // NewObjectBackupStoreGetter returns a ObjectBackupStoreGetter that can get a velero.BackupStore.
@@ -123,6 +124,15 @@ func NewObjectBackupStoreGetter(credentialStore credentials.FileStore) ObjectBac
return &objectBackupStoreGetter{credentialStore: credentialStore} 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) { func (b *objectBackupStoreGetter) Get(location *velerov1api.BackupStorageLocation, objectStoreGetter ObjectStoreGetter, logger logrus.FieldLogger) (BackupStore, error) {
if location.Spec.ObjectStorage == nil { if location.Spec.ObjectStorage == nil {
return nil, errors.New("backup storage location does not use object storage") 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 objectStoreConfig["prefix"] = prefix
// Only include a CACert if it's specified in order to maintain compatibility with plugins that don't expect it. // 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) objectStoreConfig["caCert"] = string(location.Spec.ObjectStorage.CACert)
} }

View File

@@ -1017,6 +1017,32 @@ func TestNewObjectBackupStoreGetterConfig(t *testing.T) {
"credentialsFile": "/tmp/credentials/secret-file", "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 { for _, tc := range tests {

View File

@@ -47,3 +47,31 @@ func NewFakeCredentialsFileStore(path string, err error) FileStore {
err: err, 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,
}
}