Merge pull request #8557 from kaovilai/cacertcli-auto
Some checks failed
Run the E2E test on kind / build (push) Failing after 11m26s
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Has been skipped
Main CI / Build (push) Failing after 35s

CLI automatically discovers and uses cacert from BSL
This commit is contained in:
Daniel Jiang
2025-08-04 14:08:08 +08:00
committed by GitHub
16 changed files with 2255 additions and 58 deletions

View File

@@ -0,0 +1 @@
CLI automatically discovers and uses cacert from BSL for download requests

View File

@@ -62,21 +62,21 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
if len(args) > 0 {
for _, name := range args {
backup := new(velerov1api.Backup)
err := kbClient.Get(context.TODO(), controllerclient.ObjectKey{Namespace: f.Namespace(), Name: name}, backup)
err := kbClient.Get(context.Background(), controllerclient.ObjectKey{Namespace: f.Namespace(), Name: name}, backup)
cmd.CheckError(err)
backups.Items = append(backups.Items, *backup)
}
} else {
parsedSelector, err := labels.Parse(listOptions.LabelSelector)
cmd.CheckError(err)
err = kbClient.List(context.TODO(), backups, &controllerclient.ListOptions{LabelSelector: parsedSelector, Namespace: f.Namespace()})
err = kbClient.List(context.Background(), backups, &controllerclient.ListOptions{LabelSelector: parsedSelector, Namespace: f.Namespace()})
cmd.CheckError(err)
}
first := true
for i, backup := range backups.Items {
deleteRequestList := new(velerov1api.DeleteBackupRequestList)
err := kbClient.List(context.TODO(), deleteRequestList, &controllerclient.ListOptions{
err := kbClient.List(context.Background(), deleteRequestList, &controllerclient.ListOptions{
Namespace: f.Namespace(),
LabelSelector: labels.SelectorFromSet(map[string]string{velerov1api.BackupNameLabel: label.GetValidName(backup.Name), velerov1api.BackupUIDLabel: string(backup.UID)}),
})
@@ -85,7 +85,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
}
podVolumeBackupList := new(velerov1api.PodVolumeBackupList)
err = kbClient.List(context.TODO(), podVolumeBackupList, &controllerclient.ListOptions{
err = kbClient.List(context.Background(), podVolumeBackupList, &controllerclient.ListOptions{
Namespace: f.Namespace(),
LabelSelector: labels.SelectorFromSet(map[string]string{velerov1api.BackupNameLabel: label.GetValidName(backup.Name)}),
})
@@ -115,7 +115,7 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command {
c.Flags().StringVarP(&listOptions.LabelSelector, "selector", "l", listOptions.LabelSelector, "Only show items matching this label selector.")
c.Flags().BoolVar(&details, "details", details, "Display additional detail in the command output.")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections. If not specified, the CA certificate from the BackupStorageLocation will be used if available.")
c.Flags().StringVarP(&outputFormat, "output", "o", outputFormat, "Output display format. Valid formats are 'plaintext, json'. 'json' only applies to a single backup")
return c

View File

@@ -31,6 +31,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
)
@@ -80,7 +81,7 @@ func (o *DownloadOptions) BindFlags(flags *pflag.FlagSet) {
flags.BoolVar(&o.Force, "force", o.Force, "Forces the download and will overwrite file if it exists already.")
flags.DurationVar(&o.Timeout, "timeout", o.Timeout, "Maximum time to wait to process download request.")
flags.BoolVar(&o.InsecureSkipTLSVerify, "insecure-skip-tls-verify", o.InsecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
flags.StringVar(&o.caCertFile, "cacert", o.caCertFile, "Path to a certificate bundle to use when verifying TLS connections.")
flags.StringVar(&o.caCertFile, "cacert", o.caCertFile, "Path to a certificate bundle to use when verifying TLS connections. If not specified, the CA certificate from the BackupStorageLocation will be used if available.")
}
func (o *DownloadOptions) Validate(c *cobra.Command, args []string, f client.Factory) error {
@@ -88,7 +89,7 @@ func (o *DownloadOptions) Validate(c *cobra.Command, args []string, f client.Fac
cmd.CheckError(err)
backup := new(velerov1api.Backup)
if err := kbClient.Get(context.TODO(), controllerclient.ObjectKey{Namespace: f.Namespace(), Name: o.Name}, backup); err != nil {
if err := kbClient.Get(context.Background(), controllerclient.ObjectKey{Namespace: f.Namespace(), Name: o.Name}, backup); err != nil {
return err
}
@@ -118,13 +119,27 @@ func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error {
kbClient, err := f.KubebuilderClient()
cmd.CheckError(err)
// Get the backup to fetch BSL cacert
backup := new(velerov1api.Backup)
if err := kbClient.Get(context.Background(), controllerclient.ObjectKey{Namespace: f.Namespace(), Name: o.Name}, backup); err != nil {
return err
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(context.Background(), kbClient, f.Namespace(), backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
fmt.Fprintf(os.Stderr, "WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
backupDest, err := os.OpenFile(o.Output, o.writeOptions, 0600)
if err != nil {
return err
}
defer backupDest.Close()
err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), o.Name, velerov1api.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile)
err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), o.Name, velerov1api.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile, bslCACert)
if err != nil {
os.Remove(o.Output)
cmd.CheckError(err)

View File

@@ -30,6 +30,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
)
@@ -62,7 +63,7 @@ func (l *LogsOptions) BindFlags(flags *pflag.FlagSet) {
func (l *LogsOptions) Run(c *cobra.Command, f client.Factory) error {
backup := new(velerov1api.Backup)
err := l.Client.Get(context.TODO(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: l.BackupName}, backup)
err := l.Client.Get(context.Background(), kbclient.ObjectKey{Namespace: f.Namespace(), Name: l.BackupName}, backup)
if apierrors.IsNotFound(err) {
return fmt.Errorf("backup %q does not exist", l.BackupName)
} else if err != nil {
@@ -77,7 +78,15 @@ func (l *LogsOptions) Run(c *cobra.Command, f client.Factory) error {
"until the backup has a phase of Completed or Failed and try again", l.BackupName)
}
err = downloadrequest.Stream(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile)
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(context.Background(), l.Client, f.Namespace(), backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
fmt.Fprintf(os.Stderr, "WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
err = downloadrequest.StreamWithBSLCACert(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile, bslCACert)
return err
}

View File

@@ -17,7 +17,13 @@ limitations under the License.
package backup
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strconv"
"testing"
"time"
@@ -31,6 +37,7 @@ import (
"github.com/vmware-tanzu/velero/pkg/builder"
factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks"
cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
@@ -164,4 +171,207 @@ func TestNewLogsCommand(t *testing.T) {
err := l.Complete([]string{""}, f)
require.Equal(t, "test error", err.Error())
})
t.Run("Backup with BSL cacert test", func(t *testing.T) {
backupName := "bk-logs-with-cacert"
bslName := "test-bsl"
expectedCACert := "test-cacert-content"
expectedLogContent := "test backup log content"
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
// Create BSL with cacert
bsl := builder.ForBackupStorageLocation(cmdtest.VeleroNameSpace, bslName).
Provider("aws").
Bucket("test-bucket").
CACert([]byte(expectedCACert)).
Result()
err := kbClient.Create(t.Context(), bsl, &kbclient.CreateOptions{})
require.NoError(t, err)
// Create backup referencing the BSL
backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).
Phase(velerov1api.BackupPhaseCompleted).
StorageLocation(bslName).
Result()
err = kbClient.Create(t.Context(), backup, &kbclient.CreateOptions{})
require.NoError(t, err)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
assert.Equal(t, "Get backup logs", c.Short)
l := NewLogsOptions()
flags := new(flag.FlagSet)
l.BindFlags(flags)
err = l.Complete([]string{backupName}, f)
require.NoError(t, err)
// Verify that the BSL cacert can be fetched correctly before running the command
fetchedBackup := &velerov1api.Backup{}
err = kbClient.Get(t.Context(), kbclient.ObjectKey{Namespace: cmdtest.VeleroNameSpace, Name: backupName}, fetchedBackup)
require.NoError(t, err)
// Test the cacert fetching logic directly
cacertValue, err := cacert.GetCACertFromBackup(t.Context(), kbClient, cmdtest.VeleroNameSpace, fetchedBackup)
require.NoError(t, err)
assert.Equal(t, expectedCACert, cacertValue, "BSL cacert should be retrieved correctly")
// Create a mock HTTP server to serve the log content
mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// For logs, we need to gzip the content
gzipWriter := gzip.NewWriter(w)
defer gzipWriter.Close()
gzipWriter.Write([]byte(expectedLogContent))
}))
defer mockServer.Close()
// Mock the download request controller by updating DownloadRequests
go func() {
time.Sleep(50 * time.Millisecond) // Wait a bit for the request to be created
// List all DownloadRequests
downloadRequestList := &velerov1api.DownloadRequestList{}
if err := kbClient.List(t.Context(), downloadRequestList, &kbclient.ListOptions{
Namespace: cmdtest.VeleroNameSpace,
}); err == nil {
// Update each download request with the mock server URL
for _, dr := range downloadRequestList.Items {
if dr.Spec.Target.Kind == velerov1api.DownloadTargetKindBackupLog &&
dr.Spec.Target.Name == backupName {
dr.Status.DownloadURL = mockServer.URL
dr.Status.Phase = velerov1api.DownloadRequestPhaseProcessed
kbClient.Update(t.Context(), &dr)
}
}
}
}()
// Capture the output
var logOutput bytes.Buffer
// Temporarily redirect stdout
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// Run the logs command - it should now succeed
l.Timeout = 5 * time.Second
err = l.Run(c, f)
// Restore stdout and read the output
w.Close()
os.Stdout = oldStdout
io.Copy(&logOutput, r)
// Verify the command succeeded and output is correct
require.NoError(t, err)
assert.Equal(t, expectedLogContent, logOutput.String())
})
}
func TestBSLCACertBehavior(t *testing.T) {
t.Run("Backup with BSL without cacert test", func(t *testing.T) {
backupName := "bk-logs-without-cacert"
bslName := "test-bsl-no-cacert"
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
// Create BSL without cacert
bsl := builder.ForBackupStorageLocation(cmdtest.VeleroNameSpace, bslName).
Provider("aws").
Bucket("test-bucket").
// No CACert() call - BSL will have no cacert
Result()
err := kbClient.Create(t.Context(), bsl, &kbclient.CreateOptions{})
require.NoError(t, err)
// Create backup referencing the BSL
backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).
Phase(velerov1api.BackupPhaseCompleted).
StorageLocation(bslName).
Result()
err = kbClient.Create(t.Context(), backup, &kbclient.CreateOptions{})
require.NoError(t, err)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
assert.Equal(t, "Get backup logs", c.Short)
l := NewLogsOptions()
flags := new(flag.FlagSet)
l.BindFlags(flags)
err = l.Complete([]string{backupName}, f)
require.NoError(t, err)
// Verify that the BSL cacert returns empty string when not present
fetchedBackup := &velerov1api.Backup{}
err = kbClient.Get(t.Context(), kbclient.ObjectKey{Namespace: cmdtest.VeleroNameSpace, Name: backupName}, fetchedBackup)
require.NoError(t, err)
// Test the cacert fetching logic directly
cacertValue, err := cacert.GetCACertFromBackup(t.Context(), kbClient, cmdtest.VeleroNameSpace, fetchedBackup)
require.NoError(t, err)
assert.Empty(t, cacertValue, "BSL cacert should be empty when not configured")
// The command should still work without cacert
l.Timeout = 100 * time.Millisecond
err = l.Run(c, f)
require.Error(t, err)
// The error should be about download request timeout, not about cacert fetching
assert.Contains(t, err.Error(), "download")
})
t.Run("Backup with nonexistent BSL test", func(t *testing.T) {
backupName := "bk-logs-with-missing-bsl"
bslName := "nonexistent-bsl"
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
// Create backup referencing a BSL that doesn't exist
backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).
Phase(velerov1api.BackupPhaseCompleted).
StorageLocation(bslName).
Result()
err := kbClient.Create(t.Context(), backup, &kbclient.CreateOptions{})
require.NoError(t, err)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
l := NewLogsOptions()
flags := new(flag.FlagSet)
l.BindFlags(flags)
err = l.Complete([]string{backupName}, f)
require.NoError(t, err)
// Verify that the BSL cacert returns empty string when BSL doesn't exist
fetchedBackup := &velerov1api.Backup{}
err = kbClient.Get(t.Context(), kbclient.ObjectKey{Namespace: cmdtest.VeleroNameSpace, Name: backupName}, fetchedBackup)
require.NoError(t, err)
// Test the cacert fetching logic directly - should not error when BSL is missing
cacertValue, err := cacert.GetCACertFromBackup(t.Context(), kbClient, cmdtest.VeleroNameSpace, fetchedBackup)
require.NoError(t, err)
assert.Empty(t, cacertValue, "BSL cacert should be empty when BSL doesn't exist")
// The command should still try to run even without BSL
l.Timeout = 100 * time.Millisecond
err = l.Run(c, f)
require.Error(t, err)
assert.Contains(t, err.Error(), "download")
})
}

View File

@@ -29,6 +29,7 @@ import (
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
)
@@ -53,7 +54,7 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
cmd.CheckError(err)
restore := new(velerov1api.Restore)
err = kbClient.Get(context.TODO(), ctrlclient.ObjectKey{Namespace: f.Namespace(), Name: restoreName}, restore)
err = kbClient.Get(context.Background(), ctrlclient.ObjectKey{Namespace: f.Namespace(), Name: restoreName}, restore)
if apierrors.IsNotFound(err) {
cmd.Exit("Restore %q does not exist.", restoreName)
} else if err != nil {
@@ -68,14 +69,22 @@ func NewLogsCommand(f client.Factory) *cobra.Command {
"until the restore has a phase of Completed or Failed and try again.", restoreName)
}
err = downloadrequest.Stream(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile)
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromRestore(context.Background(), kbClient, f.Namespace(), restore)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
fmt.Fprintf(os.Stderr, "WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile, bslCACert)
cmd.CheckError(err)
},
}
c.Flags().DurationVar(&timeout, "timeout", timeout, "How long to wait to receive logs.")
c.Flags().BoolVar(&insecureSkipTLSVerify, "insecure-skip-tls-verify", insecureSkipTLSVerify, "If true, the object store's TLS certificate will not be checked for validity. This is insecure and susceptible to man-in-the-middle attacks. Not recommended for production.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections.")
c.Flags().StringVar(&caCertFile, "cacert", caCertFile, "Path to a certificate bundle to use when verifying TLS connections. If not specified, the CA certificate from the BackupStorageLocation will be used if available.")
return c
}

View File

@@ -19,13 +19,18 @@ package restore
import (
"os"
"testing"
"time"
flag "github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
factorymocks "github.com/vmware-tanzu/velero/pkg/client/mocks"
cmdtest "github.com/vmware-tanzu/velero/pkg/cmd/test"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
velerotest "github.com/vmware-tanzu/velero/pkg/test"
)
func TestNewLogsCommand(t *testing.T) {
@@ -35,15 +40,124 @@ func TestNewLogsCommand(t *testing.T) {
c := NewLogsCommand(f)
require.Equal(t, "Get restore logs", c.Short)
flags := new(flag.FlagSet)
timeout := "1m0s"
// Test flag parsing
timeout := "1s"
insecureSkipTLSVerify := "true"
caCertFile := "testing"
flags.Parse([]string{"--timeout", timeout})
flags.Parse([]string{"--insecure-skip-tls-verify", insecureSkipTLSVerify})
flags.Parse([]string{"--cacert", caCertFile})
c.Flags().Set("timeout", timeout)
c.Flags().Set("insecure-skip-tls-verify", insecureSkipTLSVerify)
c.Flags().Set("cacert", caCertFile)
timeoutFlag, _ := c.Flags().GetDuration("timeout")
require.Equal(t, 1*time.Second, timeoutFlag)
insecureFlag, _ := c.Flags().GetBool("insecure-skip-tls-verify")
require.True(t, insecureFlag)
caCertFlag, _ := c.Flags().GetString("cacert")
require.Equal(t, caCertFile, caCertFlag)
})
t.Run("Restore not complete test", func(t *testing.T) {
restoreName := "rs-logs-1"
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
restore := builder.ForRestore(cmdtest.VeleroNameSpace, restoreName).Result()
err := kbClient.Create(t.Context(), restore, &kbclient.CreateOptions{})
require.NoError(t, err)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
assert.Equal(t, "Get restore logs", c.Short)
// The restore command exits with an error message when restore is not complete
// We can't easily test this since it calls cmd.Exit, which exits the process
// So we'll skip this test case
t.Skip("Cannot test restore not complete case due to cmd.Exit() call")
})
t.Run("Restore not exist test", func(t *testing.T) {
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
assert.Equal(t, "Get restore logs", c.Short)
// The restore command exits with an error message when restore doesn't exist
// We can't easily test this since it calls cmd.Exit, which exits the process
// So we'll skip this test case
t.Skip("Cannot test restore not exist case due to cmd.Exit() call")
})
t.Run("Restore with BSL cacert test", func(t *testing.T) {
restoreName := "rs-logs-with-cacert"
backupName := "bk-for-restore"
bslName := "test-bsl"
// create a factory
f := &factorymocks.Factory{}
kbClient := velerotest.NewFakeControllerRuntimeClient(t)
// Create BSL with cacert
bsl := builder.ForBackupStorageLocation(cmdtest.VeleroNameSpace, bslName).
Provider("aws").
Bucket("test-bucket").
CACert([]byte("test-cacert-content")).
Result()
err := kbClient.Create(t.Context(), bsl, &kbclient.CreateOptions{})
require.NoError(t, err)
// Create backup referencing the BSL
backup := builder.ForBackup(cmdtest.VeleroNameSpace, backupName).
StorageLocation(bslName).
Result()
err = kbClient.Create(t.Context(), backup, &kbclient.CreateOptions{})
require.NoError(t, err)
// Create restore referencing the backup
restore := builder.ForRestore(cmdtest.VeleroNameSpace, restoreName).
Phase(velerov1api.RestorePhaseCompleted).
Backup(backupName).
Result()
err = kbClient.Create(t.Context(), restore, &kbclient.CreateOptions{})
require.NoError(t, err)
f.On("Namespace").Return(cmdtest.VeleroNameSpace)
f.On("KubebuilderClient").Return(kbClient, nil)
c := NewLogsCommand(f)
assert.Equal(t, "Get restore logs", c.Short)
// We can verify that BSL cacert fetching logic is in place
// The actual command will call downloadrequest which requires a controller
// to be running, so we'll just verify the command structure
require.NotNil(t, c.Run)
// Verify the BSL cacert can be fetched
cacertValue, err := cacert.GetCACertFromRestore(t.Context(), kbClient, f.Namespace(), restore)
require.NoError(t, err)
require.Equal(t, "test-cacert-content", cacertValue)
})
t.Run("CLI execution test", func(t *testing.T) {
// create a factory
f := &factorymocks.Factory{}
c := NewLogsCommand(f)
require.Equal(t, "Get restore logs", c.Short)
if os.Getenv(cmdtest.CaptureFlag) == "1" {
c.SetArgs([]string{"test"})

View File

@@ -0,0 +1,79 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cacert
import (
"context"
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
kbclient "sigs.k8s.io/controller-runtime/pkg/client"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
)
// GetCACertFromBackup fetches the BackupStorageLocation for a backup and returns its cacert
func GetCACertFromBackup(ctx context.Context, client kbclient.Client, namespace string, backup *velerov1api.Backup) (string, error) {
return GetCACertFromBSL(ctx, client, namespace, backup.Spec.StorageLocation)
}
// GetCACertFromRestore fetches the BackupStorageLocation for a restore's backup and returns its cacert
func GetCACertFromRestore(ctx context.Context, client kbclient.Client, namespace string, restore *velerov1api.Restore) (string, error) {
// First get the backup that this restore references
backup := &velerov1api.Backup{}
key := kbclient.ObjectKey{
Namespace: namespace,
Name: restore.Spec.BackupName,
}
if err := client.Get(ctx, key, backup); err != nil {
if apierrors.IsNotFound(err) {
// Backup not found is not a fatal error for cacert retrieval
return "", nil
}
return "", errors.Wrapf(err, "error getting backup %s", restore.Spec.BackupName)
}
return GetCACertFromBackup(ctx, client, namespace, backup)
}
// GetCACertFromBSL fetches a BackupStorageLocation directly and returns its cacert
func GetCACertFromBSL(ctx context.Context, client kbclient.Client, namespace, bslName string) (string, error) {
if bslName == "" {
return "", nil
}
bsl := &velerov1api.BackupStorageLocation{}
key := kbclient.ObjectKey{
Namespace: namespace,
Name: bslName,
}
if err := client.Get(ctx, key, bsl); err != nil {
if apierrors.IsNotFound(err) {
// BSL not found is not a fatal error, just means no cacert
return "", nil
}
return "", errors.Wrapf(err, "error getting backup storage location %s", bslName)
}
if bsl.Spec.ObjectStorage != nil && len(bsl.Spec.ObjectStorage.CACert) > 0 {
return string(bsl.Spec.ObjectStorage.CACert), nil
}
return "", nil
}

View File

@@ -0,0 +1,380 @@
/*
Copyright the Velero contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cacert
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/util"
)
func TestGetCACertFromBackup(t *testing.T) {
testCases := []struct {
name string
backup *velerov1api.Backup
bsl *velerov1api.BackupStorageLocation
expectedCACert string
expectedError bool
}{
{
name: "backup with BSL containing cacert",
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("test-bsl").
Result(),
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
CACert([]byte("test-cacert-content")).
Result(),
expectedCACert: "test-cacert-content",
expectedError: false,
},
{
name: "backup with BSL without cacert",
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("test-bsl").
Result(),
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
Result(),
expectedCACert: "",
expectedError: false,
},
{
name: "backup without storage location",
backup: builder.ForBackup("test-ns", "test-backup").
Result(),
bsl: nil,
expectedCACert: "",
expectedError: false,
},
{
name: "BSL not found",
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("missing-bsl").
Result(),
bsl: nil,
expectedCACert: "",
expectedError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var objs []runtime.Object
objs = append(objs, tc.backup)
if tc.bsl != nil {
objs = append(objs, tc.bsl)
}
fakeClient := fake.NewClientBuilder().
WithScheme(util.VeleroScheme).
WithRuntimeObjects(objs...).
Build()
cacert, err := GetCACertFromBackup(t.Context(), fakeClient, "test-ns", tc.backup)
if tc.expectedError {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedCACert, cacert)
}
})
}
}
func TestGetCACertFromRestore(t *testing.T) {
testCases := []struct {
name string
restore *velerov1api.Restore
backup *velerov1api.Backup
bsl *velerov1api.BackupStorageLocation
expectedCACert string
expectedError bool
}{
{
name: "restore with backup having BSL containing cacert",
restore: builder.ForRestore("test-ns", "test-restore").
Backup("test-backup").
Result(),
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("test-bsl").
Result(),
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
CACert([]byte("test-cacert-content")).
Result(),
expectedCACert: "test-cacert-content",
expectedError: false,
},
{
name: "restore with backup not found",
restore: builder.ForRestore("test-ns", "test-restore").
Backup("missing-backup").
Result(),
backup: nil,
bsl: nil,
expectedCACert: "",
expectedError: false,
},
{
name: "restore with backup having BSL without cacert",
restore: builder.ForRestore("test-ns", "test-restore").
Backup("test-backup").
Result(),
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("test-bsl").
Result(),
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
Result(),
expectedCACert: "",
expectedError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var objs []runtime.Object
objs = append(objs, tc.restore)
if tc.backup != nil {
objs = append(objs, tc.backup)
}
if tc.bsl != nil {
objs = append(objs, tc.bsl)
}
fakeClient := fake.NewClientBuilder().
WithScheme(util.VeleroScheme).
WithRuntimeObjects(objs...).
Build()
cacert, err := GetCACertFromRestore(t.Context(), fakeClient, "test-ns", tc.restore)
if tc.expectedError {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedCACert, cacert)
}
})
}
}
func TestGetCACertFromBSL(t *testing.T) {
testCases := []struct {
name string
bslName string
bsl *velerov1api.BackupStorageLocation
expectedCACert string
expectedError bool
}{
{
name: "BSL with cacert",
bslName: "test-bsl",
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
CACert([]byte("test-cacert-content")).
Result(),
expectedCACert: "test-cacert-content",
expectedError: false,
},
{
name: "BSL without cacert",
bslName: "test-bsl",
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
Result(),
expectedCACert: "",
expectedError: false,
},
{
name: "empty BSL name",
bslName: "",
bsl: nil,
expectedCACert: "",
expectedError: false,
},
{
name: "BSL not found",
bslName: "missing-bsl",
bsl: nil,
expectedCACert: "",
expectedError: false,
},
{
name: "BSL with invalid CA cert format",
bslName: "test-bsl",
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
CACert([]byte("INVALID CERT DATA WITHOUT PEM HEADERS")).
Result(),
expectedCACert: "INVALID CERT DATA WITHOUT PEM HEADERS", // We still return it, validation happens during TLS handshake
expectedError: false,
},
{
name: "BSL with malformed PEM certificate",
bslName: "test-bsl",
bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl").
Provider("aws").
Bucket("test-bucket").
CACert([]byte("-----BEGIN CERTIFICATE-----\nINVALID BASE64 DATA!!!\n-----END CERTIFICATE-----\n")).
Result(),
expectedCACert: "-----BEGIN CERTIFICATE-----\nINVALID BASE64 DATA!!!\n-----END CERTIFICATE-----\n",
expectedError: false,
},
{
name: "BSL with nil config",
bslName: "test-bsl",
bsl: &velerov1api.BackupStorageLocation{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-ns",
Name: "test-bsl",
},
Spec: velerov1api.BackupStorageLocationSpec{
Provider: "aws",
Config: nil,
},
},
expectedCACert: "",
expectedError: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var objs []runtime.Object
if tc.bsl != nil {
objs = append(objs, tc.bsl)
}
fakeClient := fake.NewClientBuilder().
WithScheme(util.VeleroScheme).
WithRuntimeObjects(objs...).
Build()
cacert, err := GetCACertFromBSL(t.Context(), fakeClient, "test-ns", tc.bslName)
if tc.expectedError {
assert.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.expectedCACert, cacert)
}
})
}
}
// TestGetCACertFromBackup_ClientError tests error scenarios where client.Get returns non-NotFound errors
func TestGetCACertFromBackup_ClientError(t *testing.T) {
testCases := []struct {
name string
backup *velerov1api.Backup
bsl *velerov1api.BackupStorageLocation
expectedError string
}{
{
name: "client error getting BSL",
backup: builder.ForBackup("test-ns", "test-backup").
StorageLocation("test-bsl").
Result(),
bsl: builder.ForBackupStorageLocation("different-ns", "test-bsl"). // Different namespace to trigger error
Provider("aws").
Bucket("test-bucket").
CACert([]byte("test-cacert-content")).
Result(),
expectedError: "not found",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var objs []runtime.Object
objs = append(objs, tc.backup)
if tc.bsl != nil {
objs = append(objs, tc.bsl)
}
fakeClient := fake.NewClientBuilder().
WithScheme(util.VeleroScheme).
WithRuntimeObjects(objs...).
Build()
// Try to get BSL from wrong namespace to simulate error
_, err := GetCACertFromBSL(t.Context(), fakeClient, "wrong-ns", tc.backup.Spec.StorageLocation)
require.NoError(t, err) // Not found errors are handled gracefully
})
}
}
// TestGetCACertFromRestore_ClientError tests error scenarios for GetCACertFromRestore
func TestGetCACertFromRestore_ClientError(t *testing.T) {
testCases := []struct {
name string
restore *velerov1api.Restore
backup *velerov1api.Backup
expectedError string
}{
{
name: "backup in different namespace",
restore: builder.ForRestore("test-ns", "test-restore").
Backup("test-backup").
Result(),
backup: builder.ForBackup("different-ns", "test-backup"). // Different namespace
StorageLocation("test-bsl").
Result(),
expectedError: "not found",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var objs []runtime.Object
objs = append(objs, tc.restore)
if tc.backup != nil {
objs = append(objs, tc.backup)
}
fakeClient := fake.NewClientBuilder().
WithScheme(util.VeleroScheme).
WithRuntimeObjects(objs...).
Build()
// This should not find the backup in the wrong namespace
cacert, err := GetCACertFromRestore(t.Context(), fakeClient, "test-ns", tc.restore)
require.NoError(t, err) // Not found errors are handled gracefully, returning empty string
assert.Empty(t, cacert)
})
}
}

View File

@@ -50,6 +50,22 @@ func Stream(
timeout time.Duration,
insecureSkipTLSVerify bool,
caCertFile string,
) error {
return StreamWithBSLCACert(ctx, kbClient, namespace, name, kind, w, timeout, insecureSkipTLSVerify, caCertFile, "")
}
// StreamWithBSLCACert is like Stream but accepts an additional bslCACert parameter
// that contains the cacert from the BackupStorageLocation config
func StreamWithBSLCACert(
ctx context.Context,
kbClient kbclient.Client,
namespace, name string,
kind veleroV1api.DownloadTargetKind,
w io.Writer,
timeout time.Duration,
insecureSkipTLSVerify bool,
caCertFile string,
bslCACert string,
) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
@@ -59,7 +75,7 @@ func Stream(
return err
}
if err := download(ctx, downloadURL, kind, w, insecureSkipTLSVerify, caCertFile); err != nil {
if err := download(ctx, downloadURL, kind, w, insecureSkipTLSVerify, caCertFile, bslCACert); err != nil {
return err
}
@@ -109,21 +125,35 @@ func download(
w io.Writer,
insecureSkipTLSVerify bool,
caCertFile string,
caCertByteString string,
) error {
var caPool *x509.CertPool
var err error
// Initialize caPool once
caPool, err = x509.SystemCertPool()
if err != nil {
caPool = x509.NewCertPool()
}
// Try to load CA cert from file first
if len(caCertFile) > 0 {
caCert, err := os.ReadFile(caCertFile)
if err != nil {
return errors.Wrapf(err, "couldn't open cacert")
// If caCertFile fails and BSL cert is available, fall back to it
if len(caCertByteString) > 0 {
fmt.Fprintf(os.Stderr, "Warning: Failed to open CA certificate file %s: %v. Using CA certificate from backup storage location instead.\n", caCertFile, err)
caPool.AppendCertsFromPEM([]byte(caCertByteString))
} else {
// If no BSL cert available, return the original error
return errors.Wrapf(err, "couldn't open cacert")
}
} else {
caPool.AppendCertsFromPEM(caCert)
}
// bundle the passed in cert with the system cert pool
// if it's available, otherwise create a new pool just
// for this.
caPool, err = x509.SystemCertPool()
if err != nil {
caPool = x509.NewCertPool()
}
caPool.AppendCertsFromPEM(caCert)
} else if len(caCertByteString) > 0 {
// If no caCertFile specified, use BSL cert if available
caPool.AppendCertsFromPEM([]byte(caCertByteString))
}
defaultTransport := http.DefaultTransport.(*http.Transport)

File diff suppressed because it is too large Load Diff

View File

@@ -36,6 +36,7 @@ import (
veleroapishared "github.com/vmware-tanzu/velero/pkg/apis/velero/shared"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
@@ -377,8 +378,16 @@ func describeBackupItemOperations(ctx context.Context, kbClient kbclient.Client,
return
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupItemOperations, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupItemOperations, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
d.Printf("Backup Item Operations:\t<error getting operation info: %v>\n", err)
return
}
@@ -397,8 +406,16 @@ func describeBackupItemOperations(ctx context.Context, kbClient kbclient.Client,
}
func describeBackupResourceList(ctx context.Context, kbClient kbclient.Client, d *Describer, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) {
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
if err == downloadrequest.ErrNotFound {
// the backup resource list could be missing if (other reasons may exist as well):
// - the backup was taken prior to v1.1; or
@@ -444,20 +461,28 @@ func describeBackupVolumes(
) {
d.Println("Backup Volumes:")
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err = downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err == downloadrequest.ErrNotFound {
nativeSnapshots, err = retrieveNativeSnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath)
nativeSnapshots, err = retrieveNativeSnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
d.Printf("\t<error concluding native snapshot info: %v>\n", err)
return
}
csiSnapshots, err = retrieveCSISnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath)
csiSnapshots, err = retrieveCSISnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
d.Printf("\t<error concluding CSI snapshot info: %v>\n", err)
return
@@ -493,7 +518,7 @@ func describeBackupVolumes(
describePodVolumeBackups(d, details, podVolumeBackupCRs)
}
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string, bslCACert string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
nativeSnapshots := []*volume.BackupVolumeInfo{}
@@ -502,7 +527,7 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeSnapshots, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
return nativeSnapshots, errors.Wrapf(err, "error to download native snapshot info")
}
@@ -531,7 +556,7 @@ func retrieveNativeSnapshotLegacy(ctx context.Context, kbClient kbclient.Client,
return nativeSnapshots, nil
}
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) ([]*volume.BackupVolumeInfo, error) {
func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string, bslCACert string) ([]*volume.BackupVolumeInfo, error) {
status := backup.Status
csiSnapshots := []*volume.BackupVolumeInfo{}
@@ -540,7 +565,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
}
vsBuf := new(bytes.Buffer)
err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindCSIBackupVolumeSnapshots, vsBuf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindCSIBackupVolumeSnapshots, vsBuf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
return csiSnapshots, errors.Wrapf(err, "error to download vs list")
}
@@ -551,7 +576,7 @@ func retrieveCSISnapshotLegacy(ctx context.Context, kbClient kbclient.Client, ba
}
vscBuf := new(bytes.Buffer)
err = downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindCSIBackupVolumeSnapshotContents, vscBuf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err = downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindCSIBackupVolumeSnapshotContents, vscBuf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
return csiSnapshots, errors.Wrapf(err, "error to download vsc list")
}
@@ -901,12 +926,20 @@ func DescribeBackupResults(ctx context.Context, kbClient kbclient.Client, d *Des
return
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
var buf bytes.Buffer
var resultMap map[string]results.Result
// If err 'ErrNotFound' occurs, it means the backup bundle in the bucket has already been there before the backup-result file is introduced.
// We only display the count of errors and warnings in this case.
err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err = downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err == downloadrequest.ErrNotFound {
d.Printf("Errors:\t%d\n", backup.Status.Errors)
d.Printf("Warnings:\t%d\n", backup.Status.Warnings)

View File

@@ -30,6 +30,7 @@ import (
"github.com/vmware-tanzu/velero/internal/volume"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
"github.com/vmware-tanzu/velero/pkg/util/results"
)
@@ -272,11 +273,19 @@ func DescribeBackupStatusInSF(ctx context.Context, kbClient kbclient.Client, d *
}
func describeBackupResourceListInSF(ctx context.Context, kbClient kbclient.Client, backupStatusInfo map[string]any, backup *velerov1api.Backup, insecureSkipTLSVerify bool, caCertPath string) {
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
backupStatusInfo["warningGettingBSLCACert"] = fmt.Sprintf("Warning: Error getting cacert from BSL: %v", err)
bslCACert = ""
}
// In consideration of decoding structured output conveniently, the two separate fields were created here(in func describeBackupResourceList, there is only one field describing either error message or resource list)
// the field of 'errorGettingResourceList' gives specific error message when it fails to get resources list
// the field of 'resourceList' lists the rearranged resources
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
if err == downloadrequest.ErrNotFound {
// the backup resource list could be missing if (other reasons may exist as well):
// - the backup was taken prior to v1.1; or
@@ -302,20 +311,28 @@ func describeBackupVolumesInSF(ctx context.Context, kbClient kbclient.Client, ba
insecureSkipTLSVerify bool, caCertPath string, podVolumeBackupCRs []velerov1api.PodVolumeBackup, backupStatusInfo map[string]any) {
backupVolumes := make(map[string]any)
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
backupVolumes["warningGettingBSLCACert"] = fmt.Sprintf("Warning: Error getting cacert from BSL: %v", err)
bslCACert = ""
}
nativeSnapshots := []*volume.BackupVolumeInfo{}
csiSnapshots := []*volume.BackupVolumeInfo{}
legacyInfoSource := false
buf := new(bytes.Buffer)
err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err = downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupVolumeInfos, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err == downloadrequest.ErrNotFound {
nativeSnapshots, err = retrieveNativeSnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath)
nativeSnapshots, err = retrieveNativeSnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
backupVolumes["errorConcludeNativeSnapshot"] = fmt.Sprintf("error concluding native snapshot info: %v", err)
return
}
csiSnapshots, err = retrieveCSISnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath)
csiSnapshots, err = retrieveCSISnapshotLegacy(ctx, kbClient, backup, insecureSkipTLSVerify, caCertPath, bslCACert)
if err != nil {
backupVolumes["errorConcludeCSISnapshot"] = fmt.Sprintf("error concluding CSI snapshot info: %v", err)
return
@@ -538,6 +555,16 @@ func DescribeBackupResultsInSF(ctx context.Context, kbClient kbclient.Client, d
return
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromBackup(ctx, kbClient, backup.Namespace, backup)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
warnings := make(map[string]any)
warnings["warningGettingBSLCACert"] = fmt.Sprintf("Warning: Error getting cacert from BSL: %v", err)
d.Describe("warningsGettingBSLCACert", warnings)
bslCACert = ""
}
var buf bytes.Buffer
var resultMap map[string]results.Result
@@ -549,7 +576,7 @@ func DescribeBackupResultsInSF(ctx context.Context, kbClient kbclient.Client, d
// If 'ErrNotFound' occurs, it means the backup bundle in the bucket has already been there before the backup-result file is introduced.
// We only display the count of errors and warnings in this case.
err := downloadrequest.Stream(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath)
err = downloadrequest.StreamWithBSLCACert(ctx, kbClient, backup.Namespace, backup.Name, velerov1api.DownloadTargetKindBackupResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert)
if err == downloadrequest.ErrNotFound {
errors["count"] = backup.Status.Errors
warnings["count"] = backup.Status.Warnings

View File

@@ -34,6 +34,7 @@ import (
"github.com/fatih/color"
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/cmd/util/cacert"
"github.com/vmware-tanzu/velero/pkg/cmd/util/downloadrequest"
"github.com/vmware-tanzu/velero/pkg/itemoperation"
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
@@ -179,9 +180,17 @@ func DescribeRestore(
describePodVolumeRestores(d, podVolumeRestores, details)
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromRestore(ctx, kbClient, restore.Namespace, restore)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreVolumeInfo,
buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertFile); err == nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreVolumeInfo,
buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertFile, bslCACert); err == nil {
var restoreVolInfo []volume.RestoreVolumeInfo
if err := json.NewDecoder(buf).Decode(&restoreVolInfo); err != nil {
d.Printf("\t<error reading restore volume info: %v>\n", err)
@@ -250,8 +259,16 @@ func describeRestoreItemOperations(ctx context.Context, kbClient kbclient.Client
return
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromRestore(ctx, kbClient, restore.Namespace, restore)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreItemOperations, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreItemOperations, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
d.Printf("Restore Item Operations:\t<error getting operation info: %v>\n", err)
return
}
@@ -274,10 +291,18 @@ func describeRestoreResults(ctx context.Context, kbClient kbclient.Client, d *De
return
}
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromRestore(ctx, kbClient, restore.Namespace, restore)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
var buf bytes.Buffer
var resultMap map[string]results.Result
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResults, &buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
d.Printf("Warnings:\t<error getting warnings: %v>\n\nErrors:\t<error getting errors: %v>\n", err, err)
return
}
@@ -463,8 +488,16 @@ func groupRestoresByPhase(restores []velerov1api.PodVolumeRestore) map[string][]
}
func describeRestoreResourceList(ctx context.Context, kbClient kbclient.Client, d *Describer, restore *velerov1api.Restore, insecureSkipTLSVerify bool, caCertPath string) {
// Get BSL cacert if available
bslCACert, err := cacert.GetCACertFromRestore(ctx, kbClient, restore.Namespace, restore)
if err != nil {
// Log the error but don't fail - we can still try to download without the BSL cacert
d.Printf("WARNING: Error getting cacert from BSL: %v\n", err)
bslCACert = ""
}
buf := new(bytes.Buffer)
if err := downloadrequest.Stream(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath); err != nil {
if err := downloadrequest.StreamWithBSLCACert(ctx, kbClient, restore.Namespace, restore.Name, velerov1api.DownloadTargetKindRestoreResourceList, buf, downloadRequestTimeout, insecureSkipTLSVerify, caCertPath, bslCACert); err != nil {
if err == downloadrequest.ErrNotFound {
d.Println("Resource List:\t<restore resource list not found>")
} else {

View File

@@ -30,6 +30,27 @@ spec:
profile: "default"
```
### Example with self-signed certificate
When using object storage with self-signed certificates, you can specify the CA certificate:
```yaml
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: default
namespace: velero
spec:
provider: aws
objectStorage:
bucket: velero-backups
# Base64 encoded CA certificate
caCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUR1VENDQXFHZ0F3SUJBZ0lVTWRiWkNaYnBhcE9lYThDR0NMQnhhY3dVa213d0RRWUpLb1pJaHZjTkFRRUwKQlFBd2JERUxNQWtHQTFVRUJoTUNWVk14RXpBUkJnTlZCQWdNQ2tOaGJHbG1iM0p1YVdFeEZqQVVCZ05WQkFjTQpEVk5oYmlCR2NtRnVZMmx6WTI4eEdEQVdCZ05WQkFvTUQwVjRZVzF3YkdVZ1EyOXRjR0Z1ZVRFV01CUUdBMVVFCkF3d05aWGhoYlhCc1pTNXNiMk5oYkRBZUZ3MHlNekEzTVRBeE9UVXlNVGhhRncweU5EQTNNRGt4T1RVeU1UaGEKTUd3eEN6QUpCZ05WQkFZVEFsVlRNUk13RVFZRFZRUUNEQXBEWEJ4cG1iM0p1YVdFeEZqQVVCZ05WQkFjTURWTmgKYmlCR2NtRnVZMmx6WTI4eEdEQVdCZ05WQkFvTUQwVjRZVzF3YkdVZ1EyOXRjR0Z1ZVRFV01CUUdBMVVFQXd3TgpaWGhoYlhCc1pTNXNiMk5oYkRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBS1dqCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
config:
region: us-east-1
s3Url: https://minio.example.com
```
### Parameter Reference
The configurable parameters are as follows:

View File

@@ -25,19 +25,39 @@ that storage provider when backing up and restoring.
## Trusting a self-signed certificate with the Velero client
To use the describe, download, or logs commands to access a backup or restore contained
in storage secured by a self-signed certificate as in the above example, you must use
the `--cacert` flag to provide a path to the certificate to be trusted.
When using Velero client commands like describe, download, or logs to access backups or restores
in storage secured by a self-signed certificate, the CA certificate can be configured in two ways:
```bash
velero backup describe my-backup --cacert <PATH_TO_CA_BUNDLE>
```
1. **Using the `--cacert` flag** (legacy method):
```bash
velero backup describe my-backup --cacert <PATH_TO_CA_BUNDLE>
```
2. **Configuring the CA certificate in the BackupStorageLocation**:
```yaml
apiVersion: velero.io/v1
kind: BackupStorageLocation
metadata:
name: default
namespace: velero
spec:
provider: aws
objectStorage:
bucket: velero-backups
caCert: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi4uLiAoYmFzZTY0IGVuY29kZWQgY2VydGlmaWNhdGUgY29udGVudCkgLi4uCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
config:
region: us-east-1
```
When the CA certificate is configured in the BackupStorageLocation, Velero client commands will automatically use it without requiring the `--cacert` flag.
## Error with client certificate with custom S3 server
In case you are using a custom S3-compatible server, you may encounter that the backup fails with an error similar to one below.
```
```text
rpc error: code = Unknown desc = RequestError: send request failed caused by:
Get https://minio.com:3000/k8s-backup-bucket?delimiter=%2F&list-type=2&prefix=: remote error: tls: alert(116)
```
@@ -47,7 +67,6 @@ Velero as a client does not include its certificate while performing SSL handsha
From [TLS 1.3 spec](https://tools.ietf.org/html/rfc8446), verifying client certificate is optional on the server.
You will need to change this setting on the server to make it work.
## Skipping TLS verification
**Note:** The `--insecure-skip-tls-verify` flag is insecure and susceptible to man-in-the-middle attacks and meant to help your testing and developing scenarios in an on-premises environment. Using this flag in production is not recommended.