diff --git a/changelogs/unreleased/9768-Joeavaikath b/changelogs/unreleased/9768-Joeavaikath new file mode 100644 index 0000000000..8bf23063c5 --- /dev/null +++ b/changelogs/unreleased/9768-Joeavaikath @@ -0,0 +1 @@ +Inherit insecureSkipTLSVerify from BSL config for CLI downloads diff --git a/pkg/builder/backup_storage_location_builder.go b/pkg/builder/backup_storage_location_builder.go index 33becd2a7f..37aa335b53 100644 --- a/pkg/builder/backup_storage_location_builder.go +++ b/pkg/builder/backup_storage_location_builder.go @@ -137,3 +137,9 @@ func (b *BackupStorageLocationBuilder) Credential(selector *corev1api.SecretKeyS b.object.Spec.Credential = selector return b } + +// Config sets the BackupStorageLocation's provider config. +func (b *BackupStorageLocationBuilder) Config(config map[string]string) *BackupStorageLocationBuilder { + b.object.Spec.Config = config + return b +} diff --git a/pkg/cmd/cli/backup/describe.go b/pkg/cmd/cli/backup/describe.go index b0ef4a93e5..c488d3078e 100644 --- a/pkg/cmd/cli/backup/describe.go +++ b/pkg/cmd/cli/backup/describe.go @@ -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/output" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -93,13 +94,21 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { fmt.Fprintf(os.Stderr, "error getting PodVolumeBackups for backup %s: %v\n", backup.Name, err) } + // Inherit insecureSkipTLSVerify from BSL config if not set via CLI flag + effectiveInsecureSkipTLS := insecureSkipTLSVerify + bslInsecure, bslErr := cacert.GetInsecureSkipTLSVerifyFromBackup(context.Background(), kbClient, f.Namespace(), &backups.Items[i]) + if bslErr != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error getting insecureSkipTLSVerify from BSL for backup %s: %v\n", backup.Name, bslErr) + } + effectiveInsecureSkipTLS = effectiveInsecureSkipTLS || bslInsecure + // structured output only applies to a single backup in case of OOM // To describe the list of backups in structured format, users could iterate over the list and describe backup one after another. if len(backups.Items) == 1 && outputFormat != "plaintext" { - s := output.DescribeBackupInSF(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, details, insecureSkipTLSVerify, caCertFile, outputFormat) + s := output.DescribeBackupInSF(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, details, effectiveInsecureSkipTLS, caCertFile, outputFormat) fmt.Print(s) } else { - s := output.DescribeBackup(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, details, insecureSkipTLSVerify, caCertFile) + s := output.DescribeBackup(context.Background(), kbClient, &backups.Items[i], deleteRequestList.Items, podVolumeBackupList.Items, details, effectiveInsecureSkipTLS, caCertFile) if first { first = false fmt.Print(s) diff --git a/pkg/cmd/cli/backup/download.go b/pkg/cmd/cli/backup/download.go index 8bb973ff00..606b270b70 100644 --- a/pkg/cmd/cli/backup/download.go +++ b/pkg/cmd/cli/backup/download.go @@ -133,13 +133,19 @@ func (o *DownloadOptions) Run(c *cobra.Command, f client.Factory) error { bslCACert = "" } + // Inherit insecureSkipTLSVerify from BSL config if not set via CLI flag + bslInsecure, err := cacert.GetInsecureSkipTLSVerifyFromBackup(context.Background(), kbClient, f.Namespace(), backup) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error getting insecureSkipTLSVerify from BSL: %v\n", err) + } + backupDest, err := os.OpenFile(o.Output, o.writeOptions, 0600) if err != nil { return err } defer backupDest.Close() - err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), o.Name, velerov1api.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify, o.caCertFile, bslCACert) + err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), o.Name, velerov1api.DownloadTargetKindBackupContents, backupDest, o.Timeout, o.InsecureSkipTLSVerify || bslInsecure, o.caCertFile, bslCACert) if err != nil { os.Remove(o.Output) cmd.CheckError(err) diff --git a/pkg/cmd/cli/backup/logs.go b/pkg/cmd/cli/backup/logs.go index a0149acf17..55bf876e63 100644 --- a/pkg/cmd/cli/backup/logs.go +++ b/pkg/cmd/cli/backup/logs.go @@ -86,7 +86,13 @@ func (l *LogsOptions) Run(c *cobra.Command, f client.Factory) error { bslCACert = "" } - err = downloadrequest.StreamWithBSLCACert(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify, l.CaCertFile, bslCACert) + // Inherit insecureSkipTLSVerify from BSL config if not set via CLI flag + bslInsecure, err := cacert.GetInsecureSkipTLSVerifyFromBackup(context.Background(), l.Client, f.Namespace(), backup) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error getting insecureSkipTLSVerify from BSL: %v\n", err) + } + + err = downloadrequest.StreamWithBSLCACert(context.Background(), l.Client, f.Namespace(), l.BackupName, velerov1api.DownloadTargetKindBackupLog, os.Stdout, l.Timeout, l.InsecureSkipTLSVerify || bslInsecure, l.CaCertFile, bslCACert) return err } diff --git a/pkg/cmd/cli/restore/describe.go b/pkg/cmd/cli/restore/describe.go index 6404ef21d3..d46bf7ec6d 100644 --- a/pkg/cmd/cli/restore/describe.go +++ b/pkg/cmd/cli/restore/describe.go @@ -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/output" "github.com/vmware-tanzu/velero/pkg/label" ) @@ -80,7 +81,15 @@ func NewDescribeCommand(f client.Factory, use string) *cobra.Command { fmt.Fprintf(os.Stderr, "error getting PodVolumeRestores for restore %s: %v\n", restore.Name, err) } - s := output.DescribeRestore(context.Background(), kbClient, &restoreList.Items[i], podVolumeRestoreList.Items, details, insecureSkipTLSVerify, caCertFile) + // Inherit insecureSkipTLSVerify from BSL config if not set via CLI flag + effectiveInsecureSkipTLS := insecureSkipTLSVerify + bslInsecure, bslErr := cacert.GetInsecureSkipTLSVerifyFromRestore(context.Background(), kbClient, f.Namespace(), &restoreList.Items[i]) + if bslErr != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error getting insecureSkipTLSVerify from BSL for restore %s: %v\n", restore.Name, bslErr) + } + effectiveInsecureSkipTLS = effectiveInsecureSkipTLS || bslInsecure + + s := output.DescribeRestore(context.Background(), kbClient, &restoreList.Items[i], podVolumeRestoreList.Items, details, effectiveInsecureSkipTLS, caCertFile) if first { first = false fmt.Print(s) diff --git a/pkg/cmd/cli/restore/logs.go b/pkg/cmd/cli/restore/logs.go index f4315c917f..3087c56475 100644 --- a/pkg/cmd/cli/restore/logs.go +++ b/pkg/cmd/cli/restore/logs.go @@ -77,7 +77,13 @@ func NewLogsCommand(f client.Factory) *cobra.Command { bslCACert = "" } - err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify, caCertFile, bslCACert) + // Inherit insecureSkipTLSVerify from BSL config if not set via CLI flag + bslInsecure, err := cacert.GetInsecureSkipTLSVerifyFromRestore(context.Background(), kbClient, f.Namespace(), restore) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error getting insecureSkipTLSVerify from BSL: %v\n", err) + } + + err = downloadrequest.StreamWithBSLCACert(context.Background(), kbClient, f.Namespace(), restoreName, velerov1api.DownloadTargetKindRestoreLog, os.Stdout, timeout, insecureSkipTLSVerify || bslInsecure, caCertFile, bslCACert) cmd.CheckError(err) }, } diff --git a/pkg/cmd/util/cacert/bsl_insecure_tls.go b/pkg/cmd/util/cacert/bsl_insecure_tls.go new file mode 100644 index 0000000000..13a5b48771 --- /dev/null +++ b/pkg/cmd/util/cacert/bsl_insecure_tls.go @@ -0,0 +1,74 @@ +/* +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" + "strings" + + "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" +) + +func GetInsecureSkipTLSVerifyFromBackup(ctx context.Context, client kbclient.Client, namespace string, backup *velerov1api.Backup) (bool, error) { + return GetInsecureSkipTLSVerifyFromBSL(ctx, client, namespace, backup.Spec.StorageLocation) +} + +func GetInsecureSkipTLSVerifyFromRestore(ctx context.Context, client kbclient.Client, namespace string, restore *velerov1api.Restore) (bool, error) { + 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) { + return false, nil + } + return false, errors.Wrapf(err, "error getting backup %s", restore.Spec.BackupName) + } + + return GetInsecureSkipTLSVerifyFromBackup(ctx, client, namespace, backup) +} + +func GetInsecureSkipTLSVerifyFromBSL(ctx context.Context, client kbclient.Client, namespace, bslName string) (bool, error) { + if bslName == "" { + return false, nil + } + + bsl := &velerov1api.BackupStorageLocation{} + key := kbclient.ObjectKey{ + Namespace: namespace, + Name: bslName, + } + + if err := client.Get(ctx, key, bsl); err != nil { + if apierrors.IsNotFound(err) { + return false, nil + } + return false, errors.Wrapf(err, "error getting backup storage location %s", bslName) + } + + if bsl.Spec.Config == nil { + return false, nil + } + + return strings.EqualFold(bsl.Spec.Config["insecureSkipTLSVerify"], "true"), nil +} diff --git a/pkg/cmd/util/cacert/bsl_insecure_tls_test.go b/pkg/cmd/util/cacert/bsl_insecure_tls_test.go new file mode 100644 index 0000000000..c14d090c57 --- /dev/null +++ b/pkg/cmd/util/cacert/bsl_insecure_tls_test.go @@ -0,0 +1,270 @@ +/* +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" + "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 TestGetInsecureSkipTLSVerifyFromBSL(t *testing.T) { + testCases := []struct { + name string + bslName string + bsl *velerov1api.BackupStorageLocation + expected bool + }{ + { + name: "BSL with insecureSkipTLSVerify true", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"insecureSkipTLSVerify": "true"}). + Result(), + expected: true, + }, + { + name: "BSL with insecureSkipTLSVerify false", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"insecureSkipTLSVerify": "false"}). + Result(), + expected: false, + }, + { + name: "BSL with insecureSkipTLSVerify True (case insensitive)", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"insecureSkipTLSVerify": "True"}). + Result(), + expected: true, + }, + { + name: "BSL with insecureSkipTLSVerify TRUE (case insensitive)", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"insecureSkipTLSVerify": "TRUE"}). + Result(), + expected: true, + }, + { + name: "BSL with config missing the key", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"region": "us-east-1"}). + Result(), + expected: false, + }, + { + name: "BSL with nil config", + bslName: "test-bsl", + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Result(), + expected: false, + }, + { + name: "empty BSL name", + bslName: "", + bsl: nil, + expected: false, + }, + { + name: "BSL not found", + bslName: "missing-bsl", + bsl: nil, + expected: 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() + + result, err := GetInsecureSkipTLSVerifyFromBSL(t.Context(), fakeClient, "test-ns", tc.bslName) + + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestGetInsecureSkipTLSVerifyFromBackup(t *testing.T) { + testCases := []struct { + name string + backup *velerov1api.Backup + bsl *velerov1api.BackupStorageLocation + expected bool + }{ + { + name: "backup with BSL having insecureSkipTLSVerify true", + backup: builder.ForBackup("test-ns", "test-backup"). + StorageLocation("test-bsl"). + Result(), + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Config(map[string]string{"insecureSkipTLSVerify": "true"}). + Result(), + expected: true, + }, + { + name: "backup with BSL without insecureSkipTLSVerify", + backup: builder.ForBackup("test-ns", "test-backup"). + StorageLocation("test-bsl"). + Result(), + bsl: builder.ForBackupStorageLocation("test-ns", "test-bsl"). + Provider("aws"). + Bucket("test-bucket"). + Result(), + expected: false, + }, + { + name: "backup without storage location", + backup: builder.ForBackup("test-ns", "test-backup"). + Result(), + bsl: nil, + expected: false, + }, + { + name: "BSL not found", + backup: builder.ForBackup("test-ns", "test-backup"). + StorageLocation("missing-bsl"). + Result(), + bsl: nil, + expected: 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() + + result, err := GetInsecureSkipTLSVerifyFromBackup(t.Context(), fakeClient, "test-ns", tc.backup) + + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } +} + +func TestGetInsecureSkipTLSVerifyFromRestore(t *testing.T) { + testCases := []struct { + name string + restore *velerov1api.Restore + backup *velerov1api.Backup + bsl *velerov1api.BackupStorageLocation + expected bool + }{ + { + name: "restore with backup having BSL with insecureSkipTLSVerify true", + 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"). + Config(map[string]string{"insecureSkipTLSVerify": "true"}). + Result(), + expected: true, + }, + { + name: "restore with missing backup", + restore: builder.ForRestore("test-ns", "test-restore"). + Backup("missing-backup"). + Result(), + backup: nil, + bsl: nil, + expected: false, + }, + { + name: "restore with backup having BSL without insecureSkipTLSVerify", + 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(), + expected: 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() + + result, err := GetInsecureSkipTLSVerifyFromRestore(t.Context(), fakeClient, "test-ns", tc.restore) + + require.NoError(t, err) + assert.Equal(t, tc.expected, result) + }) + } +}