diff --git a/.changelog/aacb97e021754263bfbfe819a6e5d7c9.json b/.changelog/aacb97e021754263bfbfe819a6e5d7c9.json new file mode 100644 index 00000000000..e86266791da --- /dev/null +++ b/.changelog/aacb97e021754263bfbfe819a6e5d7c9.json @@ -0,0 +1,8 @@ +{ + "id": "aacb97e0-2175-4263-bfbf-e819a6e5d7c9", + "type": "feature", + "description": "Adds support for the `ca_bundle` parameter in shared config and credentials files. The usage of the file is the same as environment variable, `AWS_CA_BUNDLE`, but sourced from shared config. Fixes [#1589](https://github.com/aws/aws-sdk-go-v2/issues/1589)", + "modules": [ + "config" + ] +} diff --git a/config/codegen/main.go b/config/codegen/main.go index a3f8b573203..d74bc823c1d 100644 --- a/config/codegen/main.go +++ b/config/codegen/main.go @@ -22,7 +22,7 @@ const ( var implAsserts = map[string][]string{ "sharedConfigProfileProvider": {envConfigType, loadOptionsType}, "sharedConfigFilesProvider": {envConfigType, loadOptionsType}, - "customCABundleProvider": {envConfigType, loadOptionsType}, + "customCABundleProvider": {envConfigType, sharedConfigType, loadOptionsType}, "regionProvider": {envConfigType, sharedConfigType, loadOptionsType, ec2IMDSRegionType}, "credentialsProviderProvider": {loadOptionsType}, "defaultRegionProvider": {loadOptionsType}, diff --git a/config/provider_assert_test.go b/config/provider_assert_test.go index 78a0ceae3cf..efa46197cfc 100644 --- a/config/provider_assert_test.go +++ b/config/provider_assert_test.go @@ -30,6 +30,7 @@ var ( // customCABundleProvider implementor assertions var ( _ customCABundleProvider = &EnvConfig{} + _ customCABundleProvider = &SharedConfig{} _ customCABundleProvider = &LoadOptions{} ) diff --git a/config/shared_config.go b/config/shared_config.go index d89eed87724..4c43a165d46 100644 --- a/config/shared_config.go +++ b/config/shared_config.go @@ -1,9 +1,12 @@ package config import ( + "bytes" "context" "errors" "fmt" + "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -81,6 +84,8 @@ const ( // Retry options retryMaxAttemptsKey = "max_attempts" retryModeKey = "retry_mode" + + caBundleKey = "ca_bundle" ) // defaultSharedConfigProfile allows for swapping the default profile for testing @@ -171,12 +176,14 @@ type SharedConfig struct { // s3_use_arn_region=true S3UseARNRegion *bool - // Specifies the EC2 Instance Metadata Service default endpoint selection mode (IPv4 or IPv6) + // Specifies the EC2 Instance Metadata Service default endpoint selection + // mode (IPv4 or IPv6) // // ec2_metadata_service_endpoint_mode=IPv6 EC2IMDSEndpointMode imds.EndpointModeState - // Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EC2IMDSEndpointMode. + // Specifies the EC2 Instance Metadata Service endpoint to use. If + // specified it overrides EC2IMDSEndpointMode. // // ec2_metadata_service_endpoint=http://fd00:ec2::254 EC2IMDSEndpoint string @@ -214,6 +221,22 @@ type SharedConfig struct { // // retry_mode=standard RetryMode aws.RetryMode + + // Sets the path to a custom Credentials Authority (CA) Bundle PEM file + // that the SDK will use instead of the system's root CA bundle. Only use + // this if you want to configure the SDK to use a custom set of CAs. + // + // Enabling this option will attempt to merge the Transport into the SDK's + // HTTP client. If the client's Transport is not a http.Transport an error + // will be returned. If the Transport's TLS config is set this option will + // cause the SDK to overwrite the Transport's TLS config's RootCAs value. + // + // Setting a custom HTTPClient in the aws.Config options will override this + // setting. To use this option and custom HTTP client, the HTTP client + // needs to be provided when creating the config. Not the service client. + // + // ca_bundle=$HOME/my_custom_ca_bundle + CustomCABundle string } func (c SharedConfig) getDefaultsMode(ctx context.Context) (value aws.DefaultsMode, ok bool, err error) { @@ -323,6 +346,19 @@ func (c SharedConfig) GetUseFIPSEndpoint(ctx context.Context) (value aws.FIPSEnd return c.UseFIPSEndpoint, true, nil } +// GetCustomCABundle returns the custom CA bundle's PEM bytes if the file was +func (c SharedConfig) getCustomCABundle(context.Context) (io.Reader, bool, error) { + if len(c.CustomCABundle) == 0 { + return nil, false, nil + } + + b, err := ioutil.ReadFile(c.CustomCABundle) + if err != nil { + return nil, false, err + } + return bytes.NewReader(b), true, nil +} + // loadSharedConfigIgnoreNotExist is an alias for loadSharedConfig with the // addition of ignoring when none of the files exist or when the profile // is not found in any of the files. @@ -871,6 +907,8 @@ func (c *SharedConfig) setFromIniSection(profile string, section ini.Section) er return fmt.Errorf("failed to load %s from shared config, %w", retryModeKey, err) } + updateString(&c.CustomCABundle, section, caBundleKey) + // Shared Credentials creds := aws.Credentials{ AccessKeyID: section.String(accessKeyIDKey), diff --git a/config/shared_config_test.go b/config/shared_config_test.go index 2c63ceb37cd..5293bf561e2 100644 --- a/config/shared_config_test.go +++ b/config/shared_config_test.go @@ -574,6 +574,15 @@ func TestNewSharedConfig(t *testing.T) { Profile: "retrywithinvalidattempts", Err: fmt.Errorf("failed to load max_attempts from shared config, invalid value max_attempts=invalid, expect integer"), }, + "ca bundle options": { + ConfigFilenames: []string{testConfigFilename}, + CredentialsFilenames: []string{testCredentialsFilename}, + Profile: "with_ca_bundle", + Expected: SharedConfig{ + Profile: "with_ca_bundle", + CustomCABundle: "custom_ca_bundle_file.pem", + }, + }, "merged profiles across files": { ConfigFilenames: []string{testConfigFilename}, CredentialsFilenames: []string{testCredentialsFilename}, diff --git a/config/testdata/shared_config b/config/testdata/shared_config index 396f5df87ff..742c2fa0f49 100644 --- a/config/testdata/shared_config +++ b/config/testdata/shared_config @@ -257,3 +257,6 @@ retry_mode = invalid [profile retrywithinvalidattempts] max_attempts = invalid + +[profile with_ca_bundle] +ca_bundle = custom_ca_bundle_file.pem