diff --git a/internal/backend/remote-state/s3/backend.go b/internal/backend/remote-state/s3/backend.go index 98aa1c561e..c56e390a7f 100644 --- a/internal/backend/remote-state/s3/backend.go +++ b/internal/backend/remote-state/s3/backend.go @@ -37,6 +37,11 @@ func New() backend.Backend { if strings.HasPrefix(v.(string), "/") { return nil, []error{errors.New("key must not start with '/'")} } + // s3 will recognize objects with a trailing slash as a directory + // so they should not be valid keys + if strings.HasSuffix(v.(string), "/") { + return nil, []error{errors.New("key must not end with '/'")} + } return nil, nil }, }, diff --git a/internal/backend/remote-state/s3/backend_test.go b/internal/backend/remote-state/s3/backend_test.go index 1fd49c461a..230e4b89c8 100644 --- a/internal/backend/remote-state/s3/backend_test.go +++ b/internal/backend/remote-state/s3/backend_test.go @@ -326,6 +326,19 @@ func TestBackendConfig_invalidKey(t *testing.T) { if !diags.HasErrors() { t.Fatal("expected config validation error") } + + cfg = hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{ + "region": "us-west-1", + "bucket": "tf-test", + "key": "trailing-slash/", + "encrypt": true, + "dynamodb_table": "dynamoTable", + }) + + _, diags = New().PrepareConfig(cfg) + if !diags.HasErrors() { + t.Fatal("expected config validation error") + } } func TestBackendConfig_invalidSSECustomerKeyLength(t *testing.T) {