Skip to content

Commit ce23a35

Browse files
craig[bot]sravotto
andcommitted
Merge #147914
147914: cloud/amazon: add option to skip TLS verification for backup/restore r=sravotto a=sravotto Previously, backup/restore jobs running against secure, on-premises S3 object stores that use self signed certificate would fail, unless cloudstorage.http.custom_ca is set. However, customers want the ability to bypass the certificate validation in test environments. To address this, we added a new URL parameter (AWS_SKIP_TLS_VERIFY) that can be set in the backup URL to bypass certificate validation. Note that a CA certificate is still required; this parameter means that the client will not verify the certificate. Warning: Use this query parameter with caution, as it creates MITM vulnerabilities. This change also adds a test case in roachtest for verification against a microCeph cluster. Epic: none Fixes: 147910 Release note: None Co-authored-by: Silvano Ravotto <[email protected]>
2 parents a8729b9 + 5e3ce13 commit ce23a35

File tree

10 files changed

+221
-61
lines changed

10 files changed

+221
-61
lines changed

pkg/cloud/amazon/aws_kms.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,11 @@ func MakeAWSKMS(ctx context.Context, uri string, env cloud.KMSEnv) (cloud.KMS, e
144144
return nil, errors.New(
145145
"custom endpoints disallowed for aws kms due to --aws-kms-disable-http flag")
146146
}
147-
client, err := cloud.MakeHTTPClient(env.ClusterSettings(), cloud.NilMetrics, "aws", "KMS", "")
147+
client, err := cloud.MakeHTTPClient(env.ClusterSettings(), cloud.NilMetrics,
148+
cloud.HTTPClientConfig{
149+
Bucket: "KMS",
150+
Cloud: "aws",
151+
})
148152
if err != nil {
149153
return nil, err
150154
}

