backend/s3: add [allowed|forbidden]_account_ids arguments

pull/33805/head
Jared Baker 3 years ago
parent 2dc5795597
commit 8b665ee635
No known key found for this signature in database

@ -18,7 +18,6 @@ import (
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/s3"
awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
basediag "github.com/hashicorp/aws-sdk-go-base/v2/diag"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -66,6 +65,11 @@ func (b *Backend) ConfigSchema() *configschema.Block {
Optional: true,
Description: "AWS region of the S3 Bucket and DynamoDB Table (if used).",
},
"allowed_account_ids": {
Type: cty.Set(cty.String),
Optional: true,
Description: "List of allowed AWS account IDs.",
},
"dynamodb_endpoint": {
Type: cty.String,
Optional: true,
@ -115,6 +119,11 @@ func (b *Backend) ConfigSchema() *configschema.Block {
},
},
},
"forbidden_account_ids": {
Type: cty.Set(cty.String),
Optional: true,
Description: "List of forbidden AWS account IDs.",
},
"iam_endpoint": {
Type: cty.String,
Optional: true,
@ -522,6 +531,11 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics)
endpointModeValidators.ValidateAttr(val, attrPath, &diags)
}
validateAttributesConflict(
cty.GetAttrPath("allowed_account_ids"),
cty.GetAttrPath("forbidden_account_ids"),
)(obj, cty.Path{}, &diags)
return obj, diags
}
@ -844,19 +858,19 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
cfg.RetryMode = aws.RetryMode(v)
}
if val, ok := stringSetAttrOk(obj, "allowed_account_ids"); ok {
cfg.AllowedAccountIds = val
}
if val, ok := stringSetAttrOk(obj, "forbidden_account_ids"); ok {
cfg.ForbiddenAccountIds = val
}
_ /* ctx */, awsConfig, cfgDiags := awsbase.GetAwsConfig(ctx, cfg)
for _, diag := range cfgDiags {
var severity tfdiags.Severity
switch diag.Severity() {
case basediag.SeverityError:
severity = tfdiags.Error
case basediag.SeverityWarning:
severity = tfdiags.Warning
}
for _, d := range cfgDiags {
diags = diags.Append(tfdiags.Sourceless(
severity,
diag.Summary(),
diag.Detail(),
baseSeverityToTerraformSeverity(d.Severity()),
d.Summary(),
d.Detail(),
))
}
if diags.HasErrors() {
@ -864,6 +878,24 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
}
b.awsConfig = awsConfig
accountID, _, awsDiags := awsbase.GetAwsAccountIDAndPartition(ctx, awsConfig, cfg)
for _, d := range awsDiags {
diags = append(diags, tfdiags.Sourceless(
baseSeverityToTerraformSeverity(d.Severity()),
fmt.Sprintf("Retrieving AWS account details: %s", d.Summary()),
d.Detail(),
))
}
err := cfg.VerifyAccountIDAllowed(accountID)
if err != nil {
diags = append(diags, tfdiags.Sourceless(
tfdiags.Error,
"Invalid account ID",
err.Error(),
))
}
b.dynClient = dynamodb.NewFromConfig(awsConfig, func(opts *dynamodb.Options) {
if v, ok := retrieveArgument(&diags,
newAttributeRetriever(obj, cty.GetAttrPath("endpoints").GetAttr("dynamodb")),

@ -1693,6 +1693,7 @@ func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1707,6 +1708,7 @@ func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1722,6 +1724,7 @@ func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1735,6 +1738,7 @@ func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1749,6 +1753,7 @@ role_session_name = %[2]s
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1768,6 +1773,7 @@ role_session_name = %[2]s
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1782,6 +1788,7 @@ role_session_name = %[2]s
// ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
// MockStsEndpoints: []*servicemocks.MockEndpoint{
// servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
// servicemocks.MockStsGetCallerIdentityValidEndpoint,
// },
// },
@ -1801,6 +1808,7 @@ web_identity_token_file = no-such-file
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1821,6 +1829,7 @@ web_identity_token_file = no-such-file
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1836,6 +1845,7 @@ web_identity_token_file = no-such-file
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"DurationSeconds": "3600"}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -1851,6 +1861,7 @@ web_identity_token_file = no-such-file
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"Policy": "{}"}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
@ -2183,6 +2194,15 @@ region = us-west-2
defer closeEc2Metadata()
}
ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
defer ts.Close()
tc.config["endpoints"] = map[string]any{
"sts": ts.URL,
}
if tc.SharedConfigurationFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")

@ -1062,6 +1062,23 @@ func TestBackendConfig_PrepareConfigValidation(t *testing.T) {
),
},
},
"allowed forbidden account ids conflict": {
config: cty.ObjectVal(map[string]cty.Value{
"bucket": cty.StringVal("test"),
"key": cty.StringVal("test"),
"region": cty.StringVal("us-west-2"),
"allowed_account_ids": cty.SetVal([]cty.Value{cty.StringVal("012345678901")}),
"forbidden_account_ids": cty.SetVal([]cty.Value{cty.StringVal("012345678901")}),
}),
expectedDiags: tfdiags.Diagnostics{
attributeErrDiag(
"Invalid Attribute Combination",
`Only one of allowed_account_ids, forbidden_account_ids can be set.`,
cty.Path{},
),
},
},
}
for name, tc := range cases {

@ -7,6 +7,7 @@ import (
"fmt"
"strings"
basediag "github.com/hashicorp/aws-sdk-go-base/v2/diag"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -39,3 +40,15 @@ func diagnosticsString(diags tfdiags.Diagnostics) string {
}
return buffer.String()
}
func baseSeverityToTerraformSeverity(s basediag.Severity) tfdiags.Severity {
switch s {
case basediag.SeverityWarning:
return tfdiags.Warning
case basediag.SeverityError:
return tfdiags.Error
default:
var zero tfdiags.Severity
return zero
}
}

@ -153,9 +153,11 @@ The following configuration is required:
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`).
* `allowed_account_ids` - (Optional) List of allowed AWS account IDs to prevent potential destruction of a live environment. Conflicts with `forbidden_account_ids`.
* `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.
* `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.
* `forbidden_account_ids` - (Optional) List of forbidden AWS account IDs to prevent potential destruction of a live environment. Conflicts with `allowed_account_ids`.
* `http_proxy` - (Optional) Address of an HTTP proxy to use when accessing the AWS API. Can also be set using the `HTTP_PROXY` or `HTTPS_PROXY` environment variables.
* `iam_endpoint` - (Optional, **Deprecated**) Custom endpoint for the AWS Identity and Access Management (IAM) API.
Use `endpoints.iam` instead.

Loading…
Cancel
Save