diff --git a/.semgrep/imports.yml b/.semgrep/imports.yml index c9e97538..126987df 100644 --- a/.semgrep/imports.yml +++ b/.semgrep/imports.yml @@ -8,14 +8,14 @@ rules: paths: exclude: - awsv1shim - - tfawserr - awsmocks patterns: - pattern: | import ("$X") + - focus-metavariable: $X - metavariable-regex: metavariable: "$X" - regex: '^"github.com/aws/aws-sdk-go/.+"$' + regex: 'github.com/aws/aws-sdk-go/.+' severity: ERROR - id: no-sdkv2-imports-in-awsv1shim @@ -24,15 +24,17 @@ rules: paths: include: - awsv1shim - - tfawserr patterns: - pattern: | import ("$X") + - focus-metavariable: $X - metavariable-regex: metavariable: "$X" - regex: '^"github.com/aws/aws-sdk-go-v2/.+"$' + regex: 'github.com/aws/aws-sdk-go-v2/.+' - pattern-not: | import ("github.com/aws/aws-sdk-go-v2/aws/transport/http") - pattern-not: | import ("github.com/aws/aws-sdk-go-v2/config") + - pattern-not: | + import ("github.com/aws/aws-sdk-go-v2/aws/retry") severity: ERROR diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 00000000..e69de29b diff --git a/aws_config.go b/aws_config.go index 593ac027..e6565198 100644 --- a/aws_config.go +++ b/aws_config.go @@ -75,6 +75,11 @@ func GetAwsConfig(ctx context.Context, c *Config) (context.Context, aws.Config, } } + c.ValidateProxySettings(&diags) + if diags.HasError() { + return ctx, aws.Config{}, diags + } + logger.Debug(baseCtx, "Resolving credentials provider") credentialsProvider, initialSource, d := getCredentialsProvider(baseCtx, c) if d.HasError() { diff --git a/aws_config_test.go b/aws_config_test.go index e352951a..8a63f9cc 100644 --- a/aws_config_test.go +++ b/aws_config_test.go @@ -1239,7 +1239,7 @@ func testUserAgentProducts(t *testing.T, testCase test.UserAgentTestCase) { } } -var errCancelOperation = fmt.Errorf("Test: Cancelling request") +var errCancelOperation = errors.New("Test: Cancelling request") // cancelRequestMiddleware creates a Smithy middleware that intercepts the request before sending and cancels it func cancelRequestMiddleware(t *testing.T, id string, f func(t *testing.T, request *smithyhttp.Request)) middleware.FinalizeMiddleware { diff --git a/config.go b/config.go index f46d7915..ffbf25f2 100644 --- a/config.go +++ b/config.go @@ -33,3 +33,8 @@ func EC2MetadataEndpointMode_Values() []string { EC2MetadataEndpointModeIPv6, } } + +const ( + HTTPProxyModeLegacy = config.HTTPProxyModeLegacy + HTTPProxyModeSeparate = config.HTTPProxyModeSeparate +) diff --git a/go.mod b/go.mod index 467f5e9f..82d28f69 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.45.0 go.opentelemetry.io/otel v1.19.0 golang.org/x/exp v0.0.0-20230905200255-921286631fa9 + golang.org/x/net v0.17.0 golang.org/x/text v0.14.0 ) diff --git a/go.sum b/go.sum index 5fc06de7..97335cd3 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,8 @@ go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1 go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/http_client_test.go b/http_client_test.go index 36e45d64..b04ec420 100644 --- a/http_client_test.go +++ b/http_client_test.go @@ -4,6 +4,7 @@ package awsbase import ( + "net/http" "testing" "github.com/hashicorp/aws-sdk-go-base/v2/internal/config" @@ -33,3 +34,18 @@ func TestHTTPClientConfiguration_insecureHTTPS(t *testing.T) { test.HTTPClientConfigurationTest_insecureHTTPS(t, transport) } + +func TestHTTPClientConfiguration_proxy(t *testing.T) { + test.HTTPClientConfigurationTest_proxy(t, transport) +} + +func transport(t *testing.T, config *config.Config) *http.Transport { + t.Helper() + + client, err := defaultHttpClient(config) + if err != nil { + t.Fatalf("creating client: %s", err) + } + + return client.GetTransport() +} diff --git a/internal/config/config.go b/internal/config/config.go index 71e86927..4ff13040 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -15,8 +15,17 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/hashicorp/aws-sdk-go-base/v2/diag" "github.com/hashicorp/aws-sdk-go-base/v2/internal/expand" "github.com/hashicorp/aws-sdk-go-base/v2/logging" + "golang.org/x/net/http/httpproxy" +) + +type ProxyMode int + +const ( + HTTPProxyModeLegacy ProxyMode = iota + HTTPProxyModeSeparate ) type Config struct { @@ -33,12 +42,15 @@ type Config struct { EC2MetadataServiceEndpointMode string ForbiddenAccountIds []string HTTPClient *http.Client - HTTPProxy string + HTTPProxy *string + HTTPSProxy *string IamEndpoint string Insecure bool Logger logging.Logger MaxRetries int + NoProxy string Profile string + HTTPProxyMode ProxyMode Region string RetryMode aws.RetryMode SecretKey string @@ -88,11 +100,18 @@ func (c Config) CustomCABundleReader() (*bytes.Reader, error) { // The returned options function is called on both AWS SDKv1 and v2 default HTTP clients. func (c Config) HTTPTransportOptions() (func(*http.Transport), error) { var err error - var proxyUrl *url.URL - if c.HTTPProxy != "" { - proxyUrl, err = url.Parse(c.HTTPProxy) + var httpProxyUrl *url.URL + if c.HTTPProxy != nil { + httpProxyUrl, err = url.Parse(aws.ToString(c.HTTPProxy)) if err != nil { - return nil, fmt.Errorf("error parsing HTTP proxy URL: %w", err) + return nil, fmt.Errorf("parsing HTTP proxy URL: %w", err) + } + } + var httpsProxyUrl *url.URL + if c.HTTPSProxy != nil { + httpsProxyUrl, err = url.Parse(aws.ToString(c.HTTPSProxy)) + if err != nil { + return nil, fmt.Errorf("parsing HTTPS proxy URL: %w", err) } } @@ -111,14 +130,84 @@ func (c Config) HTTPTransportOptions() (func(*http.Transport), error) { tr.TLSClientConfig.InsecureSkipVerify = true } - if proxyUrl != nil { - tr.Proxy = http.ProxyURL(proxyUrl) + proxyConfig := httpproxy.FromEnvironment() + if httpProxyUrl != nil { + proxyConfig.HTTPProxy = httpProxyUrl.String() + if c.HTTPProxyMode == HTTPProxyModeLegacy && proxyConfig.HTTPSProxy == "" { + proxyConfig.HTTPSProxy = httpProxyUrl.String() + } + } + if httpsProxyUrl != nil { + proxyConfig.HTTPSProxy = httpsProxyUrl.String() + } + if c.NoProxy != "" { + proxyConfig.NoProxy = c.NoProxy + } + tr.Proxy = func(req *http.Request) (*url.URL, error) { + return proxyConfig.ProxyFunc()(req.URL) } } return opts, nil } +func (c Config) ValidateProxySettings(diags *diag.Diagnostics) { + if c.HTTPProxy != nil { + if _, err := url.Parse(aws.ToString(c.HTTPProxy)); err != nil { + *diags = diags.AddError( + "Invalid HTTP Proxy", + fmt.Sprintf("Unable to parse URL: %s", err), + ) + } + } + + if c.HTTPSProxy != nil { + if _, err := url.Parse(aws.ToString(c.HTTPSProxy)); err != nil { + *diags = diags.AddError( + "Invalid HTTPS Proxy", + fmt.Sprintf("Unable to parse URL: %s", err), + ) + } + } + + if c.HTTPProxy != nil && *c.HTTPProxy != "" && c.HTTPSProxy == nil && os.Getenv("HTTPS_PROXY") == "" && os.Getenv("https_proxy") == "" { + if c.HTTPProxyMode == HTTPProxyModeLegacy { + *diags = diags.Append( + missingHttpsProxyLegacyWarningDiag(aws.ToString(c.HTTPProxy)), + ) + } else { + *diags = diags.Append( + missingHttpsProxyWarningDiag(), + ) + } + } +} + +const ( + missingHttpsProxyWarningSummary = "Missing HTTPS Proxy" + missingHttpsProxyDetailProblem = "An HTTP proxy was set but no HTTPS proxy was." + missingHttpsProxyDetailResolution = "To specify no proxy for HTTPS, set the HTTPS to an empty string." +) + +func missingHttpsProxyLegacyWarningDiag(s string) diag.Diagnostic { + return diag.NewWarningDiagnostic( + missingHttpsProxyWarningSummary, + fmt.Sprintf( + missingHttpsProxyDetailProblem+" Using HTTP proxy %q for HTTPS requests. This behavior may change in future versions.\n\n"+ + missingHttpsProxyDetailResolution, + s, + ), + ) +} + +func missingHttpsProxyWarningDiag() diag.Diagnostic { + return diag.NewWarningDiagnostic( + missingHttpsProxyWarningSummary, + missingHttpsProxyDetailProblem+"\n\n"+ + missingHttpsProxyDetailResolution, + ) +} + func (c Config) ResolveSharedConfigFiles() ([]string, error) { v, err := expand.FilePaths(c.SharedConfigFiles) if err != nil { diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f18ceb23..fbdfdfd2 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,7 +4,14 @@ package config import ( + "fmt" + "net/url" "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/aws-sdk-go-base/v2/diag" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" ) func TestConfig_VerifyAccountIDAllowed(t *testing.T) { @@ -72,3 +79,146 @@ func TestConfig_VerifyAccountIDAllowed(t *testing.T) { }) } } + +func foo(_ *url.URL, err error) error { + return err +} + +func TestValidateProxyConfig(t *testing.T) { + testcases := map[string]struct { + config Config + environmentVariables map[string]string + expectedDiags diag.Diagnostics + }{ + "no config": {}, + + "invalid HTTP proxy": { + config: Config{ + HTTPProxy: aws.String(" http://invalid.test"), // explicit URL parse failure + HTTPSProxy: aws.String("http://valid.test"), + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid HTTP Proxy", + fmt.Sprintf("Unable to parse URL: %s", foo(url.Parse(" http://invalid.test"))), //nolint:staticcheck + ), + }, + }, + + "invalid HTTPS proxy": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + HTTPSProxy: aws.String(" http://invalid.test"), // explicit URL parse failure + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid HTTPS Proxy", + fmt.Sprintf("Unable to parse URL: %s", foo(url.Parse(" http://invalid.test"))), //nolint:staticcheck + ), + }, + }, + + "invalid both proxies": { + config: Config{ + HTTPProxy: aws.String(" http://invalid.test"), // explicit URL parse failure + HTTPSProxy: aws.String(" http://invalid.test"), // explicit URL parse failure + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid HTTP Proxy", + fmt.Sprintf("Unable to parse URL: %s", foo(url.Parse(" http://invalid.test"))), //nolint:staticcheck + ), + diag.NewErrorDiagnostic( + "Invalid HTTPS Proxy", + fmt.Sprintf("Unable to parse URL: %s", foo(url.Parse(" http://invalid.test"))), //nolint:staticcheck + ), + }, + }, + + "HTTP proxy without HTTPS proxy Legacy": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + HTTPProxyMode: HTTPProxyModeLegacy, + }, + expectedDiags: diag.Diagnostics{ + diag.NewWarningDiagnostic( + missingHttpsProxyWarningSummary, + fmt.Sprintf( + "An HTTP proxy was set but no HTTPS proxy was. Using HTTP proxy %q for HTTPS requests. This behavior may change in future versions.\n\n"+ + "To specify no proxy for HTTPS, set the HTTPS to an empty string.", + "http://valid.test"), + ), + }, + }, + + "HTTP proxy empty string": { + config: Config{ + HTTPProxy: aws.String(""), + }, + expectedDiags: diag.Diagnostics{}, + }, + + "HTTP proxy with HTTPS proxy empty string Legacy": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + HTTPSProxy: aws.String(""), + HTTPProxyMode: HTTPProxyModeLegacy, + }, + expectedDiags: diag.Diagnostics{}, + }, + + "HTTP proxy config with HTTPS_PROXY envvar": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + }, + environmentVariables: map[string]string{ + "HTTPS_PROXY": "http://envvar-proxy.test:1234", + }, + expectedDiags: diag.Diagnostics{}, + }, + + "HTTP proxy config with https_proxy envvar": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + }, + environmentVariables: map[string]string{ + "https_proxy": "http://envvar-proxy.test:1234", + }, + expectedDiags: diag.Diagnostics{}, + }, + + "HTTP proxy without HTTPS proxy Separate": { + config: Config{ + HTTPProxy: aws.String("http://valid.test"), + HTTPProxyMode: HTTPProxyModeSeparate, + }, + expectedDiags: diag.Diagnostics{ + diag.NewWarningDiagnostic( + missingHttpsProxyWarningSummary, + "An HTTP proxy was set but no HTTPS proxy was.\n\n"+ + "To specify no proxy for HTTPS, set the HTTPS to an empty string.", + ), + }, + }, + } + + for name, testcase := range testcases { + testcase := testcase + + t.Run(name, func(t *testing.T) { + servicemocks.InitSessionTestEnv(t) + + for k, v := range testcase.environmentVariables { + t.Setenv(k, v) + } + + var diags diag.Diagnostics + + testcase.config.ValidateProxySettings(&diags) + + if diff := cmp.Diff(diags, testcase.expectedDiags); diff != "" { + t.Errorf("Unexpected response (+wanted, -got): %s", diff) + } + }) + } +} diff --git a/internal/test/http_client.go b/internal/test/http_client.go index fd04a596..05efb909 100644 --- a/internal/test/http_client.go +++ b/internal/test/http_client.go @@ -8,10 +8,17 @@ import ( "net/http" "testing" + "github.com/aws/aws-sdk-go-v2/aws" awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + "github.com/hashicorp/aws-sdk-go-base/v2/internal/config" + "github.com/hashicorp/aws-sdk-go-base/v2/servicemocks" ) +type TransportGetter func(t *testing.T, config *config.Config) *http.Transport + func HTTPClientConfigurationTest_basic(t *testing.T, transport *http.Transport) { + t.Helper() + if a, e := transport.MaxIdleConns, awshttp.DefaultHTTPTransportMaxIdleConns; a != e { t.Errorf("expected MaxIdleConns to be %d, got %d", e, a) } @@ -44,8 +51,522 @@ func HTTPClientConfigurationTest_basic(t *testing.T, transport *http.Transport) } func HTTPClientConfigurationTest_insecureHTTPS(t *testing.T, transport *http.Transport) { + t.Helper() + tlsConfig := transport.TLSClientConfig if !tlsConfig.InsecureSkipVerify { t.Error("expected InsecureSkipVerify to be true, got false") } } + +type proxyCase struct { + url string + expectedProxy string +} + +func HTTPClientConfigurationTest_proxy(t *testing.T, getter TransportGetter) { + t.Helper() + + // Go supports both the upper- and lower-case versions of the proxy environment variables + testcases := map[string]struct { + config config.Config + environmentVariables map[string]string + urls []proxyCase + }{ + "no config": { + config: config.Config{}, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config empty string": { + config: config.Config{ + HTTPProxy: aws.String(""), + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config Legacy": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeLegacy, + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config Separate": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeSeparate, + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPSProxy config": { + config: config.Config{ + HTTPSProxy: aws.String("http://https-proxy.test:1234"), + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config HTTPSProxy config": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPSProxy: aws.String("http://https-proxy.test:1234"), + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config HTTPSProxy config empty string Legacy": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPSProxy: aws.String(""), + HTTPProxyMode: config.HTTPProxyModeLegacy, + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPSProxy config HTTPProxy config empty string": { + config: config.Config{ + HTTPProxy: aws.String(""), + HTTPSProxy: aws.String("http://https-proxy.test:1234"), + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config HTTPSProxy config NoProxy config": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPSProxy: aws.String("http://https-proxy.test:1234"), + NoProxy: "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTP_PROXY envvar": { + config: config.Config{}, + environmentVariables: map[string]string{ + "HTTP_PROXY": "http://http-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "http_proxy envvar": { + config: config.Config{}, + environmentVariables: map[string]string{ + "http_proxy": "http://http-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPS_PROXY envvar": { + config: config.Config{}, + environmentVariables: map[string]string{ + "HTTPS_PROXY": "http://https-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "https_proxy envvar": { + config: config.Config{}, + environmentVariables: map[string]string{ + "https_proxy": "http://https-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config HTTPS_PROXY envvar": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + }, + environmentVariables: map[string]string{ + "HTTPS_PROXY": "http://https-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config https_proxy envvar": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + }, + environmentVariables: map[string]string{ + "https_proxy": "http://https-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config NO_PROXY envvar Legacy": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeLegacy, + }, + environmentVariables: map[string]string{ + "NO_PROXY": "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config NO_PROXY envvar Separate": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeSeparate, + }, + environmentVariables: map[string]string{ + "NO_PROXY": "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config no_proxy envvar Legacy": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeLegacy, + }, + environmentVariables: map[string]string{ + "no_proxy": "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config no_proxy envvar Separate": { + config: config.Config{ + HTTPProxy: aws.String("http://http-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeSeparate, + }, + environmentVariables: map[string]string{ + "no_proxy": "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTP_PROXY envvar HTTPS_PROXY envvar NO_PROXY envvar": { + config: config.Config{}, + environmentVariables: map[string]string{ + "HTTP_PROXY": "http://http-proxy.test:1234", + "HTTPS_PROXY": "http://https-proxy.test:1234", + "NO_PROXY": "dont-proxy.test", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://http-proxy.test:1234", + }, + { + url: "http://dont-proxy.test", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://https-proxy.test:1234", + }, + { + url: "https://dont-proxy.test", + expectedProxy: "", + }, + }, + }, + + "HTTPProxy config overrides HTTP_PROXY envvar Legacy": { + config: config.Config{ + HTTPProxy: aws.String("http://config-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeLegacy, + }, + environmentVariables: map[string]string{ + "HTTP_PROXY": "http://envvar-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://config-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "http://config-proxy.test:1234", + }, + }, + }, + + "HTTPProxy config overrides HTTP_PROXY envvar Separate": { + config: config.Config{ + HTTPProxy: aws.String("http://config-proxy.test:1234"), + HTTPProxyMode: config.HTTPProxyModeSeparate, + }, + environmentVariables: map[string]string{ + "HTTP_PROXY": "http://envvar-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "http://config-proxy.test:1234", + }, + { + url: "https://example.com", + expectedProxy: "", + }, + }, + }, + + "HTTPSProxy config overrides HTTPS_PROXY envvar": { + config: config.Config{ + HTTPSProxy: aws.String("http://config-proxy.test:1234"), + }, + environmentVariables: map[string]string{ + "HTTPS_PROXY": "http://envvar-proxy.test:1234", + }, + urls: []proxyCase{ + { + url: "http://example.com", + expectedProxy: "", + }, + { + url: "https://example.com", + expectedProxy: "http://config-proxy.test:1234", + }, + }, + }, + } + + for name, testcase := range testcases { + testcase := testcase + + t.Run(name, func(t *testing.T) { + servicemocks.InitSessionTestEnv(t) + + for k, v := range testcase.environmentVariables { + t.Setenv(k, v) + } + + transport := getter(t, &testcase.config) + proxy := transport.Proxy + + for _, url := range testcase.urls { + req, _ := http.NewRequest("GET", url.url, nil) + pUrl, err := proxy(req) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if url.expectedProxy != "" { + if pUrl == nil { + t.Errorf("expected proxy for %q, got none", url.url) + } else if pUrl.String() != url.expectedProxy { + t.Errorf("expected proxy %q for %q, got %q", url.expectedProxy, url.url, pUrl.String()) + } + } else { + if pUrl != nil { + t.Errorf("expected no proxy for %q, got %q", url.url, pUrl.String()) + } + } + } + }) + } +} diff --git a/v2/awsv1shim/credentials.go b/v2/awsv1shim/credentials.go index e2ddba80..f0a1582b 100644 --- a/v2/awsv1shim/credentials.go +++ b/v2/awsv1shim/credentials.go @@ -3,13 +3,13 @@ package awsv1shim -import ( // nosemgrep: no-sdkv2-imports-in-awsv1shim +import ( "context" "fmt" "sync/atomic" "time" - awsv2 "github.com/aws/aws-sdk-go-v2/aws" + awsv2 "github.com/aws/aws-sdk-go-v2/aws" // nosemgrep: no-sdkv2-imports-in-awsv1shim "github.com/aws/aws-sdk-go/aws/credentials" ) diff --git a/v2/awsv1shim/credentials_test.go b/v2/awsv1shim/credentials_test.go index ea8420e7..187d3145 100644 --- a/v2/awsv1shim/credentials_test.go +++ b/v2/awsv1shim/credentials_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - awsv2 "github.com/aws/aws-sdk-go-v2/aws" - credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials" - stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds" - stsv2 "github.com/aws/aws-sdk-go-v2/service/sts" - ststypesv2 "github.com/aws/aws-sdk-go-v2/service/sts/types" + awsv2 "github.com/aws/aws-sdk-go-v2/aws" // nosemgrep: no-sdkv2-imports-in-awsv1shim + credentialsv2 "github.com/aws/aws-sdk-go-v2/credentials" // nosemgrep: no-sdkv2-imports-in-awsv1shim + stscredsv2 "github.com/aws/aws-sdk-go-v2/credentials/stscreds" // nosemgrep: no-sdkv2-imports-in-awsv1shim + stsv2 "github.com/aws/aws-sdk-go-v2/service/sts" // nosemgrep: no-sdkv2-imports-in-awsv1shim + ststypesv2 "github.com/aws/aws-sdk-go-v2/service/sts/types" // nosemgrep: no-sdkv2-imports-in-awsv1shim "github.com/aws/aws-sdk-go/aws" "github.com/hashicorp/aws-sdk-go-base/v2/internal/test" ) diff --git a/v2/awsv1shim/go.mod b/v2/awsv1shim/go.mod index f59d9b63..ab811328 100644 --- a/v2/awsv1shim/go.mod +++ b/v2/awsv1shim/go.mod @@ -49,6 +49,7 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/v2/awsv1shim/go.sum b/v2/awsv1shim/go.sum index a69fadd7..94cca4ae 100644 --- a/v2/awsv1shim/go.sum +++ b/v2/awsv1shim/go.sum @@ -100,6 +100,8 @@ go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1 go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/v2/awsv1shim/http_client_test.go b/v2/awsv1shim/http_client_test.go index 3d752acb..9da0d853 100644 --- a/v2/awsv1shim/http_client_test.go +++ b/v2/awsv1shim/http_client_test.go @@ -40,3 +40,23 @@ func TestHTTPClientConfiguration_insecureHTTPS(t *testing.T) { test.HTTPClientConfigurationTest_insecureHTTPS(t, transport) } + +func TestHTTPClientConfiguration_proxy(t *testing.T) { + test.HTTPClientConfigurationTest_proxy(t, transport) +} + +func transport(t *testing.T, config *config.Config) *http.Transport { + t.Helper() + + client, err := defaultHttpClient(config) + if err != nil { + t.Fatalf("creating client: %s", err) + } + + transport, ok := client.Transport.(*http.Transport) + if !ok { + t.Fatalf("Unexpected type for HTTP client transport: %T", client.Transport) + } + + return transport +} diff --git a/v2/awsv1shim/session.go b/v2/awsv1shim/session.go index c3ebc25a..3094a6eb 100644 --- a/v2/awsv1shim/session.go +++ b/v2/awsv1shim/session.go @@ -8,7 +8,7 @@ import ( // nosemgrep: no-sdkv2-imports-in-awsv1shim "fmt" "os" - awsv2 "github.com/aws/aws-sdk-go-v2/aws" + awsv2 "github.com/aws/aws-sdk-go-v2/aws" // nosemgrep: no-sdkv2-imports-in-awsv1shim "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/endpoints" "github.com/aws/aws-sdk-go/aws/request" diff --git a/v2/awsv1shim/session_test.go b/v2/awsv1shim/session_test.go index e2774a81..07fd8ef2 100644 --- a/v2/awsv1shim/session_test.go +++ b/v2/awsv1shim/session_test.go @@ -17,10 +17,10 @@ import ( "testing" "time" - retryModev2 "github.com/aws/aws-sdk-go-v2/aws" + awsv2 "github.com/aws/aws-sdk-go-v2/aws" // nosemgrep: no-sdkv2-imports-in-awsv1shim retryv2 "github.com/aws/aws-sdk-go-v2/aws/retry" configv2 "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" // nosemgrep: no-sdkv2-imports-in-awsv1shim "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/client" @@ -1395,7 +1395,7 @@ func TestRetryMode(t *testing.T) { Config *awsbase.Config EnvironmentVariables map[string]string SharedConfigurationFile string - ExpectedRetryMode retryModev2.RetryMode + ExpectedRetryMode awsv2.RetryMode }{ "no configuration": { Config: &awsbase.Config{ @@ -1409,9 +1409,9 @@ func TestRetryMode(t *testing.T) { Config: &awsbase.Config{ AccessKey: servicemocks.MockStaticAccessKey, SecretKey: servicemocks.MockStaticSecretKey, - RetryMode: retryModev2.RetryModeStandard, + RetryMode: awsv2.RetryModeStandard, }, - ExpectedRetryMode: retryModev2.RetryModeStandard, + ExpectedRetryMode: awsv2.RetryModeStandard, }, "AWS_RETRY_MODE": { @@ -1422,7 +1422,7 @@ func TestRetryMode(t *testing.T) { EnvironmentVariables: map[string]string{ "AWS_RETRY_MODE": "adaptive", }, - ExpectedRetryMode: retryModev2.RetryModeAdaptive, + ExpectedRetryMode: awsv2.RetryModeAdaptive, }, "shared configuration file": { @@ -1434,19 +1434,19 @@ func TestRetryMode(t *testing.T) { [default] retry_mode = standard `, - ExpectedRetryMode: retryModev2.RetryModeStandard, + ExpectedRetryMode: awsv2.RetryModeStandard, }, "config overrides AWS_RETRY_MODE": { Config: &awsbase.Config{ AccessKey: servicemocks.MockStaticAccessKey, SecretKey: servicemocks.MockStaticSecretKey, - RetryMode: retryModev2.RetryModeStandard, + RetryMode: awsv2.RetryModeStandard, }, EnvironmentVariables: map[string]string{ "AWS_RETRY_MODE": "adaptive", }, - ExpectedRetryMode: retryModev2.RetryModeStandard, + ExpectedRetryMode: awsv2.RetryModeStandard, }, "AWS_RETRY_MODE overrides shared configuration": { @@ -1461,7 +1461,7 @@ func TestRetryMode(t *testing.T) { [default] retry_mode = adaptive `, - ExpectedRetryMode: retryModev2.RetryModeStandard, + ExpectedRetryMode: awsv2.RetryModeStandard, }, }