pkg/cloud/amazon/s3_storage.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const (
5959
AWSUsePathStyle = "AWS_USE_PATH_STYLE"
6060
// AWSSkipChecksumParam is the query parameter for SkipChecksum in S3 options.
6161
AWSSkipChecksumParam = "AWS_SKIP_CHECKSUM"
62+
// AWSSkipTLSVerify is the query parameter for skipping certificate verification.
63+
AWSSkipTLSVerify = "AWS_SKIP_TLS_VERIFY"
6264

6365
// AWSServerSideEncryptionMode is the query parameter in an AWS URI, for the
6466
// mode to be used for server side encryption. It can either be AES256 or
@@ -87,6 +89,9 @@ const (
8789
scheme = "s3"
8890

8991
checksumAlgorithm = types.ChecksumAlgorithmSha256
92+
93+
// TODO(yevgeniy): Revisit retry logic. Retrying 10 times seems arbitrary.
94+
defaultRetryMaxAttempts = 10
9095
)
9196

9297
// NightlyEnvVarS3Params maps param keys that get added to an S3
@@ -120,6 +125,16 @@ type s3Storage struct {
120125
cached *s3Client
121126
}
122127

128+
// retryMaxAttempts defines how many times we will retry a
129+
// S3 request on a retriable error.
130+
var retryMaxAttempts = defaultRetryMaxAttempts
131+
132+
// InjectTestingRetryMaxAttempts is used to change the
133+
// default retries for tests that need quick fail.
134+
func InjectTestingRetryMaxAttempts(maxAttempts int) {
135+
retryMaxAttempts = maxAttempts
136+
}
137+
123138
// customRetryer implements the `request.Retryer` interface and allows for
124139
// customization of the retry behaviour of an AWS client.
125140
type customRetryer struct{}
@@ -202,7 +217,8 @@ type s3ClientConfig struct {
202217
assumeRoleProvider roleProvider
203218
delegateRoleProviders []roleProvider
204219

205-
skipChecksum bool
220+
skipChecksum bool
221+
skipTLSVerify bool
206222
// log.V(2) decides session init params so include it in key.
207223
verbose bool
208224
}
@@ -236,6 +252,7 @@ func clientConfig(conf *cloudpb.ExternalStorage_S3) s3ClientConfig {
236252
endpoint: conf.Endpoint,
237253
usePathStyle: conf.UsePathStyle,
238254
skipChecksum: conf.SkipChecksum,
255+
skipTLSVerify: conf.SkipTLSVerify,
239256
region: conf.Region,
240257
bucket: conf.Bucket,
241258
accessKey: conf.AccessKey,
@@ -341,7 +358,15 @@ func parseS3URL(uri *url.URL) (cloudpb.ExternalStorage, error) {
341358
return cloudpb.ExternalStorage{}, errors.Wrapf(err, "cannot parse %s as bool", AWSSkipChecksumParam)
342359
}
343360
}
344-
361+
skipTLSVerifyStr := s3URL.ConsumeParam(AWSSkipTLSVerify)
362+
skipTLSVerifyBool := false
363+
if skipTLSVerifyStr != "" {
364+
var err error
365+
skipTLSVerifyBool, err = strconv.ParseBool(skipTLSVerifyStr)
366+
if err != nil {
367+
return cloudpb.ExternalStorage{}, errors.Wrapf(err, "cannot parse %s as bool", AWSSkipTLSVerify)
368+
}
369+
}
345370
conf.S3Config = &cloudpb.ExternalStorage_S3{
346371
Bucket: s3URL.Host,
347372
Prefix: s3URL.Path,
@@ -351,6 +376,7 @@ func parseS3URL(uri *url.URL) (cloudpb.ExternalStorage, error) {
351376
Endpoint: s3URL.ConsumeParam(AWSEndpointParam),
352377
UsePathStyle: pathStyleBool,
353378
SkipChecksum: skipChecksumBool,
379+
SkipTLSVerify: skipTLSVerifyBool,
354380
Region: s3URL.ConsumeParam(S3RegionParam),
355381
Auth: s3URL.ConsumeParam(cloud.AuthParam),
356382
ServerEncMode: s3URL.ConsumeParam(AWSServerSideEncryptionMode),
@@ -573,21 +599,24 @@ func (s *s3Storage) newClient(ctx context.Context) (s3Client, string, error) {
573599
loadOptions = append(loadOptions, option)
574600
}
575601

576-
client, err := cloud.MakeHTTPClient(s.settings, s.metrics, "aws", s.opts.bucket, s.storageOptions.ClientName)
602+
client, err := cloud.MakeHTTPClient(s.settings, s.metrics,
603+
cloud.HTTPClientConfig{
604+
Bucket: s.opts.bucket,
605+
Client: s.storageOptions.ClientName,
606+
Cloud: "aws",
607+
InsecureSkipVerify: s.opts.skipTLSVerify,
608+
})
577609
if err != nil {
578610
return s3Client{}, "", err
579611
}
580612
addLoadOption(config.WithHTTPClient(client))
581613

582-
// TODO(yevgeniy): Revisit retry logic. Retrying 10 times seems arbitrary.
583-
retryMaxAttempts := 10
584614
addLoadOption(config.WithRetryMaxAttempts(retryMaxAttempts))
585615

586616
addLoadOption(config.WithLogger(newLogAdapter(ctx)))
587617
if s.opts.verbose {
588618
addLoadOption(config.WithClientLogMode(awsVerboseLogging))
589619
}
590-
591620
config.WithRetryer(func() aws.Retryer {
592621
return retry.NewStandard(func(opts *retry.StandardOptions) {
593622
opts.MaxAttempts = retryMaxAttempts

pkg/cloud/amazon/s3_storage_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,70 @@ func TestPutS3Endpoint(t *testing.T) {
442442
t.Fatal(err)
443443
}
444444
})
445+
446+
t.Run("skip-tls-verify", func(t *testing.T) {
447+
ctx := context.Background()
448+
srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
449+
defer srv.Close()
450+
q := make(url.Values)
451+
// Convert IP address to `localhost` to exercise the DNS-defining path in the S3 client.
452+
localhostURL := strings.Replace(srv.URL, "127.0.0.1", "localhost", -1)
453+
q.Add(AWSEndpointParam, localhostURL)
454+
q.Add(AWSAccessKeyParam, "key")
455+
q.Add(AWSSecretParam, "secret")
456+
q.Add(S3RegionParam, "region")
457+
q.Add(AWSUsePathStyle, "true")
458+
459+
// Verify that it fails without the flag.
460+
u := url.URL{
461+
Scheme: "s3",
462+
Host: "bucket",
463+
Path: "subdir1/subdir2",
464+
RawQuery: q.Encode(),
465+
}
466+
467+
user := username.RootUserName()
468+
ioConf := base.ExternalIODirConfig{}
469+
470+
conf, err := cloud.ExternalStorageConfFromURI(u.String(), user)
471+
if err != nil {
472+
t.Fatal(err)
473+
}
474+
475+
// Setup a sink for the given args.
476+
testSettings := cluster.MakeTestingClusterSettings()
477+
clientFactory := blobs.TestBlobServiceClient("")
478+
// Force to fail quickly.
479+
InjectTestingRetryMaxAttempts(1)
480+
storage, err := cloud.MakeExternalStorage(ctx, conf, ioConf, testSettings, clientFactory,
481+
nil, nil, cloud.NilMetrics)
482+
if err != nil {
483+
t.Fatal(err)
484+
}
485+
defer storage.Close()
486+
_, _, err = storage.ReadFile(ctx, "test file", cloud.ReadOptions{NoFileSize: true})
487+
require.Error(t, err)
488+
489+
// Set AWSSkipTLSVerify to use with HTTPS Server with self signed certificates.
490+
q.Add(AWSSkipTLSVerify, "true")
491+
u.RawQuery = q.Encode()
492+
493+
confWithSkipVerify, err := cloud.ExternalStorageConfFromURI(u.String(), user)
494+
if err != nil {
495+
t.Fatal(err)
496+
}
497+
storageWithSkipVerify, err := cloud.MakeExternalStorage(ctx, confWithSkipVerify, ioConf, testSettings, clientFactory,
498+
nil, nil, cloud.NilMetrics)
499+
if err != nil {
500+
t.Fatal(err)
501+
}
502+
defer storageWithSkipVerify.Close()
503+
require.True(t, storageWithSkipVerify.Conf().S3Config.SkipTLSVerify)
504+
_, _, err = storageWithSkipVerify.ReadFile(ctx, "another test file", cloud.ReadOptions{NoFileSize: true})
505+
if err != nil {
506+
t.Fatal(err)
507+
}
508+
})
445509
}
446510

447511
func TestS3DisallowCustomEndpoints(t *testing.T) {

pkg/cloud/azure/azure_storage.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,12 @@ func makeAzureStorage(
221221
}
222222

223223
options := args.ExternalStorageOptions()
224-
t, err := cloud.MakeHTTPClient(args.Settings, args.MetricsRecorder, "azure", dest.AzureConfig.Container, options.ClientName)
224+
t, err := cloud.MakeHTTPClient(args.Settings, args.MetricsRecorder,
225+
cloud.HTTPClientConfig{
226+
Bucket: dest.AzureConfig.Container,
227+
Client: options.ClientName,
228+
Cloud: "azure",
229+
})
225230
if err != nil {
226231
return nil, errors.Wrap(err, "azure: unable to create transport")
227232
}

pkg/cloud/cloud_io.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ import (
2828
"github.com/cockroachdb/errors"
2929
)
3030

31+
// HTTPClientConfig defines the settings for building the underlying transport.
32+
type HTTPClientConfig struct {
33+
// Bucket is name of the bucket. Used to label metrics.
34+
Bucket string
35+
// Client type (e.g. CDC vs backup). Used to label metrics.
36+
Client string
37+
// Cloud is the storage provider. Used to label metrics.
38+
Cloud string
39+
// InsecureSkipVerify controls whether a client verifies the server's
40+
// certificate chain and host name. If InsecureSkipVerify is true, crypto/tls
41+
// accepts any certificate presented by the server and any host name in that
42+
// certificate. In this mode, TLS is susceptible to machine-in-the-middle attacks.
43+
InsecureSkipVerify bool
44+
}
45+
3146
// Timeout is a cluster setting used for cloud storage interactions.
3247
var Timeout = settings.RegisterDurationSetting(
3348
settings.ApplicationLevel,
@@ -77,9 +92,9 @@ var httpMetrics = settings.RegisterBoolSetting(
7792
// MakeHTTPClient makes an http client configured with the common settings used
7893
// for interacting with cloud storage (timeouts, retries, CA certs, etc).
7994
func MakeHTTPClient(
80-
settings *cluster.Settings, metrics *Metrics, cloud, bucket, client string,
95+
settings *cluster.Settings, metrics *Metrics, config HTTPClientConfig,
8196
) (*http.Client, error) {
82-
t, err := MakeTransport(settings, metrics, cloud, bucket, client)
97+
t, err := MakeTransport(settings, metrics, config)
8398
if err != nil {
8499
return nil, err
85100
}
@@ -99,10 +114,12 @@ func MakeHTTPClientForTransport(t http.RoundTripper) (*http.Client, error) {
99114
// used for interacting with cloud storage (timeouts, retries, CA certs, etc).
100115
// Prefer MakeHTTPClient where possible.
101116
func MakeTransport(
102-
settings *cluster.Settings, metrics *Metrics, cloud, bucket, client string,
117+
settings *cluster.Settings, metrics *Metrics, config HTTPClientConfig,
103118
) (*http.Transport, error) {
104119
var tlsConf *tls.Config
105-
if pem := httpCustomCA.Get(&settings.SV); pem != "" {
120+
if config.InsecureSkipVerify {
121+
tlsConf = &tls.Config{InsecureSkipVerify: true}
122+
} else if pem := httpCustomCA.Get(&settings.SV); pem != "" {
106123
roots, err := x509.SystemCertPool()
107124
if err != nil {
108125
return nil, errors.Wrap(err, "could not load system root CA pool")
@@ -121,7 +138,7 @@ func MakeTransport(
121138
// most bulk jobs.
122139
t.MaxIdleConnsPerHost = 64
123140
if metrics != nil {
124-
t.DialContext = metrics.NetMetrics.Wrap(t.DialContext, cloud, bucket, client)
141+
t.DialContext = metrics.NetMetrics.Wrap(t.DialContext, config.Cloud, config.Bucket, config.Client)
125142
}
126143
return t, nil
127144
}

pkg/cloud/cloudpb/external_storage.proto

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ message ExternalStorage {
6363
string temp_token = 5;
6464
string endpoint = 6;
6565
bool use_path_style=16;
66-
bool skip_checksum=17;
66+
bool skip_checksum=17;
67+
bool skip_tls_verify=18 [(gogoproto.customname) = "SkipTLSVerify"];
6768
string region = 7;
6869
string auth = 8;
6970
string server_enc_mode = 9;
@@ -93,7 +94,7 @@ message ExternalStorage {
9394
// list so that the role specified in AssumeRoleProvider can be assumed.
9495
repeated AssumeRoleProvider delegate_role_providers = 15 [(gogoproto.nullable) = false];
9596

96-
// Next ID: 18;
97+
// Next ID: 19;
9798
}
9899

99100
message GCS {

pkg/cloud/gcp/gcs_storage.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,11 @@ func makeGCSStorage(
186186
}
187187

188188
clientName := args.ExternalStorageOptions().ClientName
189-
baseTransport, err := cloud.MakeTransport(args.Settings, args.MetricsRecorder, "gcs", conf.Bucket, clientName)
189+
baseTransport, err := cloud.MakeTransport(args.Settings, args.MetricsRecorder, cloud.HTTPClientConfig{
190+
Bucket: conf.Bucket,
191+
Client: clientName,
192+
Cloud: "gcs",
193+
})
190194
if err != nil {
191195
return nil, errors.Wrap(err, "failed to create http transport")
192196
}

pkg/cloud/httpsink/http_storage.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,12 @@ func MakeHTTPStorage(
6767
}
6868

6969
clientName := args.ExternalStorageOptions().ClientName
70-
client, err := cloud.MakeHTTPClient(args.Settings, args.MetricsRecorder, "http", base, clientName)
70+
client, err := cloud.MakeHTTPClient(args.Settings, args.MetricsRecorder,
71+
cloud.HTTPClientConfig{
72+
Cloud: "http",
73+
Bucket: "base",
74+
Client: clientName,
75+
})
7176
if err != nil {
7277
return nil, err
7378
}

0 commit comments

Comments
 (0)