github.com/opentofu/opentofu@v1.7.1/internal/backend/remote-state/s3/backend.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package s3
     7  
     8  import (
     9  	"context"
    10  	"encoding/base64"
    11  	"fmt"
    12  	"log"
    13  	"os"
    14  	"regexp"
    15  	"sort"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/aws/aws-sdk-go-v2/aws"
    20  	"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
    21  	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
    22  	"github.com/aws/aws-sdk-go-v2/service/s3"
    23  	awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
    24  	baselogging "github.com/hashicorp/aws-sdk-go-base/v2/logging"
    25  	awsbaseValidation "github.com/hashicorp/aws-sdk-go-base/v2/validation"
    26  	"github.com/opentofu/opentofu/internal/backend"
    27  	"github.com/opentofu/opentofu/internal/configs/configschema"
    28  	"github.com/opentofu/opentofu/internal/encryption"
    29  	"github.com/opentofu/opentofu/internal/httpclient"
    30  	"github.com/opentofu/opentofu/internal/logging"
    31  	"github.com/opentofu/opentofu/internal/tfdiags"
    32  	"github.com/opentofu/opentofu/version"
    33  	"github.com/zclconf/go-cty/cty"
    34  	"github.com/zclconf/go-cty/cty/gocty"
    35  )
    36  
    37  func New(enc encryption.StateEncryption) backend.Backend {
    38  	return &Backend{encryption: enc}
    39  }
    40  
    41  type Backend struct {
    42  	encryption encryption.StateEncryption
    43  	s3Client   *s3.Client
    44  	dynClient  *dynamodb.Client
    45  	awsConfig  aws.Config
    46  
    47  	bucketName            string
    48  	keyName               string
    49  	serverSideEncryption  bool
    50  	customerEncryptionKey []byte
    51  	acl                   string
    52  	kmsKeyID              string
    53  	ddbTable              string
    54  	workspaceKeyPrefix    string
    55  	skipS3Checksum        bool
    56  }
    57  
    58  // ConfigSchema returns a description of the expected configuration
    59  // structure for the receiving backend.
    60  // This structure is mirrored by the encryption aws_kms key provider and should be kept in sync.
    61  func (b *Backend) ConfigSchema() *configschema.Block {
    62  	return &configschema.Block{
    63  		Attributes: map[string]*configschema.Attribute{
    64  			"bucket": {
    65  				Type:        cty.String,
    66  				Required:    true,
    67  				Description: "The name of the S3 bucket",
    68  			},
    69  			"key": {
    70  				Type:        cty.String,
    71  				Required:    true,
    72  				Description: "The path to the state file inside the bucket",
    73  			},
    74  			"region": {
    75  				Type:        cty.String,
    76  				Optional:    true,
    77  				Description: "AWS region of the S3 Bucket and DynamoDB Table (if used).",
    78  			},
    79  			"endpoints": {
    80  				Optional: true,
    81  				NestedType: &configschema.Object{
    82  					Nesting: configschema.NestingSingle,
    83  					Attributes: map[string]*configschema.Attribute{
    84  						"s3": {
    85  							Type:        cty.String,
    86  							Optional:    true,
    87  							Description: "A custom endpoint for the S3 API.",
    88  						},
    89  						"iam": {
    90  							Type:        cty.String,
    91  							Optional:    true,
    92  							Description: "A custom endpoint for the IAM API.",
    93  						},
    94  						"sts": {
    95  							Type:        cty.String,
    96  							Optional:    true,
    97  							Description: "A custom endpoint for the STS API.",
    98  						},
    99  						"dynamodb": {
   100  							Type:        cty.String,
   101  							Optional:    true,
   102  							Description: "A custom endpoint for the DynamoDB API.",
   103  						},
   104  					},
   105  				},
   106  			},
   107  			"dynamodb_endpoint": {
   108  				Type:        cty.String,
   109  				Optional:    true,
   110  				Description: "A custom endpoint for the DynamoDB API. Use `endpoints.dynamodb` instead.",
   111  				Deprecated:  true,
   112  			},
   113  			"endpoint": {
   114  				Type:        cty.String,
   115  				Optional:    true,
   116  				Description: "A custom endpoint for the S3 API. Use `endpoints.s3` instead",
   117  				Deprecated:  true,
   118  			},
   119  			"iam_endpoint": {
   120  				Type:        cty.String,
   121  				Optional:    true,
   122  				Description: "A custom endpoint for the IAM API. Use `endpoints.iam` instead",
   123  				Deprecated:  true,
   124  			},
   125  			"sts_endpoint": {
   126  				Type:        cty.String,
   127  				Optional:    true,
   128  				Description: "A custom endpoint for the STS API. Use `endpoints.sts` instead",
   129  				Deprecated:  true,
   130  			},
   131  			"sts_region": {
   132  				Type:        cty.String,
   133  				Optional:    true,
   134  				Description: "The region where AWS STS operations will take place",
   135  			},
   136  			"encrypt": {
   137  				Type:        cty.Bool,
   138  				Optional:    true,
   139  				Description: "Whether to enable server side encryption of the state file",
   140  			},
   141  			"acl": {
   142  				Type:        cty.String,
   143  				Optional:    true,
   144  				Description: "Canned ACL to be applied to the state file",
   145  			},
   146  			"access_key": {
   147  				Type:        cty.String,
   148  				Optional:    true,
   149  				Description: "AWS access key",
   150  			},
   151  			"secret_key": {
   152  				Type:        cty.String,
   153  				Optional:    true,
   154  				Description: "AWS secret key",
   155  			},
   156  			"kms_key_id": {
   157  				Type:        cty.String,
   158  				Optional:    true,
   159  				Description: "The ARN of a KMS Key to use for encrypting the state",
   160  			},
   161  			"dynamodb_table": {
   162  				Type:        cty.String,
   163  				Optional:    true,
   164  				Description: "DynamoDB table for state locking and consistency",
   165  			},
   166  			"profile": {
   167  				Type:        cty.String,
   168  				Optional:    true,
   169  				Description: "AWS profile name",
   170  			},
   171  			"shared_credentials_file": {
   172  				Type:        cty.String,
   173  				Optional:    true,
   174  				Description: "Path to a shared credentials file",
   175  			},
   176  			"shared_credentials_files": {
   177  				Type:        cty.Set(cty.String),
   178  				Optional:    true,
   179  				Description: "Paths to a shared credentials files",
   180  			},
   181  			"shared_config_files": {
   182  				Type:        cty.Set(cty.String),
   183  				Optional:    true,
   184  				Description: "Paths to shared config files",
   185  			},
   186  			"token": {
   187  				Type:        cty.String,
   188  				Optional:    true,
   189  				Description: "MFA token",
   190  			},
   191  			"skip_credentials_validation": {
   192  				Type:        cty.Bool,
   193  				Optional:    true,
   194  				Description: "Skip the credentials validation via STS API.",
   195  			},
   196  			"skip_metadata_api_check": {
   197  				Type:        cty.Bool,
   198  				Optional:    true,
   199  				Description: "Skip the AWS Metadata API check.",
   200  			},
   201  			"skip_region_validation": {
   202  				Type:        cty.Bool,
   203  				Optional:    true,
   204  				Description: "Skip static validation of region name.",
   205  			},
   206  			"skip_requesting_account_id": {
   207  				Type:        cty.Bool,
   208  				Optional:    true,
   209  				Description: "Skip requesting the account ID. Useful for AWS API implementations that do not have the IAM, STS API, or metadata API.",
   210  			},
   211  			"sse_customer_key": {
   212  				Type:        cty.String,
   213  				Optional:    true,
   214  				Description: "The base64-encoded encryption key to use for server-side encryption with customer-provided keys (SSE-C).",
   215  				Sensitive:   true,
   216  			},
   217  			"role_arn": {
   218  				Type:        cty.String,
   219  				Optional:    true,
   220  				Description: "The role to be assumed",
   221  				Deprecated:  true,
   222  			},
   223  			"session_name": {
   224  				Type:        cty.String,
   225  				Optional:    true,
   226  				Description: "The session name to use when assuming the role.",
   227  				Deprecated:  true,
   228  			},
   229  			"external_id": {
   230  				Type:        cty.String,
   231  				Optional:    true,
   232  				Description: "The external ID to use when assuming the role",
   233  				Deprecated:  true,
   234  			},
   235  			"assume_role_duration_seconds": {
   236  				Type:        cty.Number,
   237  				Optional:    true,
   238  				Description: "Seconds to restrict the assume role session duration.",
   239  				Deprecated:  true,
   240  			},
   241  			"assume_role_policy": {
   242  				Type:        cty.String,
   243  				Optional:    true,
   244  				Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
   245  				Deprecated:  true,
   246  			},
   247  			"assume_role_policy_arns": {
   248  				Type:        cty.Set(cty.String),
   249  				Optional:    true,
   250  				Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
   251  				Deprecated:  true,
   252  			},
   253  			"assume_role_tags": {
   254  				Type:        cty.Map(cty.String),
   255  				Optional:    true,
   256  				Description: "Assume role session tags.",
   257  				Deprecated:  true,
   258  			},
   259  			"assume_role_transitive_tag_keys": {
   260  				Type:        cty.Set(cty.String),
   261  				Optional:    true,
   262  				Description: "Assume role session tag keys to pass to any subsequent sessions.",
   263  				Deprecated:  true,
   264  			},
   265  			"workspace_key_prefix": {
   266  				Type:        cty.String,
   267  				Optional:    true,
   268  				Description: "The prefix applied to the non-default state path inside the bucket.",
   269  			},
   270  			"force_path_style": {
   271  				Type:        cty.Bool,
   272  				Optional:    true,
   273  				Description: "Force s3 to use path style api. Use `use_path_style` instead.",
   274  				Deprecated:  true,
   275  			},
   276  			"use_path_style": {
   277  				Type:        cty.Bool,
   278  				Optional:    true,
   279  				Description: "Enable path-style S3 URLs.",
   280  			},
   281  			"retry_mode": {
   282  				Type:        cty.String,
   283  				Optional:    true,
   284  				Description: "Specifies how retries are attempted. Valid values are `standard` and `adaptive`.",
   285  			},
   286  			"max_retries": {
   287  				Type:        cty.Number,
   288  				Optional:    true,
   289  				Description: "The maximum number of times an AWS API request is retried on retryable failure.",
   290  			},
   291  			"use_legacy_workflow": {
   292  				Type:        cty.Bool,
   293  				Optional:    true,
   294  				Description: "Use the legacy authentication workflow, preferring environment variables over backend configuration.",
   295  				Deprecated:  true,
   296  			},
   297  			"custom_ca_bundle": {
   298  				Type:        cty.String,
   299  				Optional:    true,
   300  				Description: "File containing custom root and intermediate certificates. Can also be configured using the `AWS_CA_BUNDLE` environment variable.",
   301  			},
   302  			"ec2_metadata_service_endpoint": {
   303  				Type:        cty.String,
   304  				Optional:    true,
   305  				Description: "The endpoint of IMDS.",
   306  			},
   307  			"ec2_metadata_service_endpoint_mode": {
   308  				Type:        cty.String,
   309  				Optional:    true,
   310  				Description: "The endpoint mode of IMDS. Valid values: IPv4, IPv6.",
   311  			},
   312  			"assume_role": {
   313  				Optional: true,
   314  				NestedType: &configschema.Object{
   315  					Nesting: configschema.NestingSingle,
   316  					Attributes: map[string]*configschema.Attribute{
   317  						"role_arn": {
   318  							Type:        cty.String,
   319  							Required:    true,
   320  							Description: "The role to be assumed.",
   321  						},
   322  						"duration": {
   323  							Type:        cty.String,
   324  							Optional:    true,
   325  							Description: "Seconds to restrict the assume role session duration.",
   326  						},
   327  						"external_id": {
   328  							Type:        cty.String,
   329  							Optional:    true,
   330  							Description: "The external ID to use when assuming the role",
   331  						},
   332  						"policy": {
   333  							Type:        cty.String,
   334  							Optional:    true,
   335  							Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
   336  						},
   337  						"policy_arns": {
   338  							Type:        cty.Set(cty.String),
   339  							Optional:    true,
   340  							Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
   341  						},
   342  						"session_name": {
   343  							Type:        cty.String,
   344  							Optional:    true,
   345  							Description: "The session name to use when assuming the role.",
   346  						},
   347  						"tags": {
   348  							Type:        cty.Map(cty.String),
   349  							Optional:    true,
   350  							Description: "Assume role session tags.",
   351  						},
   352  						"transitive_tag_keys": {
   353  							Type:        cty.Set(cty.String),
   354  							Optional:    true,
   355  							Description: "Assume role session tag keys to pass to any subsequent sessions.",
   356  						},
   357  						//
   358  						// NOT SUPPORTED by `aws-sdk-go-base/v1`
   359  						// Cannot be added yet.
   360  						//
   361  						// "source_identity": stringAttribute{
   362  						// 	configschema.Attribute{
   363  						// 		Type:         cty.String,
   364  						// 		Optional:     true,
   365  						// 		Description:  "Source identity specified by the principal assuming the role.",
   366  						// 		ValidateFunc: validAssumeRoleSourceIdentity,
   367  						// 	},
   368  						// },
   369  					},
   370  				},
   371  			},
   372  			"assume_role_with_web_identity": {
   373  				Optional: true,
   374  				NestedType: &configschema.Object{
   375  					Nesting: configschema.NestingSingle,
   376  					Attributes: map[string]*configschema.Attribute{
   377  						"role_arn": {
   378  							Type:        cty.String,
   379  							Optional:    true,
   380  							Description: "The Amazon Resource Name (ARN) role to assume.",
   381  						},
   382  						"web_identity_token": {
   383  							Type:        cty.String,
   384  							Optional:    true,
   385  							Sensitive:   true,
   386  							Description: "The OAuth 2.0 access token or OpenID Connect ID token that is provided by the identity provider.",
   387  						},
   388  						"web_identity_token_file": {
   389  							Type:        cty.String,
   390  							Optional:    true,
   391  							Description: "The path to a file which contains an OAuth 2.0 access token or OpenID Connect ID token that is provided by the identity provider.",
   392  						},
   393  						"session_name": {
   394  							Type:        cty.String,
   395  							Optional:    true,
   396  							Description: "The name applied to this assume-role session.",
   397  						},
   398  						"policy": {
   399  							Type:        cty.String,
   400  							Optional:    true,
   401  							Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
   402  						},
   403  						"policy_arns": {
   404  							Type:        cty.Set(cty.String),
   405  							Optional:    true,
   406  							Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
   407  						},
   408  						"duration": {
   409  							Type:        cty.String,
   410  							Optional:    true,
   411  							Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
   412  						},
   413  					},
   414  				},
   415  			},
   416  			"forbidden_account_ids": {
   417  				Type:        cty.Set(cty.String),
   418  				Optional:    true,
   419  				Description: "List of forbidden AWS account IDs.",
   420  			},
   421  			"allowed_account_ids": {
   422  				Type:        cty.Set(cty.String),
   423  				Optional:    true,
   424  				Description: "List of allowed AWS account IDs.",
   425  			},
   426  			"http_proxy": {
   427  				Type:        cty.String,
   428  				Optional:    true,
   429  				Description: "The address of an HTTP proxy to use when accessing the AWS API.",
   430  			},
   431  			"https_proxy": {
   432  				Type:        cty.String,
   433  				Optional:    true,
   434  				Description: "The address of an HTTPS proxy to use when accessing the AWS API.",
   435  			},
   436  			"no_proxy": {
   437  				Type:     cty.String,
   438  				Optional: true,
   439  				Description: `Comma-separated values which specify hosts that should be excluded from proxying.
   440  See details: https://cs.opensource.google/go/x/net/+/refs/tags/v0.17.0:http/httpproxy/proxy.go;l=38-50.`,
   441  			},
   442  			"insecure": {
   443  				Type:        cty.Bool,
   444  				Optional:    true,
   445  				Description: "Explicitly allow the backend to perform \"insecure\" SSL requests.",
   446  			},
   447  			"use_dualstack_endpoint": {
   448  				Type:        cty.Bool,
   449  				Optional:    true,
   450  				Description: "Resolve an endpoint with DualStack capability.",
   451  			},
   452  			"use_fips_endpoint": {
   453  				Type:        cty.Bool,
   454  				Optional:    true,
   455  				Description: "Resolve an endpoint with FIPS capability.",
   456  			},
   457  			"skip_s3_checksum": {
   458  				Type:        cty.Bool,
   459  				Optional:    true,
   460  				Description: "Do not include checksum when uploading S3 Objects. Useful for some S3-Compatible APIs as some of them do not support checksum checks.",
   461  			},
   462  		},
   463  	}
   464  }
   465  
   466  // PrepareConfig checks the validity of the values in the given
   467  // configuration, and inserts any missing defaults, assuming that its
   468  // structure has already been validated per the schema returned by
   469  // ConfigSchema.
   470  func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
   471  	var diags tfdiags.Diagnostics
   472  	if obj.IsNull() {
   473  		return obj, diags
   474  	}
   475  
   476  	if val := obj.GetAttr("bucket"); val.IsNull() || val.AsString() == "" {
   477  		diags = diags.Append(tfdiags.AttributeValue(
   478  			tfdiags.Error,
   479  			"Invalid bucket value",
   480  			`The "bucket" attribute value must not be empty.`,
   481  			cty.Path{cty.GetAttrStep{Name: "bucket"}},
   482  		))
   483  	}
   484  
   485  	if val := obj.GetAttr("key"); val.IsNull() || val.AsString() == "" {
   486  		diags = diags.Append(tfdiags.AttributeValue(
   487  			tfdiags.Error,
   488  			"Invalid key value",
   489  			`The "key" attribute value must not be empty.`,
   490  			cty.Path{cty.GetAttrStep{Name: "key"}},
   491  		))
   492  	} else if strings.HasPrefix(val.AsString(), "/") || strings.HasSuffix(val.AsString(), "/") {
   493  		// S3 will strip leading slashes from an object, so while this will
   494  		// technically be accepted by S3, it will break our workspace hierarchy.
   495  		// S3 will recognize objects with a trailing slash as a directory
   496  		// so they should not be valid keys
   497  		diags = diags.Append(tfdiags.AttributeValue(
   498  			tfdiags.Error,
   499  			"Invalid key value",
   500  			`The "key" attribute value must not start or end with with "/".`,
   501  			cty.Path{cty.GetAttrStep{Name: "key"}},
   502  		))
   503  	}
   504  
   505  	if val := obj.GetAttr("region"); val.IsNull() || val.AsString() == "" {
   506  		if os.Getenv("AWS_REGION") == "" && os.Getenv("AWS_DEFAULT_REGION") == "" {
   507  			diags = diags.Append(tfdiags.AttributeValue(
   508  				tfdiags.Error,
   509  				"Missing region value",
   510  				`The "region" attribute or the "AWS_REGION" or "AWS_DEFAULT_REGION" environment variables must be set.`,
   511  				cty.Path{cty.GetAttrStep{Name: "region"}},
   512  			))
   513  		}
   514  	}
   515  
   516  	if val := obj.GetAttr("kms_key_id"); !val.IsNull() && val.AsString() != "" {
   517  		if val := obj.GetAttr("sse_customer_key"); !val.IsNull() && val.AsString() != "" {
   518  			diags = diags.Append(tfdiags.AttributeValue(
   519  				tfdiags.Error,
   520  				"Invalid encryption configuration",
   521  				encryptionKeyConflictError,
   522  				cty.Path{},
   523  			))
   524  		} else if customerKey := os.Getenv("AWS_SSE_CUSTOMER_KEY"); customerKey != "" {
   525  			diags = diags.Append(tfdiags.AttributeValue(
   526  				tfdiags.Error,
   527  				"Invalid encryption configuration",
   528  				encryptionKeyConflictEnvVarError,
   529  				cty.Path{},
   530  			))
   531  		}
   532  
   533  		diags = diags.Append(validateKMSKey(cty.Path{cty.GetAttrStep{Name: "kms_key_id"}}, val.AsString()))
   534  	}
   535  
   536  	if val := obj.GetAttr("workspace_key_prefix"); !val.IsNull() {
   537  		if v := val.AsString(); strings.HasPrefix(v, "/") || strings.HasSuffix(v, "/") {
   538  			diags = diags.Append(tfdiags.AttributeValue(
   539  				tfdiags.Error,
   540  				"Invalid workspace_key_prefix value",
   541  				`The "workspace_key_prefix" attribute value must not start with "/".`,
   542  				cty.Path{cty.GetAttrStep{Name: "workspace_key_prefix"}},
   543  			))
   544  		}
   545  	}
   546  
   547  	validateAttributesConflict(
   548  		cty.GetAttrPath("shared_credentials_file"),
   549  		cty.GetAttrPath("shared_credentials_files"),
   550  	)(obj, cty.Path{}, &diags)
   551  
   552  	attrPath := cty.GetAttrPath("shared_credentials_file")
   553  	if val := obj.GetAttr("shared_credentials_file"); !val.IsNull() {
   554  		detail := fmt.Sprintf(
   555  			`Parameter "%s" is deprecated. Use "%s" instead.`,
   556  			pathString(attrPath),
   557  			pathString(cty.GetAttrPath("shared_credentials_files")))
   558  
   559  		diags = diags.Append(attributeWarningDiag(
   560  			"Deprecated Parameter",
   561  			detail,
   562  			attrPath))
   563  	}
   564  
   565  	if val := obj.GetAttr("force_path_style"); !val.IsNull() {
   566  		attrPath := cty.GetAttrPath("force_path_style")
   567  		detail := fmt.Sprintf(
   568  			`Parameter "%s" is deprecated. Use "%s" instead.`,
   569  			pathString(attrPath),
   570  			pathString(cty.GetAttrPath("use_path_style")))
   571  
   572  		diags = diags.Append(attributeWarningDiag(
   573  			"Deprecated Parameter",
   574  			detail,
   575  			attrPath))
   576  	}
   577  
   578  	if val := obj.GetAttr("use_legacy_workflow"); !val.IsNull() {
   579  		attrPath := cty.GetAttrPath("use_legacy_workflow")
   580  		detail := fmt.Sprintf(
   581  			`Parameter "%s" is deprecated and will be removed in an upcoming minor version.`,
   582  			pathString(attrPath))
   583  
   584  		diags = diags.Append(attributeWarningDiag(
   585  			"Deprecated Parameter",
   586  			detail,
   587  			attrPath))
   588  	}
   589  
   590  	validateAttributesConflict(
   591  		cty.GetAttrPath("force_path_style"),
   592  		cty.GetAttrPath("use_path_style"),
   593  	)(obj, cty.Path{}, &diags)
   594  
   595  	var assumeRoleDeprecatedFields = map[string]string{
   596  		"role_arn":                        "assume_role.role_arn",
   597  		"session_name":                    "assume_role.session_name",
   598  		"external_id":                     "assume_role.external_id",
   599  		"assume_role_duration_seconds":    "assume_role.duration",
   600  		"assume_role_policy":              "assume_role.policy",
   601  		"assume_role_policy_arns":         "assume_role.policy_arns",
   602  		"assume_role_tags":                "assume_role.tags",
   603  		"assume_role_transitive_tag_keys": "assume_role.transitive_tag_keys",
   604  	}
   605  
   606  	if val := obj.GetAttr("assume_role"); !val.IsNull() {
   607  		diags = diags.Append(validateNestedAssumeRole(val, cty.Path{cty.GetAttrStep{Name: "assume_role"}}))
   608  
   609  		if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
   610  			diags = diags.Append(tfdiags.WholeContainingBody(
   611  				tfdiags.Error,
   612  				"Conflicting Parameters",
   613  				`The following deprecated parameters conflict with the parameter "assume_role". Replace them as follows:`+"\n"+
   614  					formatDeprecated(defined),
   615  			))
   616  		}
   617  	} else {
   618  		if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
   619  			diags = diags.Append(tfdiags.WholeContainingBody(
   620  				tfdiags.Warning,
   621  				"Deprecated Parameters",
   622  				`The following parameters have been deprecated. Replace them as follows:`+"\n"+
   623  					formatDeprecated(defined),
   624  			))
   625  		}
   626  	}
   627  
   628  	if val := obj.GetAttr("assume_role_with_web_identity"); !val.IsNull() {
   629  		diags = diags.Append(validateAssumeRoleWithWebIdentity(val, cty.GetAttrPath("assume_role_with_web_identity")))
   630  	}
   631  
   632  	validateAttributesConflict(
   633  		cty.GetAttrPath("allowed_account_ids"),
   634  		cty.GetAttrPath("forbidden_account_ids"),
   635  	)(obj, cty.Path{}, &diags)
   636  
   637  	if val := obj.GetAttr("retry_mode"); !val.IsNull() {
   638  		s := val.AsString()
   639  		if _, err := aws.ParseRetryMode(s); err != nil {
   640  			diags = diags.Append(tfdiags.AttributeValue(
   641  				tfdiags.Error,
   642  				"Invalid retry mode",
   643  				fmt.Sprintf("Valid values are %q and %q.", aws.RetryModeStandard, aws.RetryModeAdaptive),
   644  				cty.Path{cty.GetAttrStep{Name: "retry_mode"}},
   645  			))
   646  		}
   647  	}
   648  
   649  	for _, endpoint := range customEndpoints {
   650  		endpoint.Validate(obj, &diags)
   651  	}
   652  
   653  	return obj, diags
   654  }
   655  
   656  // Configure uses the provided configuration to set configuration fields
   657  // within the backend.
   658  //
   659  // The given configuration is assumed to have already been validated
   660  // against the schema returned by ConfigSchema and passed validation
   661  // via PrepareConfig.
   662  func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
   663  	var diags tfdiags.Diagnostics
   664  	if obj.IsNull() {
   665  		return diags
   666  	}
   667  
   668  	var region string
   669  	if v, ok := stringAttrOk(obj, "region"); ok {
   670  		region = v
   671  	}
   672  
   673  	if region != "" && !boolAttr(obj, "skip_region_validation") {
   674  		if err := awsbaseValidation.SupportedRegion(region); err != nil {
   675  			diags = diags.Append(tfdiags.AttributeValue(
   676  				tfdiags.Error,
   677  				"Invalid region value",
   678  				err.Error(),
   679  				cty.Path{cty.GetAttrStep{Name: "region"}},
   680  			))
   681  			return diags
   682  		}
   683  	}
   684  
   685  	b.bucketName = stringAttr(obj, "bucket")
   686  	b.keyName = stringAttr(obj, "key")
   687  	b.acl = stringAttr(obj, "acl")
   688  	b.workspaceKeyPrefix = stringAttrDefault(obj, "workspace_key_prefix", "env:")
   689  	b.serverSideEncryption = boolAttr(obj, "encrypt")
   690  	b.kmsKeyID = stringAttr(obj, "kms_key_id")
   691  	b.ddbTable = stringAttr(obj, "dynamodb_table")
   692  	b.skipS3Checksum = boolAttr(obj, "skip_s3_checksum")
   693  
   694  	if customerKey, ok := stringAttrOk(obj, "sse_customer_key"); ok {
   695  		if len(customerKey) != 44 {
   696  			diags = diags.Append(tfdiags.AttributeValue(
   697  				tfdiags.Error,
   698  				"Invalid sse_customer_key value",
   699  				"sse_customer_key must be 44 characters in length",
   700  				cty.Path{cty.GetAttrStep{Name: "sse_customer_key"}},
   701  			))
   702  		} else {
   703  			var err error
   704  			if b.customerEncryptionKey, err = base64.StdEncoding.DecodeString(customerKey); err != nil {
   705  				diags = diags.Append(tfdiags.AttributeValue(
   706  					tfdiags.Error,
   707  					"Invalid sse_customer_key value",
   708  					fmt.Sprintf("sse_customer_key must be base64 encoded: %s", err),
   709  					cty.Path{cty.GetAttrStep{Name: "sse_customer_key"}},
   710  				))
   711  			}
   712  		}
   713  	} else if customerKey := os.Getenv("AWS_SSE_CUSTOMER_KEY"); customerKey != "" {
   714  		if len(customerKey) != 44 {
   715  			diags = diags.Append(tfdiags.Sourceless(
   716  				tfdiags.Error,
   717  				"Invalid AWS_SSE_CUSTOMER_KEY value",
   718  				`The environment variable "AWS_SSE_CUSTOMER_KEY" must be 44 characters in length`,
   719  			))
   720  		} else {
   721  			var err error
   722  			if b.customerEncryptionKey, err = base64.StdEncoding.DecodeString(customerKey); err != nil {
   723  				diags = diags.Append(tfdiags.Sourceless(
   724  					tfdiags.Error,
   725  					"Invalid AWS_SSE_CUSTOMER_KEY value",
   726  					fmt.Sprintf(`The environment variable "AWS_SSE_CUSTOMER_KEY" must be base64 encoded: %s`, err),
   727  				))
   728  			}
   729  		}
   730  	}
   731  
   732  	ctx := context.TODO()
   733  	ctx, baselog := attachLoggerToContext(ctx)
   734  
   735  	cfg := &awsbase.Config{
   736  		AccessKey:               stringAttr(obj, "access_key"),
   737  		CallerDocumentationURL:  "https://opentofu.org/docs/language/settings/backends/s3",
   738  		CallerName:              "S3 Backend",
   739  		IamEndpoint:             customEndpoints["iam"].String(obj),
   740  		MaxRetries:              intAttrDefault(obj, "max_retries", 5),
   741  		Profile:                 stringAttr(obj, "profile"),
   742  		Region:                  stringAttr(obj, "region"),
   743  		SecretKey:               stringAttr(obj, "secret_key"),
   744  		SkipCredsValidation:     boolAttr(obj, "skip_credentials_validation"),
   745  		SkipRequestingAccountId: boolAttr(obj, "skip_requesting_account_id"),
   746  		StsEndpoint:             customEndpoints["sts"].String(obj),
   747  		StsRegion:               stringAttr(obj, "sts_region"),
   748  		Token:                   stringAttr(obj, "token"),
   749  
   750  		// Note: we don't need to read env variables explicitly because they are read implicitly by aws-sdk-base-go:
   751  		// see: https://github.com/hashicorp/aws-sdk-go-base/blob/v2.0.0-beta.41/internal/config/config.go#L133
   752  		// which relies on: https://cs.opensource.google/go/x/net/+/refs/tags/v0.18.0:http/httpproxy/proxy.go;l=89-96
   753  		HTTPProxy:            aws.String(stringAttrDefaultEnvVar(obj, "http_proxy", "HTTP_PROXY")),
   754  		HTTPSProxy:           aws.String(stringAttrDefaultEnvVar(obj, "https_proxy", "HTTPS_PROXY")),
   755  		NoProxy:              stringAttrDefaultEnvVar(obj, "no_proxy", "NO_PROXY"),
   756  		Insecure:             boolAttr(obj, "insecure"),
   757  		UseDualStackEndpoint: boolAttr(obj, "use_dualstack_endpoint"),
   758  		UseFIPSEndpoint:      boolAttr(obj, "use_fips_endpoint"),
   759  		UserAgent: awsbase.UserAgentProducts{
   760  			{Name: "APN", Version: "1.0"},
   761  			{Name: httpclient.DefaultApplicationName, Version: version.String()},
   762  		},
   763  		CustomCABundle:                 stringAttrDefaultEnvVar(obj, "custom_ca_bundle", "AWS_CA_BUNDLE"),
   764  		EC2MetadataServiceEndpoint:     stringAttrDefaultEnvVar(obj, "ec2_metadata_service_endpoint", "AWS_EC2_METADATA_SERVICE_ENDPOINT"),
   765  		EC2MetadataServiceEndpointMode: stringAttrDefaultEnvVar(obj, "ec2_metadata_service_endpoint_mode", "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE"),
   766  		Logger:                         baselog,
   767  	}
   768  
   769  	cfg.UseLegacyWorkflow = boolAttr(obj, "use_legacy_workflow")
   770  
   771  	if val, ok := boolAttrOk(obj, "skip_metadata_api_check"); ok {
   772  		if val {
   773  			cfg.EC2MetadataServiceEnableState = imds.ClientDisabled
   774  		} else {
   775  			cfg.EC2MetadataServiceEnableState = imds.ClientEnabled
   776  		}
   777  	}
   778  
   779  	if val, ok := stringAttrOk(obj, "shared_credentials_file"); ok {
   780  		cfg.SharedCredentialsFiles = []string{val}
   781  	}
   782  
   783  	if value := obj.GetAttr("assume_role"); !value.IsNull() {
   784  		cfg.AssumeRole = configureNestedAssumeRole(obj)
   785  	} else if value := obj.GetAttr("role_arn"); !value.IsNull() {
   786  		cfg.AssumeRole = configureAssumeRole(obj)
   787  	}
   788  
   789  	if val := obj.GetAttr("assume_role_with_web_identity"); !val.IsNull() {
   790  		cfg.AssumeRoleWithWebIdentity = configureAssumeRoleWithWebIdentity(val)
   791  	}
   792  
   793  	if val, ok := stringSliceAttrDefaultEnvVarOk(obj, "shared_credentials_files", "AWS_SHARED_CREDENTIALS_FILE"); ok {
   794  		cfg.SharedCredentialsFiles = val
   795  	}
   796  	if val, ok := stringSliceAttrDefaultEnvVarOk(obj, "shared_config_files", "AWS_SHARED_CONFIG_FILE"); ok {
   797  		cfg.SharedConfigFiles = val
   798  	}
   799  
   800  	if val, ok := stringSliceAttrOk(obj, "allowed_account_ids"); ok {
   801  		cfg.AllowedAccountIds = val
   802  	}
   803  
   804  	if val, ok := stringSliceAttrOk(obj, "forbidden_account_ids"); ok {
   805  		cfg.ForbiddenAccountIds = val
   806  	}
   807  
   808  	if val, ok := stringAttrOk(obj, "retry_mode"); ok {
   809  		mode, err := aws.ParseRetryMode(val)
   810  		if err != nil {
   811  			panic(fmt.Sprintf("invalid retry mode %q: %s", val, err))
   812  		}
   813  		cfg.RetryMode = mode
   814  	}
   815  
   816  	_, awsConfig, awsDiags := awsbase.GetAwsConfig(ctx, cfg)
   817  
   818  	for _, d := range awsDiags {
   819  		diags = diags.Append(tfdiags.Sourceless(
   820  			baseSeverityToTofuSeverity(d.Severity()),
   821  			d.Summary(),
   822  			d.Detail(),
   823  		))
   824  	}
   825  
   826  	if d := verifyAllowedAccountID(ctx, awsConfig, cfg); len(d) != 0 {
   827  		diags = diags.Append(d)
   828  	}
   829  
   830  	if diags.HasErrors() {
   831  		return diags
   832  	}
   833  
   834  	b.awsConfig = awsConfig
   835  
   836  	b.dynClient = dynamodb.NewFromConfig(awsConfig, getDynamoDBConfig(obj))
   837  
   838  	b.s3Client = s3.NewFromConfig(awsConfig, getS3Config(obj))
   839  
   840  	return diags
   841  }
   842  
   843  func attachLoggerToContext(ctx context.Context) (context.Context, baselogging.HcLogger) {
   844  	ctx, baselog := baselogging.NewHcLogger(ctx, logging.HCLogger().Named("backend-s3"))
   845  	ctx = baselogging.RegisterLogger(ctx, baselog)
   846  	return ctx, baselog
   847  }
   848  
   849  func verifyAllowedAccountID(ctx context.Context, awsConfig aws.Config, cfg *awsbase.Config) tfdiags.Diagnostics {
   850  	if len(cfg.ForbiddenAccountIds) == 0 && len(cfg.AllowedAccountIds) == 0 {
   851  		return nil
   852  	}
   853  
   854  	var diags tfdiags.Diagnostics
   855  	accountID, _, awsDiags := awsbase.GetAwsAccountIDAndPartition(ctx, awsConfig, cfg)
   856  	for _, d := range awsDiags {
   857  		diags = diags.Append(tfdiags.Sourceless(
   858  			baseSeverityToTofuSeverity(d.Severity()),
   859  			fmt.Sprintf("Retrieving AWS account details: %s", d.Summary()),
   860  			d.Detail(),
   861  		))
   862  	}
   863  
   864  	err := cfg.VerifyAccountIDAllowed(accountID)
   865  	if err != nil {
   866  		diags = diags.Append(tfdiags.Sourceless(
   867  			tfdiags.Error,
   868  			"Invalid account ID",
   869  			err.Error(),
   870  		))
   871  	}
   872  	return diags
   873  }
   874  
   875  func getDynamoDBConfig(obj cty.Value) func(options *dynamodb.Options) {
   876  	return func(options *dynamodb.Options) {
   877  		if v, ok := customEndpoints["dynamodb"].StringOk(obj); ok {
   878  			options.BaseEndpoint = aws.String(v)
   879  		}
   880  	}
   881  }
   882  
   883  func getS3Config(obj cty.Value) func(options *s3.Options) {
   884  	return func(options *s3.Options) {
   885  		if v, ok := customEndpoints["s3"].StringOk(obj); ok {
   886  			options.BaseEndpoint = aws.String(v)
   887  		}
   888  		if v, ok := boolAttrOk(obj, "force_path_style"); ok {
   889  			options.UsePathStyle = v
   890  		}
   891  		if v, ok := boolAttrOk(obj, "use_path_style"); ok {
   892  			options.UsePathStyle = v
   893  		}
   894  	}
   895  }
   896  
   897  func configureNestedAssumeRole(obj cty.Value) *awsbase.AssumeRole {
   898  	assumeRole := awsbase.AssumeRole{}
   899  
   900  	obj = obj.GetAttr("assume_role")
   901  	if val, ok := stringAttrOk(obj, "role_arn"); ok {
   902  		assumeRole.RoleARN = val
   903  	}
   904  	if val, ok := stringAttrOk(obj, "duration"); ok {
   905  		dur, err := time.ParseDuration(val)
   906  		if err != nil {
   907  			// This should never happen because the schema should have
   908  			// already validated the duration.
   909  			panic(fmt.Sprintf("invalid duration %q: %s", val, err))
   910  		}
   911  
   912  		assumeRole.Duration = dur
   913  	}
   914  	if val, ok := stringAttrOk(obj, "external_id"); ok {
   915  		assumeRole.ExternalID = val
   916  	}
   917  
   918  	if val, ok := stringAttrOk(obj, "policy"); ok {
   919  		assumeRole.Policy = strings.TrimSpace(val)
   920  	}
   921  	if val, ok := stringSliceAttrOk(obj, "policy_arns"); ok {
   922  		assumeRole.PolicyARNs = val
   923  	}
   924  	if val, ok := stringAttrOk(obj, "session_name"); ok {
   925  		assumeRole.SessionName = val
   926  	}
   927  	if val, ok := stringMapAttrOk(obj, "tags"); ok {
   928  		assumeRole.Tags = val
   929  	}
   930  	if val, ok := stringSliceAttrOk(obj, "transitive_tag_keys"); ok {
   931  		assumeRole.TransitiveTagKeys = val
   932  	}
   933  
   934  	return &assumeRole
   935  }
   936  
   937  func configureAssumeRole(obj cty.Value) *awsbase.AssumeRole {
   938  	assumeRole := awsbase.AssumeRole{}
   939  
   940  	assumeRole.RoleARN = stringAttr(obj, "role_arn")
   941  	assumeRole.Duration = time.Duration(int64(intAttr(obj, "assume_role_duration_seconds")) * int64(time.Second))
   942  	assumeRole.ExternalID = stringAttr(obj, "external_id")
   943  	assumeRole.Policy = stringAttr(obj, "assume_role_policy")
   944  	assumeRole.SessionName = stringAttr(obj, "session_name")
   945  
   946  	if val, ok := stringSliceAttrOk(obj, "assume_role_policy_arns"); ok {
   947  		assumeRole.PolicyARNs = val
   948  	}
   949  	if val, ok := stringMapAttrOk(obj, "assume_role_tags"); ok {
   950  		assumeRole.Tags = val
   951  	}
   952  	if val, ok := stringSliceAttrOk(obj, "assume_role_transitive_tag_keys"); ok {
   953  		assumeRole.TransitiveTagKeys = val
   954  	}
   955  
   956  	return &assumeRole
   957  }
   958  
   959  func configureAssumeRoleWithWebIdentity(obj cty.Value) *awsbase.AssumeRoleWithWebIdentity {
   960  	cfg := &awsbase.AssumeRoleWithWebIdentity{
   961  		RoleARN:              stringAttrDefaultEnvVar(obj, "role_arn", "AWS_ROLE_ARN"),
   962  		Policy:               stringAttr(obj, "policy"),
   963  		PolicyARNs:           stringSliceAttr(obj, "policy_arns"),
   964  		SessionName:          stringAttrDefaultEnvVar(obj, "session_name", "AWS_ROLE_SESSION_NAME"),
   965  		WebIdentityToken:     stringAttrDefaultEnvVar(obj, "web_identity_token", "AWS_WEB_IDENTITY_TOKEN"),
   966  		WebIdentityTokenFile: stringAttrDefaultEnvVar(obj, "web_identity_token_file", "AWS_WEB_IDENTITY_TOKEN_FILE"),
   967  	}
   968  	if val, ok := stringAttrOk(obj, "duration"); ok {
   969  		d, err := time.ParseDuration(val)
   970  		if err != nil {
   971  			// This should never happen because the schema should have
   972  			// already validated the duration.
   973  			panic(fmt.Sprintf("invalid duration %q: %s", val, err))
   974  		}
   975  		cfg.Duration = d
   976  	}
   977  	return cfg
   978  }
   979  
   980  func stringValue(val cty.Value) string {
   981  	v, _ := stringValueOk(val)
   982  	return v
   983  }
   984  
   985  func stringValueOk(val cty.Value) (string, bool) {
   986  	if val.IsNull() {
   987  		return "", false
   988  	} else {
   989  		return val.AsString(), true
   990  	}
   991  }
   992  
   993  func stringAttr(obj cty.Value, name string) string {
   994  	return stringValue(obj.GetAttr(name))
   995  }
   996  
   997  func stringAttrOk(obj cty.Value, name string) (string, bool) {
   998  	return stringValueOk(obj.GetAttr(name))
   999  }
  1000  
  1001  func stringAttrDefault(obj cty.Value, name, def string) string {
  1002  	if v, ok := stringAttrOk(obj, name); !ok {
  1003  		return def
  1004  	} else {
  1005  		return v
  1006  	}
  1007  }
  1008  
  1009  func stringSliceValue(val cty.Value) []string {
  1010  	v, _ := stringSliceValueOk(val)
  1011  	return v
  1012  }
  1013  
  1014  func stringSliceValueOk(val cty.Value) ([]string, bool) {
  1015  	if val.IsNull() {
  1016  		return nil, false
  1017  	}
  1018  
  1019  	var v []string
  1020  	if err := gocty.FromCtyValue(val, &v); err != nil {
  1021  		return nil, false
  1022  	}
  1023  	return v, true
  1024  }
  1025  
  1026  func stringSliceAttr(obj cty.Value, name string) []string {
  1027  	return stringSliceValue(obj.GetAttr(name))
  1028  }
  1029  
  1030  func stringSliceAttrOk(obj cty.Value, name string) ([]string, bool) {
  1031  	return stringSliceValueOk(obj.GetAttr(name))
  1032  }
  1033  
  1034  func stringSliceAttrDefaultEnvVarOk(obj cty.Value, name string, envvars ...string) ([]string, bool) {
  1035  	if v, ok := stringSliceAttrOk(obj, name); !ok {
  1036  		for _, envvar := range envvars {
  1037  			if ev := os.Getenv(envvar); ev != "" {
  1038  				return []string{ev}, true
  1039  			}
  1040  		}
  1041  		return nil, false
  1042  	} else {
  1043  		return v, true
  1044  	}
  1045  }
  1046  
  1047  func stringAttrDefaultEnvVar(obj cty.Value, name string, envvars ...string) string {
  1048  	if v, ok := stringAttrDefaultEnvVarOk(obj, name, envvars...); !ok {
  1049  		return ""
  1050  	} else {
  1051  		return v
  1052  	}
  1053  }
  1054  
  1055  func stringAttrDefaultEnvVarOk(obj cty.Value, name string, envvars ...string) (string, bool) {
  1056  	if v, ok := stringAttrOk(obj, name); !ok {
  1057  		for _, envvar := range envvars {
  1058  			if v := os.Getenv(envvar); v != "" {
  1059  				return v, true
  1060  			}
  1061  		}
  1062  		return "", false
  1063  	} else {
  1064  		return v, true
  1065  	}
  1066  }
  1067  
  1068  func boolAttr(obj cty.Value, name string) bool {
  1069  	v, _ := boolAttrOk(obj, name)
  1070  	return v
  1071  }
  1072  
  1073  func boolAttrOk(obj cty.Value, name string) (bool, bool) {
  1074  	if val := obj.GetAttr(name); val.IsNull() {
  1075  		return false, false
  1076  	} else {
  1077  		return val.True(), true
  1078  	}
  1079  }
  1080  
  1081  func intAttr(obj cty.Value, name string) int {
  1082  	v, _ := intAttrOk(obj, name)
  1083  	return v
  1084  }
  1085  
  1086  func intAttrOk(obj cty.Value, name string) (int, bool) {
  1087  	if val := obj.GetAttr(name); val.IsNull() {
  1088  		return 0, false
  1089  	} else {
  1090  		var v int
  1091  		if err := gocty.FromCtyValue(val, &v); err != nil {
  1092  			return 0, false
  1093  		}
  1094  		return v, true
  1095  	}
  1096  }
  1097  
  1098  func intAttrDefault(obj cty.Value, name string, def int) int {
  1099  	if v, ok := intAttrOk(obj, name); !ok {
  1100  		return def
  1101  	} else {
  1102  		return v
  1103  	}
  1104  }
  1105  
  1106  func stringMapValueOk(val cty.Value) (map[string]string, bool) {
  1107  	var m map[string]string
  1108  	err := gocty.FromCtyValue(val, &m)
  1109  	if err != nil {
  1110  		return nil, false
  1111  	}
  1112  	return m, true
  1113  }
  1114  
  1115  func stringMapAttrOk(obj cty.Value, name string) (map[string]string, bool) {
  1116  	return stringMapValueOk(obj.GetAttr(name))
  1117  }
  1118  
  1119  func customEndpointAttrDefaultEnvVarOk(obj cty.Value, endpointsKey, deprecatedKey string, envvars ...string) (string, bool) {
  1120  	if val := obj.GetAttr("endpoints"); !val.IsNull() {
  1121  		if v, ok := stringAttrDefaultEnvVarOk(val, endpointsKey, envvars...); ok {
  1122  			return v, true
  1123  		}
  1124  	}
  1125  	return stringAttrDefaultEnvVarOk(obj, deprecatedKey, envvars...)
  1126  }
  1127  
  1128  func pathString(path cty.Path) string {
  1129  	var buf strings.Builder
  1130  	for i, step := range path {
  1131  		switch x := step.(type) {
  1132  		case cty.GetAttrStep:
  1133  			if i != 0 {
  1134  				buf.WriteString(".")
  1135  			}
  1136  			buf.WriteString(x.Name)
  1137  		case cty.IndexStep:
  1138  			val := x.Key
  1139  			typ := val.Type()
  1140  			var s string
  1141  			switch {
  1142  			case typ == cty.String:
  1143  				s = val.AsString()
  1144  			case typ == cty.Number:
  1145  				num := val.AsBigFloat()
  1146  				if num.IsInt() {
  1147  					s = num.Text('f', -1)
  1148  				} else {
  1149  					s = num.String()
  1150  				}
  1151  			default:
  1152  				s = fmt.Sprintf("<unexpected index: %s>", typ.FriendlyName())
  1153  			}
  1154  			buf.WriteString(fmt.Sprintf("[%s]", s))
  1155  		default:
  1156  			if i != 0 {
  1157  				buf.WriteString(".")
  1158  			}
  1159  			buf.WriteString(fmt.Sprintf("<unexpected step: %[1]T %[1]v>", x))
  1160  		}
  1161  	}
  1162  	return buf.String()
  1163  }
  1164  
  1165  func findDeprecatedFields(obj cty.Value, attrs map[string]string) map[string]string {
  1166  	defined := make(map[string]string)
  1167  	for attr, v := range attrs {
  1168  		if val := obj.GetAttr(attr); !val.IsNull() {
  1169  			defined[attr] = v
  1170  		}
  1171  	}
  1172  	return defined
  1173  }
  1174  
  1175  func formatDeprecated(attrs map[string]string) string {
  1176  	var maxLen int
  1177  	var buf strings.Builder
  1178  
  1179  	names := make([]string, 0, len(attrs))
  1180  	for deprecated, replacement := range attrs {
  1181  		names = append(names, deprecated)
  1182  		if l := len(deprecated); l > maxLen {
  1183  			maxLen = l
  1184  		}
  1185  
  1186  		fmt.Fprintf(&buf, "  * %-[1]*[2]s -> %s\n", maxLen, deprecated, replacement)
  1187  	}
  1188  
  1189  	sort.Strings(names)
  1190  
  1191  	return buf.String()
  1192  }
  1193  
  1194  const encryptionKeyConflictError = `Only one of "kms_key_id" and "sse_customer_key" can be set.
  1195  
  1196  The "kms_key_id" is used for encryption with KMS-Managed Keys (SSE-KMS)
  1197  while "sse_customer_key" is used for encryption with customer-managed keys (SSE-C).
  1198  Please choose one or the other.`
  1199  
  1200  const encryptionKeyConflictEnvVarError = `Only one of "kms_key_id" and the environment variable "AWS_SSE_CUSTOMER_KEY" can be set.
  1201  
  1202  The "kms_key_id" is used for encryption with KMS-Managed Keys (SSE-KMS)
  1203  while "AWS_SSE_CUSTOMER_KEY" is used for encryption with customer-managed keys (SSE-C).
  1204  Please choose one or the other.`
  1205  
  1206  type customEndpoint struct {
  1207  	Paths   []cty.Path
  1208  	EnvVars []string
  1209  }
  1210  
  1211  func (e customEndpoint) Validate(obj cty.Value, diags *tfdiags.Diagnostics) {
  1212  	validateAttributesConflict(e.Paths...)(obj, cty.Path{}, diags)
  1213  }
  1214  
  1215  func (e customEndpoint) String(obj cty.Value) string {
  1216  	v, _ := e.StringOk(obj)
  1217  	return v
  1218  }
  1219  
  1220  func includeProtoIfNessesary(endpoint string) string {
  1221  	if matched, _ := regexp.MatchString("[a-z]*://.*", endpoint); !matched {
  1222  		log.Printf("[DEBUG] Adding https:// prefix to endpoint '%s'", endpoint)
  1223  		endpoint = fmt.Sprintf("https://%s", endpoint)
  1224  	}
  1225  	return endpoint
  1226  }
  1227  
  1228  func (e customEndpoint) StringOk(obj cty.Value) (string, bool) {
  1229  	for _, path := range e.Paths {
  1230  		val, err := path.Apply(obj)
  1231  		if err != nil {
  1232  			continue
  1233  		}
  1234  		if s, ok := stringValueOk(val); ok {
  1235  			return includeProtoIfNessesary(s), true
  1236  		}
  1237  	}
  1238  	for _, envVar := range e.EnvVars {
  1239  		if v := os.Getenv(envVar); v != "" {
  1240  			return includeProtoIfNessesary(v), true
  1241  		}
  1242  	}
  1243  	return "", false
  1244  }
  1245  
  1246  var customEndpoints = map[string]customEndpoint{
  1247  	"s3": {
  1248  		Paths: []cty.Path{
  1249  			cty.GetAttrPath("endpoints").GetAttr("s3"),
  1250  			cty.GetAttrPath("endpoint"),
  1251  		},
  1252  		EnvVars: []string{
  1253  			"AWS_ENDPOINT_URL_S3",
  1254  			"AWS_S3_ENDPOINT",
  1255  		},
  1256  	},
  1257  	"iam": {
  1258  		Paths: []cty.Path{
  1259  			cty.GetAttrPath("endpoints").GetAttr("iam"),
  1260  			cty.GetAttrPath("iam_endpoint"),
  1261  		},
  1262  		EnvVars: []string{
  1263  			"AWS_ENDPOINT_URL_IAM",
  1264  			"AWS_IAM_ENDPOINT",
  1265  		},
  1266  	},
  1267  	"sts": {
  1268  		Paths: []cty.Path{
  1269  			cty.GetAttrPath("endpoints").GetAttr("sts"),
  1270  			cty.GetAttrPath("sts_endpoint"),
  1271  		},
  1272  		EnvVars: []string{
  1273  			"AWS_ENDPOINT_URL_STS",
  1274  			"AWS_STS_ENDPOINT",
  1275  		},
  1276  	},
  1277  	"dynamodb": {
  1278  		Paths: []cty.Path{
  1279  			cty.GetAttrPath("endpoints").GetAttr("dynamodb"),
  1280  			cty.GetAttrPath("dynamodb_endpoint"),
  1281  		},
  1282  		EnvVars: []string{
  1283  			"AWS_ENDPOINT_URL_DYNAMODB",
  1284  			"AWS_DYNAMODB_ENDPOINT",
  1285  		},
  1286  	},
  1287  }