From c1a2d94efdb8dc68378cbafa2791b68918ae34b2 Mon Sep 17 00:00:00 2001 From: Jared Baker Date: Thu, 24 Aug 2023 13:55:58 -0400 Subject: [PATCH] backend/s3: add ec2_metadata_service_endpoint argument --- CHANGELOG.md | 1 + internal/backend/remote-state/s3/backend.go | 26 +++++++ .../backend/remote-state/s3/backend_test.go | 76 +++++++++++++++++++ .../docs/language/settings/backends/s3.mdx | 2 + 4 files changed, 105 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a63ecbd2aa..6a8fd11412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ ENHANCEMENTS: * `shared_config_files` and `shared_credentials_files` arguments for specifying credential and configuration files as part of the backend configuration. ([#30493](https://github.com/hashicorp/terraform/issues/30493)) * Internally the backend now uses AWS SDK for Go v2, which should address various other missing behaviors that are handled by the SDK rather than by Terraform itself. ([#30443](https://github.com/hashicorp/terraform/issues/30443)) * `custom_ca_bundle` argument and support for the corresponding AWS environment variable, `AWS_CA_BUNDLE`, for providing custom root and intermediate certificates. ([#33689](https://github.com/hashicorp/terraform/issues/33689)) + * `ec2_metadata_service_endpoint` and `ec2_metadata_service_endpoint_mode` arguments and support for the corresponding AWS environment variables, `AWS_EC2_METADATA_SERVICE_ENDPOINT` and `AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE` for setting the EC2 metadata service (IMDS) endpoint. The environment variable `AWS_METADATA_URL` is also supported for compatibility with the AWS provider, but is deprecated. ([#30444](https://github.com/hashicorp/terraform/issues/30444)) * backend/cos: Support custom HTTP(S) endpoint and root domain for the API client. [#33656] BUG FIXES: diff --git a/internal/backend/remote-state/s3/backend.go b/internal/backend/remote-state/s3/backend.go index 05bd87e2c6..e12f697a39 100644 --- a/internal/backend/remote-state/s3/backend.go +++ b/internal/backend/remote-state/s3/backend.go @@ -72,6 +72,16 @@ func (b *Backend) ConfigSchema() *configschema.Block { Description: "A custom endpoint for the DynamoDB API", Deprecated: true, }, + "ec2_metadata_service_endpoint": { + Type: cty.String, + Optional: true, + Description: "Address of the EC2 metadata service (IMDS) endpoint to use.", + }, + "ec2_metadata_service_endpoint_mode": { + Type: cty.String, + Optional: true, + Description: "Mode to use in communicating with the metadata service.", + }, "endpoint": { Type: cty.String, Optional: true, @@ -568,6 +578,7 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics { "AWS_IAM_ENDPOINT": "AWS_ENDPOINT_URL_IAM", "AWS_S3_ENDPOINT": "AWS_ENDPOINT_URL_S3", "AWS_STS_ENDPOINT": "AWS_ENDPOINT_URL_STS", + "AWS_METADATA_URL": "AWS_EC2_METADATA_SERVICE_ENDPOINT", } { if val := os.Getenv(envvar); val != "" { diags = diags.Append(deprecatedEnvVarDiag(envvar, replacement)) @@ -607,6 +618,21 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics { } } + if v, ok := retrieveArgument(&diags, + newAttributeRetriever(obj, cty.GetAttrPath("ec2_metadata_service_endpoint")), + newEnvvarRetriever("AWS_EC2_METADATA_SERVICE_ENDPOINT"), + newEnvvarRetriever("AWS_METADATA_URL"), + ); ok { + cfg.EC2MetadataServiceEndpoint = v + } + + if v, ok := retrieveArgument(&diags, + newAttributeRetriever(obj, cty.GetAttrPath("ec2_metadata_service_endpoint_mode")), + newEnvvarRetriever("AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"), + ); ok { + cfg.EC2MetadataServiceEndpointMode = v + } + if val, ok := stringAttrOk(obj, "shared_credentials_file"); ok { cfg.SharedCredentialsFiles = []string{ val, diff --git a/internal/backend/remote-state/s3/backend_test.go b/internal/backend/remote-state/s3/backend_test.go index 260b17db89..babbdde283 100644 --- a/internal/backend/remote-state/s3/backend_test.go +++ b/internal/backend/remote-state/s3/backend_test.go @@ -516,6 +516,82 @@ func TestBackendConfig_S3Endpoint(t *testing.T) { } } +func TestBackendConfig_EC2MetadataEndpoint(t *testing.T) { + testACC(t) + + cases := map[string]struct { + config map[string]any + vars map[string]string + expectedEndpoint string + expectedDiags tfdiags.Diagnostics + }{ + "none": { + expectedEndpoint: "", + }, + "config": { + config: map[string]any{ + "ec2_metadata_service_endpoint": "ec2.test", + }, + expectedEndpoint: "ec2.test", + }, + "envvar": { + vars: map[string]string{ + "AWS_EC2_METADATA_SERVICE_ENDPOINT": "ec2.test", + }, + expectedEndpoint: "ec2.test", + }, + "deprecated envvar": { + vars: map[string]string{ + "AWS_METADATA_URL": "ec2.test", + }, + expectedEndpoint: "ec2.test", + expectedDiags: tfdiags.Diagnostics{ + deprecatedEnvVarDiag("AWS_METADATA_URL", "AWS_EC2_METADATA_SERVICE_ENDPOINT"), + }, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + config := map[string]interface{}{ + "region": "us-west-1", + "bucket": "tf-test", + "key": "state", + } + + if tc.vars != nil { + for k, v := range tc.vars { + os.Setenv(k, v) + } + t.Cleanup(func() { + for k := range tc.vars { + os.Unsetenv(k) + } + }) + } + + if tc.config != nil { + for k, v := range tc.config { + config[k] = v + } + } + + _, diags := testBackendConfigDiags(t, New(), backend.TestWrapConfig(config)) + // raw, diags := testBackendConfigDiags(t, New(), backend.TestWrapConfig(config)) + // b := raw.(*Backend) + + if diff := cmp.Diff(diags, tc.expectedDiags, cmp.Comparer(diagnosticComparer)); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if !diags.HasErrors() { + // TODO: Convert to AWS SDK v2 equivalent + // checkClientEndpoint(t, b.s3Client.Config, tc.expectedEndpoint) + } + }) + } +} + func TestBackendConfig_AssumeRole(t *testing.T) { testACC(t) diff --git a/website/docs/language/settings/backends/s3.mdx b/website/docs/language/settings/backends/s3.mdx index 49a9011d5e..752552b836 100644 --- a/website/docs/language/settings/backends/s3.mdx +++ b/website/docs/language/settings/backends/s3.mdx @@ -155,6 +155,8 @@ The following configuration is optional: * `access_key` - (Optional) AWS access key. If configured, must also configure `secret_key`. This can also be sourced from the `AWS_ACCESS_KEY_ID` environment variable, AWS shared credentials file (e.g. `~/.aws/credentials`), or AWS shared configuration file (e.g. `~/.aws/config`). * `custom_ca_bundle` - (Optional) File containing custom root and intermediate certificates. Can also be set using the `AWS_CA_BUNDLE` environment variable. Setting ca_bundle in the shared config file is not supported. * `secret_key` - (Optional) AWS access key. If configured, must also configure `access_key`. This can also be sourced from the `AWS_SECRET_ACCESS_KEY` environment variable, AWS shared credentials file (e.g. `~/.aws/credentials`), or AWS shared configuration file (e.g. `~/.aws/config`). +* `ec2_metadata_service_endpoint` - (Optional) Address of the EC2 metadata service (IMDS) endpoint to use. Can also be set with the `AWS_EC2_METADATA_SERVICE_ENDPOINT` environment variable. +* `ec2_metadata_service_endpoint_mode` - (Optional) Mode to use in communicating with the metadata service. Valid values are `IPv4` and `IPv6`. Can also be set with the `AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE` environment variable. * `iam_endpoint` - (Optional, **Deprecated**) Custom endpoint for the AWS Identity and Access Management (IAM) API. Use `endpoints.iam` instead. * `max_retries` - (Optional) The maximum number of times an AWS API request is retried on retryable failure. Defaults to 5.