CLI automatically discovers and uses cacert from BSL for download requests

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>

feat: Add CA cert fallback when caCertFile fails in download requests

- Fallback to BSL cert when caCertFile cannot be opened
- Combine certificate handling blocks to reuse CA pool initialization
- Add comprehensive unit tests for fallback behavior

This improves robustness by allowing downloads to proceed with BSL CA cert
when the provided CA cert file is unavailable or unreadable.

🤖 Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tiger Kaovilai
2024-12-25 12:00:44 +07:00
parent bd3aa00b29
commit f4233c0f9f
16 changed files with 2255 additions and 58 deletions

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 {