From 0f27c5023e7a8aa3dc9c3968c994391f44f5e4bb Mon Sep 17 00:00:00 2001 From: Matt Cadorette Date: Tue, 2 Apr 2024 17:45:07 -0400 Subject: [PATCH] fix: add policy_exception field_values_map (#619) Deprecates field_value_map for field_values_map. The data type needs to change for the exceptions to take effect. By deprecating field_value_map instead of changing it we will not introduce a breaking change, corrupt any current state, and avoid any required code changes due to breakage. --- docs/resources/policy_exception.md | 35 +++++++- .../current/main.tf | 50 +++++++++++ .../{ => deprecated}/main.tf | 0 ...resource_lacework_policy_exception_test.go | 90 ++++++++++++++++++- .../resource_lacework_policy_exception.go | 30 ++++++- 5 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 examples/resource_lacework_policy_exception/current/main.tf rename examples/resource_lacework_policy_exception/{ => deprecated}/main.tf (100%) diff --git a/docs/resources/policy_exception.md b/docs/resources/policy_exception.md index 85aa2b327..14528b098 100644 --- a/docs/resources/policy_exception.md +++ b/docs/resources/policy_exception.md @@ -27,6 +27,28 @@ resource "lacework_policy_exception" "example" { } ``` + +Create a Lacework Policy Exception to exempt specified `resourceTags` from policy. + +```hcl +resource "lacework_policy_exception" "example" { + policy_id = "lacework-global-73" + description = "Exception for resource tag example1 and example2" + + constraint { + field_key = "resourceTags" + field_values_map { + key = "example_tag1" + value = ["example_value", "example_value1"] + } + field_values_map { + key = "example_tag2" + value = ["example_value", "example_value1"] + } + } +} +``` + ## Argument Reference The following arguments are supported: @@ -40,7 +62,18 @@ The following arguments are supported: `constraint` supports the following arguments: * `field_key` - (Required) The key of the constraint being applied. Example for Aws polices this could be `accountIds`. -* `field_values` - (Required) The values related to the constraint key. +* `field_values` - (Optional) The values related to the constraint key. +* `field_value_map` - (Optional, **Deprecated**) FieldValueMap. See[FieldValueMap](#FieldValueMap) below for details. +* `field_values_map` - (Optional) FieldValueMap. See[FieldValuesMap](#FieldValuesMap) below for details. + +### FieldValueMap + +`field_value_map` allows defining constraint values for the `resourceTags` field key. Where `field_value_map` key +property is the name a given resource tag, and `value` includes any value that should match this exception. +**Deprecated** Use `field_values_map` instead. + +`field_values_map` allows defining constraint values for the `resourceTags` field key. Where `field_values_map` key +property is the name a given resource tag, and `value` includes a list of values that should match this exception. ## Import diff --git a/examples/resource_lacework_policy_exception/current/main.tf b/examples/resource_lacework_policy_exception/current/main.tf new file mode 100644 index 000000000..07468da3f --- /dev/null +++ b/examples/resource_lacework_policy_exception/current/main.tf @@ -0,0 +1,50 @@ +terraform { + required_providers { + lacework = { + source = "lacework/lacework" + } + } +} + +resource "lacework_policy_exception" "example" { + policy_id = "lacework-global-39" + description = var.description + constraint { + field_key = var.field_key + field_values = ["*"] + } + + constraint { + field_key = "resourceTags" + field_values_map { + key = "test" + value = ["test", "test"] + } + } +} + +variable "policy_id" { + type = string + default = "lacework-global-46" +} +variable "field_key" { + type = string + default = "accountIds" +} +variable "field_values" { + type = list(string) + default = ["*"] +} + +variable "description" { + type = string + default = "Policy Exception Created via Terraform" +} + +output "description" { + value = lacework_policy_exception.example.description +} + +output "policy_id" { + value = lacework_policy_exception.example.policy_id +} diff --git a/examples/resource_lacework_policy_exception/main.tf b/examples/resource_lacework_policy_exception/deprecated/main.tf similarity index 100% rename from examples/resource_lacework_policy_exception/main.tf rename to examples/resource_lacework_policy_exception/deprecated/main.tf diff --git a/integration/resource_lacework_policy_exception_test.go b/integration/resource_lacework_policy_exception_test.go index 1f94fbfc6..25613d4e0 100644 --- a/integration/resource_lacework_policy_exception_test.go +++ b/integration/resource_lacework_policy_exception_test.go @@ -8,13 +8,13 @@ import ( ) // TestPolicyExceptionCreate applies integration terraform: -// => '../examples/resource_lacework_policy_exception' +// => '../examples/resource_lacework_policy_exception/deprecated' // // It uses the go-sdk to verify the created policy exception, // applies an update and destroys it func TestPolicyExceptionCreate(t *testing.T) { terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ - TerraformDir: "../examples/resource_lacework_policy_exception", + TerraformDir: "../examples/resource_lacework_policy_exception/current", EnvVars: tokenEnvVar, Vars: map[string]interface{}{ "policy_id": "lacework-global-46", @@ -50,13 +50,97 @@ func TestPolicyExceptionCreate(t *testing.T) { assert.Contains(t, "Policy Exception Created via Terraform Updated", updateProps.Data.Description) + for _, c := range updateProps.Data.Constraints { + if c.FieldKey == "resourceTags" { + for _, v := range c.FieldValues { + data, ok := v.(map[string]interface{}) + assert.True(t, ok, "data in constraint should have been map[string]interface{}") + + keyValue, ok := data["key"] + assert.True(t, ok, "test key in resourceTags should have existed") + assert.Equal(t, "test", keyValue) + + dataValue, ok := data["value"] + assert.True(t, ok, "value key should have existed") + + testDataValues, ok := dataValue.([]interface{}) + assert.True(t, ok, "data values should have been []string") + assert.EqualValues(t, []interface{}{"test", "test"}, testDataValues) + } + } + } + assert.Equal(t, "Policy Exception Created via Terraform Updated", actualDescription) } // TestPolicyExceptionInvalidConstraint tests an invalid constraint returns an error and lists valid constraint fields func TestPolicyExceptionInvalidConstraint(t *testing.T) { terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ - TerraformDir: "../examples/resource_lacework_policy_exception", + TerraformDir: "../examples/resource_lacework_policy_exception/current", + EnvVars: tokenEnvVar, + Vars: map[string]interface{}{ + "policy_id": "lacework-global-46", + "description": "Policy Exception Created via Terraform", + "field_key": "invalid", + "field_values": []string{"*"}, + }, + }) + defer terraform.Destroy(t, terraformOptions) + + _, err := terraform.InitAndApplyE(t, terraformOptions) + assert.ErrorContains(t, err, "[400] fieldKey: invalid is not applicable to policy lacework-global-39. Valid fieldKey are [accountIds, arns, resourceNames, resourceTags]") +} + +// TestDeprecatedPolicyExceptionCreate applies integration terraform: +// => '../examples/resource_lacework_policy_exception/deprecated' +// +// It uses the go-sdk to verify the created policy exception, +// applies an update and destroys it +func TestDeprecatedPolicyExceptionCreate(t *testing.T) { + terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: "../examples/resource_lacework_policy_exception/deprecated", + EnvVars: tokenEnvVar, + Vars: map[string]interface{}{ + "policy_id": "lacework-global-46", + "description": "Policy Exception Created via Terraform", + "field_key": "accountIds", + "field_values": []string{"*"}, + }, + }) + defer terraform.Destroy(t, terraformOptions) + + // Create new Policy Exception + create := terraform.InitAndApplyAndIdempotent(t, terraformOptions) + actualDescription := terraform.Output(t, terraformOptions, "description") + actualPolicyID := terraform.Output(t, terraformOptions, "policy_id") + createProps := GetPolicyExceptionProps(create, actualPolicyID) + + assert.Contains(t, "Policy Exception Created via Terraform", createProps.Data.Description) + + assert.Equal(t, "Policy Exception Created via Terraform", actualDescription) + + // Update Policy Exception + terraformOptions.Vars = map[string]interface{}{ + "policy_id": "lacework-global-46", + "description": "Policy Exception Created via Terraform Updated", + "field_key": "accountIds", + "field_values": []string{"*"}, + } + + update := terraform.ApplyAndIdempotent(t, terraformOptions) + updateProps := GetPolicyExceptionProps(update, actualPolicyID) + + actualDescription = terraform.Output(t, terraformOptions, "description") + + assert.Contains(t, "Policy Exception Created via Terraform Updated", updateProps.Data.Description) + + assert.Equal(t, "Policy Exception Created via Terraform Updated", actualDescription) +} + +// TestDeprecatedPolicyExceptionInvalidConstraint tests an invalid constraint returns an error and lists valid constraint fields +func TestDeprecatedPolicyExceptionInvalidConstraint(t *testing.T) { + terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ + TerraformDir: "../examples/resource_lacework_policy_exception/deprecated", EnvVars: tokenEnvVar, Vars: map[string]interface{}{ "policy_id": "lacework-global-46", diff --git a/lacework/resource_lacework_policy_exception.go b/lacework/resource_lacework_policy_exception.go index ddc19beec..29d768f7c 100644 --- a/lacework/resource_lacework_policy_exception.go +++ b/lacework/resource_lacework_policy_exception.go @@ -57,10 +57,36 @@ func resourceLaceworkPolicyException() *schema.Resource { }, }, }, + "field_values_map": { + Type: schema.TypeSet, + Optional: true, + Description: "A list of key values pairs to filter the policy exception", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Description: "The values map key", + Required: true, + }, + "value": { + Type: schema.TypeList, + Description: "The values map value list", + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + StateFunc: func(val interface{}) string { + return strings.TrimSpace(val.(string)) + }, + }, + }, + }, + }, + }, "field_value_map": { Type: schema.TypeSet, Optional: true, Description: "A list of key value pairs to filter the policy exception", + Deprecated: "This attribute is deprecated and has been replaced by `field_values_map`", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "key": { @@ -247,7 +273,7 @@ func sanitizeConstraintKeys(itemMap map[string]any) map[string]any { var newMap = make(map[string]any) var constraintMapList []any for k, v := range itemMap { - if k == "field_value_map" { + if k == "field_value_map" || k == "field_values_map" { list := v.(*schema.Set).List() if len(list) > 0 { constraintMapList = append(constraintMapList, list...) @@ -256,7 +282,7 @@ func sanitizeConstraintKeys(itemMap map[string]any) map[string]any { } } newKey := strings.Replace(k, "_", "", -1) - if newKey != "fieldvaluemap" { + if newKey != "fieldvaluemap" && newKey != "fieldvaluesmap" { newMap[newKey] = v } }