diff --git a/pkg/repository/provider/unified_repo.go b/pkg/repository/provider/unified_repo.go index f684cc4986..51b9ef2873 100644 --- a/pkg/repository/provider/unified_repo.go +++ b/pkg/repository/provider/unified_repo.go @@ -498,10 +498,6 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo result[udmrepo.StoreOptionS3Endpoint] = strings.Trim(s3URL, "/") result[udmrepo.StoreOptionS3DisableTLSVerify] = config["insecureSkipTLSVerify"] result[udmrepo.StoreOptionS3DisableTLS] = strconv.FormatBool(disableTLS) - - if backupLocation.Spec.ObjectStorage != nil && backupLocation.Spec.ObjectStorage.CACert != nil { - result[udmrepo.StoreOptionS3CustomCA] = base64.StdEncoding.EncodeToString(backupLocation.Spec.ObjectStorage.CACert) - } } else if backendType == repoconfig.AzureBackend { for k, v := range config { result[k] = v @@ -510,6 +506,9 @@ func getStorageVariables(backupLocation *velerov1api.BackupStorageLocation, repo result[udmrepo.StoreOptionOssBucket] = bucket result[udmrepo.StoreOptionPrefix] = prefix + if backupLocation.Spec.ObjectStorage != nil && backupLocation.Spec.ObjectStorage.CACert != nil { + result[udmrepo.StoreOptionCACert] = base64.StdEncoding.EncodeToString(backupLocation.Spec.ObjectStorage.CACert) + } result[udmrepo.StoreOptionOssRegion] = strings.Trim(region, "/") result[udmrepo.StoreOptionFsPath] = config["fspath"] diff --git a/pkg/repository/provider/unified_repo_test.go b/pkg/repository/provider/unified_repo_test.go index 0910ef841c..ff6c31b4a7 100644 --- a/pkg/repository/provider/unified_repo_test.go +++ b/pkg/repository/provider/unified_repo_test.go @@ -384,7 +384,7 @@ func TestGetStorageVariables(t *testing.T) { "endpoint": "fake-url", "doNotUseTLS": "false", "skipTLSVerify": "false", - "customCA": base64.StdEncoding.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04, 0x05}), + "caCert": base64.StdEncoding.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04, 0x05}), }, }, { diff --git a/pkg/repository/udmrepo/kopialib/backend/azure.go b/pkg/repository/udmrepo/kopialib/backend/azure.go index ddf0e4d925..5cb07e95a6 100644 --- a/pkg/repository/udmrepo/kopialib/backend/azure.go +++ b/pkg/repository/udmrepo/kopialib/backend/azure.go @@ -18,9 +18,12 @@ package backend import ( "context" + "encoding/base64" "github.com/kopia/kopia/repo/blob" + "github.com/pkg/errors" + "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo/kopialib/backend/azure" ) @@ -29,6 +32,16 @@ type AzureBackend struct { } func (c *AzureBackend) Setup(ctx context.Context, flags map[string]string) error { + // As pkg/util/azure.NewStorageClient(config) is used in both repository and plugin, + // the caCert isn't encoded when passing to the plugin, so we need to decode the caCert + // before passing the config into the NewStorageClient() + if flags[udmrepo.StoreOptionCACert] != "" { + caCert, err := base64.StdEncoding.DecodeString(flags[udmrepo.StoreOptionCACert]) + if err != nil { + return errors.Wrapf(err, "failed to decode the CA cert") + } + flags[udmrepo.StoreOptionCACert] = string(caCert) + } c.option = azure.Option{ Config: flags, Limits: setupLimits(ctx, flags), diff --git a/pkg/repository/udmrepo/kopialib/backend/azure/azure_storage_wrapper.go b/pkg/repository/udmrepo/kopialib/backend/azure/azure_storage_wrapper.go index 5e855611c2..046b0459a3 100644 --- a/pkg/repository/udmrepo/kopialib/backend/azure/azure_storage_wrapper.go +++ b/pkg/repository/udmrepo/kopialib/backend/azure/azure_storage_wrapper.go @@ -20,10 +20,10 @@ import ( "context" "github.com/kopia/kopia/repo/blob" + "github.com/kopia/kopia/repo/blob/azure" "github.com/kopia/kopia/repo/blob/throttling" "github.com/sirupsen/logrus" - "github.com/kopia/kopia/repo/blob/azure" "github.com/vmware-tanzu/velero/pkg/repository/udmrepo" azureutil "github.com/vmware-tanzu/velero/pkg/util/azure" ) diff --git a/pkg/repository/udmrepo/kopialib/backend/s3.go b/pkg/repository/udmrepo/kopialib/backend/s3.go index 43a7fc821b..075be9af19 100644 --- a/pkg/repository/udmrepo/kopialib/backend/s3.go +++ b/pkg/repository/udmrepo/kopialib/backend/s3.go @@ -44,7 +44,7 @@ func (c *S3Backend) Setup(ctx context.Context, flags map[string]string) error { c.options.DoNotUseTLS = optionalHaveBool(ctx, udmrepo.StoreOptionS3DisableTLS, flags) c.options.DoNotVerifyTLS = optionalHaveBool(ctx, udmrepo.StoreOptionS3DisableTLSVerify, flags) c.options.SessionToken = optionalHaveString(udmrepo.StoreOptionS3Token, flags) - c.options.RootCA = optionalHaveBase64(ctx, udmrepo.StoreOptionS3CustomCA, flags) + c.options.RootCA = optionalHaveBase64(ctx, udmrepo.StoreOptionCACert, flags) c.options.Limits = setupLimits(ctx, flags) diff --git a/pkg/repository/udmrepo/kopialib/backend/s3_test.go b/pkg/repository/udmrepo/kopialib/backend/s3_test.go index 43a761688b..df96de36e9 100644 --- a/pkg/repository/udmrepo/kopialib/backend/s3_test.go +++ b/pkg/repository/udmrepo/kopialib/backend/s3_test.go @@ -95,8 +95,8 @@ func TestS3Setup(t *testing.T) { { name: "with wrong ca", flags: map[string]string{ - udmrepo.StoreOptionOssBucket: "fake-bucket", - udmrepo.StoreOptionS3CustomCA: "fake-base-64", + udmrepo.StoreOptionOssBucket: "fake-bucket", + udmrepo.StoreOptionCACert: "fake-base-64", }, expectedOptions: s3.Options{ BucketName: "fake-bucket", @@ -105,8 +105,8 @@ func TestS3Setup(t *testing.T) { { name: "with correct ca", flags: map[string]string{ - udmrepo.StoreOptionOssBucket: "fake-bucket", - udmrepo.StoreOptionS3CustomCA: "ZmFrZS1jYQ==", + udmrepo.StoreOptionOssBucket: "fake-bucket", + udmrepo.StoreOptionCACert: "ZmFrZS1jYQ==", }, expectedOptions: s3.Options{ BucketName: "fake-bucket", diff --git a/pkg/repository/udmrepo/repo_options.go b/pkg/repository/udmrepo/repo_options.go index 09acc49f72..af54e09477 100644 --- a/pkg/repository/udmrepo/repo_options.go +++ b/pkg/repository/udmrepo/repo_options.go @@ -42,7 +42,6 @@ const ( StoreOptionS3Endpoint = "endpoint" StoreOptionS3DisableTLS = "doNotUseTLS" StoreOptionS3DisableTLSVerify = "skipTLSVerify" - StoreOptionS3CustomCA = "customCA" StoreOptionFsPath = "fspath" @@ -50,6 +49,7 @@ const ( StoreOptionOssBucket = "bucket" StoreOptionOssRegion = "region" + StoreOptionCACert = "caCert" StoreOptionCredentialFile = "credFile" StoreOptionPrefix = "prefix" diff --git a/pkg/util/azure/util.go b/pkg/util/azure/util.go index 21c5ef0577..709a989ab0 100644 --- a/pkg/util/azure/util.go +++ b/pkg/util/azure/util.go @@ -17,9 +17,14 @@ limitations under the License. package azure import ( + "crypto/tls" + "crypto/x509" "fmt" + "net" + "net/http" "os" "strings" + "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" @@ -66,13 +71,44 @@ func LoadCredentials(config map[string]string) (map[string]string, error) { // GetClientOptions returns the client options based on the BSL/VSL config and credentials func GetClientOptions(locationCfg, creds map[string]string) (policy.ClientOptions, error) { + options := policy.ClientOptions{} + cloudCfg, err := getCloudConfiguration(locationCfg, creds) if err != nil { - return policy.ClientOptions{}, err + return options, err } - return policy.ClientOptions{ - Cloud: cloudCfg, - }, nil + options.Cloud = cloudCfg + + if locationCfg["caCert"] != "" { + certPool, _ := x509.SystemCertPool() + if certPool == nil { + certPool = x509.NewCertPool() + } + certPool.AppendCertsFromPEM([]byte(locationCfg["caCert"])) + + // https://github.com/Azure/azure-sdk-for-go/blob/sdk/azcore/v1.6.1/sdk/azcore/runtime/transport_default_http_client.go#L19 + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: &tls.Config{ + MinVersion: tls.VersionTLS12, + RootCAs: certPool, + }, + } + options.Transport = &http.Client{ + Transport: transport, + } + } + + return options, nil } // getCloudConfiguration based on the BSL/VSL config and credentials diff --git a/pkg/util/azure/util_test.go b/pkg/util/azure/util_test.go index 28336501ac..e5a92f78cf 100644 --- a/pkg/util/azure/util_test.go +++ b/pkg/util/azure/util_test.go @@ -64,14 +64,26 @@ func TestGetClientOptions(t *testing.T) { _, err := GetClientOptions(bslCfg, creds) require.NotNil(t, err) - // valid + // specify caCert bslCfg = map[string]string{ CredentialKeyCloudName: "", + "caCert": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZTakNDQXpLZ0F3SUJBZ0lVWmcxbzRpWld2bVh5ekJrQ0J6SGdiODZGemtFd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1VqRUxNQWtHQTFVRUJoTUNRMDR4RERBS0JnTlZCQWdNQTFCRlN6RVJNQThHQTFVRUJ3d0lRbVZwSUVwcApibWN4RHpBTkJnTlZCQW9NQmxaTmQyRnlaVEVSTUE4R0ExVUVBd3dJU0dGeVltOXlRMEV3SGhjTk1qTXdPVEEyCk1ESXpOakUyV2hjTk1qUXdPVEExTURJek5qRTJXakJYTVFzd0NRWURWUVFHRXdKRFRqRU1NQW9HQTFVRUNBd0QKVUVWTE1SRXdEd1lEVlFRSERBaENaV2tnU21sdVp6RVBNQTBHQTFVRUNnd0dWazEzWVhKbE1SWXdGQVlEVlFRRApEQTFJWVhKaWIzSk5ZVzVoWjJWeU1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBCnIrK1FHaHYvUnBDUTFIcncrMnYyQWNoaGhUVTVQL3hCd2RIWkZHWWJzMmxGbGtiL3oycEs2Y05ycFZmNUtmdjIKVUNpZEovMjFhZHc2SWNGZWxkSnFudU4rSlJaWXh5S0w0bzdRRGNVSk1sUTZJZk5kbEI0NUNwcGFBZVA4blVVTgo0YUwyV244b094L1pROTd2YmRXeERIR1FqZGR4N3p0Q09PaVZ0SEk4NS9Ka3kydTJnNmVhMklndmh1ZEVPZ3JtCjJzNU8zZlVtdHhSTEhwNnpDbURYZGZFUWg4ZFpndCs1d0RlazRWR2t4Zk81VG1tUHJ0LzBPTnVGYjJMWGVWV0sKeXkzVDFFTGNOSWhPSzQ1amhEejNnb2JhQzAwK0JTdzJMejVocXRxdUQ2RGxmME53TWtBQkt6d1dMZkROOXNrRApVazVYTmZNa2c0L3JhblRIWHlCNUNKSlk1akhORUtBQWlnM2NFSFNvejVjeGJqTE14VDhoMEk3MitEWldmUzFTCjU3Rm1SN2ZTNXk0QUcrU3Z1U3kvbktCUktJS2dQZ0t2Rjl6NktuL2ZPMTNsbk5LbHQwWU5mWFBFV2hZQytmMUoKTWpTOWc5eHBpYkZhM2Q0aHpOeWZhMWJHcUxtbkUwNVNpbWZNMVI1Z21Tamw1Z1FKQlltTHA3dWRLdjFDSUNRSwptQng2WG4vcnJEZHFiMndCRUNRSjBMbUo2SW5SaFZtT0s0WUdFeFRqZ1FRMldSWHYxMnhVK05GYWlZS3cxZkp0CkdaemFQeENxaG5JZXM5cGNPY0FjdmFHVngwSjlFYnRod0ltekdoTjBBREdCOVZaL1dFdHYvN0gwQ2xjOVlyT0gKNnRMb212b2pjQUZnN0xFbXZxeFFEOFFSTzlZZVdTTkgvV1REY1hVb3R5a0NBd0VBQWFNVE1CRXdEd1lEVlIwUgpCQWd3Qm9jRUNycFFxakFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBZnRVdmR1UFMvajhSaWx4ME5aelhSeEY3Ck9HZW9qU2JaQ1ZvamNPdnRNTVlMaFkxdDE2Y2laY1VWMGF4Z2FUWkkvak9WMGJPTEl3dmEvcVB2Z1RmSWZqL0gKVzhiMlNTRVdIUzZPSFFaR1BYNy9zVFVwQzB6QVcva2haN1FWR1BoWEcyK0V1NjFaNE95ejZ5dTRPdi9MYjlMUQpmMU9zTXhwandkbmhxazFKaERxUkpZbGIvZ05TRGZnVlN0YmhHVzVhb3paUlBBMUtqVXVaT3QzR2xQR09Wd3ZLCnpUcFFMdGVTUHNibTJMcUl2ZEg4dlgzK1kwcHIzdEdtdnExbWtIWUhYQTlBZWtYRkVsRHc4dGtZVHdLaEFqblUKZEFjWTFkTis5ODNiMDI0L0JQUXZKQlRTVjd4blEyUnlrUmMrVGxIL3B5RlM1cEtVbUF0aU9qTElxL2ZEMmJVagorTzlxT1hjK0c1b0xEaXlXWDRXSG9XdkZZdTdva1gwT1dGcHFETXFOcHlLUkRzQ1FENXViMEVQaVlVS0hnWEhiCnV3UXVtK0pRRUREdzRXL1kzZktnMW9TWW1XOHJndFNPZmtRQlQ0UnlaTUg2SzN6cFp5dVVsbmJUV0NWeEcyYVoKWVo0T2JpbUFGbVlveGRYdktWdFU0YUdlTjRoaXBvb2dzaXVXKzZYQ3Bqa2pWZlZuUEY4elZVNlZ3anRQVkkzKwpxdWxRNWJLS3lKYng3bk9NNXFob2svSmk2N1pyZDhob3ZwclhhRUdvakNDTVI3MllPWGVuMlB3bVlZZWNkQ2pyCnErSDdHNUV3ZXBoRWxrN3RWRWY4RVV4OEc1Mk9SVEtZMkF1dlRGVlliUC8yaTROS1FlMWdEWWZrWnNzUk1MajEKK0JCQVVJcnFVMnRuUHhwZW4vMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=", } creds = map[string]string{} options, err := GetClientOptions(bslCfg, creds) require.Nil(t, err) assert.Equal(t, options.Cloud, cloud.AzurePublic) + assert.NotNil(t, options.Transport) + + // doesn't specify caCert + bslCfg = map[string]string{ + CredentialKeyCloudName: "", + } + creds = map[string]string{} + options, err = GetClientOptions(bslCfg, creds) + require.Nil(t, err) + assert.Equal(t, options.Cloud, cloud.AzurePublic) + assert.Nil(t, options.Transport) } func Test_getCloudConfiguration(t *testing.T) {