mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
S3 backend add account ID whitelisting arguments (#760)
Signed-off-by: tomasmik <tomasmik@protonmail.com>
This commit is contained in:
parent
080d89c9b6
commit
e1b3b4ff82
@ -53,6 +53,7 @@ S3 BACKEND:
|
||||
* Adds support for `shared_config_files` and `shared_credentials_files` arguments and deprecates the `shared_credentials_file` argument. ([#690](https://github.com/opentofu/opentofu/issues/690))
|
||||
* Arguments associated with assuming an IAM role were moved into a nested block - `assume_role`.
|
||||
This deprecates the arguments `role_arn`, `session_name`, `external_id`, `assume_role_duration_seconds`, `assume_role_policy`, `assume_role_policy_arns`, `assume_role_tags`, and `assume_role_transitive_tag_keys`. ([#747](https://github.com/opentofu/opentofu/issues/747))
|
||||
* Adds support for account whitelisting using the `forbidden_account_ids` and `allowed_account_ids` arguments. ([#699](https://github.com/opentofu/opentofu/issues/699))
|
||||
|
||||
## Previous Releases
|
||||
|
||||
|
@ -297,6 +297,16 @@ func (b *Backend) ConfigSchema() *configschema.Block {
|
||||
},
|
||||
},
|
||||
},
|
||||
"forbidden_account_ids": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "List of forbidden AWS account IDs.",
|
||||
},
|
||||
"allowed_account_ids": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "List of allowed AWS account IDs.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -433,6 +443,11 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics)
|
||||
}
|
||||
}
|
||||
|
||||
validateAttributesConflict(
|
||||
cty.GetAttrPath("allowed_account_ids"),
|
||||
cty.GetAttrPath("forbidden_account_ids"),
|
||||
)(obj, cty.Path{}, &diags)
|
||||
|
||||
return obj, diags
|
||||
}
|
||||
|
||||
@ -569,6 +584,14 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
cfg.SharedConfigFiles = val
|
||||
}
|
||||
|
||||
if val, ok := stringSliceAttrOk(obj, "allowed_account_ids"); ok {
|
||||
cfg.AllowedAccountIds = val
|
||||
}
|
||||
|
||||
if val, ok := stringSliceAttrOk(obj, "forbidden_account_ids"); ok {
|
||||
cfg.ForbiddenAccountIds = val
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
_, awsConfig, awsDiags := awsbase.GetAwsConfig(ctx, cfg)
|
||||
|
||||
@ -580,6 +603,10 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
))
|
||||
}
|
||||
|
||||
if d := verifyAllowedAccountID(ctx, awsConfig, cfg); len(d) != 0 {
|
||||
diags = diags.Append(d)
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
@ -593,6 +620,28 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
return diags
|
||||
}
|
||||
|
||||
func verifyAllowedAccountID(ctx context.Context, awsConfig aws.Config, cfg *awsbase.Config) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
accountID, _, awsDiags := awsbase.GetAwsAccountIDAndPartition(ctx, awsConfig, cfg)
|
||||
for _, d := range awsDiags {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
baseSeverityToTofuSeverity(d.Severity()),
|
||||
fmt.Sprintf("Retrieving AWS account details: %s", d.Summary()),
|
||||
d.Detail(),
|
||||
))
|
||||
}
|
||||
|
||||
err := cfg.VerifyAccountIDAllowed(accountID)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Invalid account ID",
|
||||
err.Error(),
|
||||
))
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
func getDynamoDBConfig(obj cty.Value) func(options *dynamodb.Options) {
|
||||
return func(options *dynamodb.Options) {
|
||||
if v, ok := stringAttrDefaultEnvVarOk(obj, "dynamodb_endpoint", "AWS_DYNAMODB_ENDPOINT", "AWS_ENDPOINT_URL_DYNAMODB"); ok {
|
||||
|
@ -156,6 +156,9 @@ func TestBackendConfig_Authentication(t *testing.T) {
|
||||
}{
|
||||
"empty config": {
|
||||
config: map[string]any{},
|
||||
MockStsEndpoints: []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
},
|
||||
ValidateDiags: ExpectDiagsMatching(
|
||||
tfdiags.Error,
|
||||
equalsMatcher("No valid credential sources found"),
|
||||
@ -175,6 +178,23 @@ func TestBackendConfig_Authentication(t *testing.T) {
|
||||
ValidateDiags: ExpectNoDiags,
|
||||
},
|
||||
|
||||
"config AccessKey forbidden account": {
|
||||
config: map[string]any{
|
||||
"access_key": servicemocks.MockStaticAccessKey,
|
||||
"secret_key": servicemocks.MockStaticSecretKey,
|
||||
"forbidden_account_ids": []any{"222222222222"},
|
||||
},
|
||||
MockStsEndpoints: []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
},
|
||||
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
|
||||
ValidateDiags: ExpectDiagsMatching(
|
||||
tfdiags.Error,
|
||||
equalsMatcher("Invalid account ID"),
|
||||
equalsMatcher("AWS account ID not allowed: 222222222222"),
|
||||
),
|
||||
},
|
||||
|
||||
"config Profile shared credentials profile aws_access_key_id": {
|
||||
config: map[string]any{
|
||||
"profile": "SharedCredentialsProfile",
|
||||
@ -545,6 +565,9 @@ region = us-east-1
|
||||
EnvironmentVariables: map[string]string{
|
||||
"AWS_PROFILE": "no-such-profile",
|
||||
},
|
||||
MockStsEndpoints: []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
},
|
||||
SharedCredentialsFile: `
|
||||
[some-profile]
|
||||
aws_access_key_id = DefaultSharedCredentialsAccessKey
|
||||
@ -566,6 +589,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
||||
aws_access_key_id = DefaultSharedCredentialsAccessKey
|
||||
aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
||||
`,
|
||||
MockStsEndpoints: []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
},
|
||||
ValidateDiags: ExpectDiagsMatching(
|
||||
tfdiags.Error,
|
||||
equalsMatcher("failed to get shared config profile, no-such-profile"),
|
||||
@ -603,6 +629,9 @@ aws_secret_access_key = ProfileSharedCredentialsSecretKey
|
||||
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
|
||||
"AWS_PROFILE": "no-such-profile",
|
||||
},
|
||||
MockStsEndpoints: []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
},
|
||||
SharedCredentialsFile: `
|
||||
[some-profile]
|
||||
aws_access_key_id = DefaultSharedCredentialsAccessKey
|
||||
@ -1766,6 +1795,13 @@ region = us-west-2
|
||||
defer closeEc2Metadata()
|
||||
}
|
||||
|
||||
sts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
|
||||
servicemocks.MockStsGetCallerIdentityValidEndpoint,
|
||||
})
|
||||
defer sts.Close()
|
||||
|
||||
tc.config["sts_endpoint"] = sts.URL
|
||||
|
||||
if tc.SharedConfigurationFile != "" {
|
||||
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")
|
||||
|
||||
|
@ -713,6 +713,16 @@ func TestBackendConfig_PrepareConfigValidation(t *testing.T) {
|
||||
}),
|
||||
expectedErr: `Only one of "kms_key_id" and "sse_customer_key" can be set`,
|
||||
},
|
||||
"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("111111111111")}),
|
||||
"forbidden_account_ids": cty.SetVal([]cty.Value{cty.StringVal("111111111111")}),
|
||||
}),
|
||||
expectedErr: "Invalid Attribute Combination: Only one of allowed_account_ids, forbidden_account_ids can be set.",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
|
@ -170,6 +170,8 @@ The following configuration is optional:
|
||||
* `skip_metadata_api_check` - (Optional) Skip usage of EC2 Metadata API.
|
||||
* `sts_endpoint` - (Optional) Custom endpoint for the AWS Security Token Service (STS) API. This can also be sourced from the `AWS_STS_ENDPOINT` environment variable.
|
||||
* `token` - (Optional) Multi-Factor Authentication (MFA) token. This can also be sourced from the `AWS_SESSION_TOKEN` environment variable.
|
||||
* `allowed_account_ids` (Optional): A list of permitted AWS account IDs to safeguard against accidental disruption of a live environment. This option conflicts with `forbidden_account_ids`.
|
||||
* `forbidden_account_ids` (Optional): A list of prohibited AWS account IDs to prevent unintentional disruption of a live environment. This option conflicts with `allowed_account_ids`.
|
||||
* `use_legacy_workflow` - (Optional) Prefer environment variables for legacy authentication; default is 'true.' This method doesn't match AWS CLI or SDK authentication and will be removed in the future.
|
||||
|
||||
#### Assume Role Configuration
|
||||
|
Loading…
Reference in New Issue
Block a user