diff --git a/README.md b/README.md index e0d7984..0ebcc2d 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,11 @@ This module configures an IBM Cloud Security and Compliance instance. * [terraform-ibm-scc](#terraform-ibm-scc) * [Submodules](./modules) * [attachment](./modules/attachment) + * [rules](./modules/rules) * [Examples](./examples) * [Basic example](./examples/basic) + * [Complete example](./examples/complete) + * [Custom example](./examples/custom) * [Complete example with CBR rules](./examples/complete) * [Contributing](#contributing) diff --git a/examples/custom/README.md b/examples/custom/README.md new file mode 100644 index 0000000..951c0a1 --- /dev/null +++ b/examples/custom/README.md @@ -0,0 +1,12 @@ +# Custom example + + + +A basic example that will provision the following: +- A new resource group if one is not passed in. +- A new Security and Compliance Center instance with COS bucket configuration +- Creates 3 new custom SCC rules for a Security and Compliance Center instance diff --git a/examples/custom/main.tf b/examples/custom/main.tf new file mode 100644 index 0000000..3d72639 --- /dev/null +++ b/examples/custom/main.tf @@ -0,0 +1,136 @@ +module "resource_group" { + source = "terraform-ibm-modules/resource-group/ibm" + version = "1.1.5" + resource_group_name = var.resource_group == null ? "${var.prefix}-resource-group" : null + existing_resource_group_name = var.resource_group +} + +module "cos" { + source = "terraform-ibm-modules/cos/ibm" + version = "7.5.1" + cos_instance_name = "${var.prefix}-cos" + kms_encryption_enabled = false + retention_enabled = false + resource_group_id = module.resource_group.resource_group_id + bucket_name = "${var.prefix}-cb" +} + +module "create_scc_instance" { + source = "../.." + instance_name = "${var.prefix}-instance" + region = var.region + resource_group_id = module.resource_group.resource_group_id + resource_tags = var.resource_tags + cos_bucket = module.cos.bucket_name + cos_instance_crn = module.cos.cos_instance_id + skip_cos_iam_authorization_policy = false +} + +module "create_scc_rules" { + source = "../../modules/rules" + scc_instance_id = module.create_scc_instance.guid + rules_version = "1.0.0" + rules = [ + { + description = "new rule 1" + import = { + parameters = [] + } + required_config = { + description = "restrict endpoints" + and = [ + { + property = "endpoints_restricted", + operator = "is_true" + } + ] + } + target = { + service_name = "kms" + resource_kind = "instance" + additional_target_attributes = [ + { + "name" : "location", + operator : "string_equals", + value : "us-south" + } + ] + } + }, + { + description = "new rule 2" + import = { + parameters = [] + } + required_config = { + description = "required config" + and = [ + { + property = "cloud_directory_enabled", + operator = "is_true" + }, + { + property = "email_dispatcher_provider", + operator = "string_not_equals" + value = "appid" + } + ] + } + target = { + service_name = "appid", + service_display_name = "App ID", + resource_kind = "instance", + additional_target_attributes = [] + } + }, + { + description = "new rule 3" + import = { + parameters = [] + } + required_config = { + description = "required config" + or = [ + { + and = [ + { + property : "endpoints_restricted", + operator : "is_true" + }, + { + property : "cbr_private_public_allowed_ip_list", + operator : "is_empty" + } + ] + }, + { + and = [ + { + property : "endpoints_restricted", + operator : "is_true" + }, + { + property : "cbr_private_public_allowed_ip_list", + operator : "is_not_empty" + }, + ] + }, + { + and = [ + { + property : "firewall.allowed_ip", + operator : "is_not_empty" + }, + ] + } + ] + } + target = { + service_name = "cloud-object-storage", + service_display_name = "Cloud Object Storage", + resource_kind = "bucket", + additional_target_attributes = [] + } + } + ] +} diff --git a/examples/custom/outputs.tf b/examples/custom/outputs.tf new file mode 100644 index 0000000..a167643 --- /dev/null +++ b/examples/custom/outputs.tf @@ -0,0 +1,53 @@ +######################################################################################################################## +# Outputs +######################################################################################################################## + +output "resource_group_id" { + description = "The id of the resource group where SCC instance is created by this module" + value = module.resource_group.resource_group_id +} + +output "id" { + description = "The id of the SCC instance created by this module" + value = module.create_scc_instance.id +} + +output "guid" { + description = "The GUID of the SCC instance created by this module" + value = module.create_scc_instance.guid +} + +output "crn" { + description = "The CRN of the SCC instance created by this module" + value = module.create_scc_instance.crn +} + +output "name" { + description = "The name of the SCC instance created by this module" + value = module.create_scc_instance.name +} + +output "location" { + description = "The location of the SCC instance created by this module" + value = module.create_scc_instance.location +} + +output "plan" { + description = "The pricing plan used to create SCC instance in this module" + value = module.create_scc_instance.plan +} + +output "cos_instance_id" { + description = "The COS instance ID created in this example" + value = module.cos.cos_instance_id +} + +output "cos_bucket" { + description = "The COS bucket created in this example" + value = module.cos.bucket_name +} + +output "rule_ids" { + description = "The unique identifier of the scc_rules created by this module." + value = module.create_scc_rules.rule_ids +} diff --git a/examples/custom/provider.tf b/examples/custom/provider.tf new file mode 100644 index 0000000..84b6985 --- /dev/null +++ b/examples/custom/provider.tf @@ -0,0 +1,8 @@ +######################################################################################################################## +# Provider config +######################################################################################################################## + +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region +} diff --git a/examples/custom/variables.tf b/examples/custom/variables.tf new file mode 100644 index 0000000..8c44c2a --- /dev/null +++ b/examples/custom/variables.tf @@ -0,0 +1,33 @@ +######################################################################################################################## +# Input variables +######################################################################################################################## + +variable "ibmcloud_api_key" { + type = string + description = "The IBM Cloud API Key" + sensitive = true +} + +variable "region" { + type = string + description = "Region to provision all resources created by this example" + default = "us-south" +} + +variable "prefix" { + type = string + description = "Prefix to append to all resources created by this example" + default = "scc" +} + +variable "resource_group" { + type = string + description = "The name of an existing resource group to provision resources in to. If not set a new resource group will be created using the prefix variable" + default = null +} + +variable "resource_tags" { + type = list(string) + description = "Optional list of tags to be added to created resources" + default = [] +} diff --git a/examples/custom/version.tf b/examples/custom/version.tf new file mode 100644 index 0000000..98294c3 --- /dev/null +++ b/examples/custom/version.tf @@ -0,0 +1,12 @@ +terraform { + required_version = ">= 1.3.0, <1.7.0" + + # Ensure that there is always 1 example locked into the lowest provider version of the range defined in the main + # module's version.tf (usually a basic example), and 1 example that will always use the latest provider version. + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = "1.63.0" + } + } +} diff --git a/modules/rules/README.md b/modules/rules/README.md new file mode 100644 index 0000000..f6fe5ab --- /dev/null +++ b/modules/rules/README.md @@ -0,0 +1,87 @@ +# SCC Rules module + +A module to configure SCC Rules. + +Features: +- Create scc rules for an scc instance + +### Usage + +```hcl +module "create_scc_rules" { + source = "terraform-ibm-modules/scc/ibm//modules/rules" + version = "X.X.X" + scc_instance_id = "123-XXX-XXX" + rules = [ + { + description = "new rule 1" + version = "1.0.0" + import = { + parameters = [] + } + target = { + service_name = "kms" + resource_kind = "instance" + additional_target_attributes = [ + { + "name" : "location", + "operator" : "string_equals", + "value" : "us-south" + } + ] + } + }, + { + description = "new rule 2" + version = "1.0.0" + import = { + parameters = [] + } + target = { + service_name = "kms" + resource_kind = "instance" + additional_target_attributes = [ + { + "name" : "location", + "operator" : "string_equals", + "value" : "eu-de" + } + ] + } + } + ] +} +``` + + +### Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0, <1.7.0 | +| [ibm](#requirement\_ibm) | >=1.63.0, <2.0.0 | + +### Modules + +No modules. + +### Resources + +| Name | Type | +|------|------| +| [ibm_scc_rule.scc_rule_instance](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/scc_rule) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [rules](#input\_rules) | The rules to set for the SCC rules. |
list(object({
description = optional(string)
operator = optional(string)
property = optional(string)
value = optional(string)
import = object({
parameters = list(object({
name = optional(string)
display_name = optional(string)
description = optional(string)
type = optional(string)
}))
})
required_config = object({
description = optional(string)
operator = optional(string)
property = optional(string)
value = optional(string)
and = optional(list(
object({
description = optional(string)
operator = string
property = string
value = optional(string)
and = optional(list(
object({
description = optional(string)
operator = string
property = string
value = optional(string)
})
))
or = optional(list(
object({
description = optional(string)
operator = string
property = string
value = optional(string)
})
))
})
))
or = optional(list(
object({
description = optional(string)
operator = optional(string)
property = optional(string)
value = optional(string)
and = optional(list(
object({
description = optional(string)
operator = string
property = string
value = optional(string)
})
))
or = optional(list(
object({
description = optional(string)
operator = string
property = string
value = optional(string)
})
))
})
))
})
target = object({
service_name = optional(string)
service_display_name = optional(string)
resource_kind = optional(string)
additional_target_attributes = list(object({
name = optional(string)
operator = optional(string)
value = optional(string)
}))
})
}))
| n/a | yes | +| [rules\_version](#input\_rules\_version) | The version number of a rule. | `string` | n/a | yes | +| [scc\_instance\_id](#input\_scc\_instance\_id) | ID of the SCC instance in which to create the rules. | `string` | n/a | yes | + +### Outputs + +| Name | Description | +|------|-------------| +| [rule\_ids](#output\_rule\_ids) | The unique identifier of the scc\_rules created by this module. | + diff --git a/modules/rules/main.tf b/modules/rules/main.tf new file mode 100644 index 0000000..baab060 --- /dev/null +++ b/modules/rules/main.tf @@ -0,0 +1,96 @@ +############################################################################## +# SCC rules +############################################################################## + +resource "ibm_scc_rule" "scc_rule_instance" { + count = length(var.rules) > 0 ? length(var.rules) : 0 + instance_id = var.scc_instance_id + description = var.rules[count.index].description + + dynamic "import" { + for_each = length(var.rules[count.index].import.parameters) > 0 ? [var.rules[count.index].import] : [] + content { + dynamic "parameters" { + for_each = import.value.parameters + content { + name = parameters.value.name + display_name = parameters.value.display_name + description = parameters.value.description + type = parameters.value.type + } + } + } + } + + required_config { + description = var.rules[count.index].required_config.description + operator = var.rules[count.index].required_config.operator + property = var.rules[count.index].required_config.property + value = var.rules[count.index].required_config.value + dynamic "and" { + for_each = var.rules[count.index].required_config.and == null ? [] : var.rules[count.index].required_config.and + content { + description = and.value.description + property = and.value.property + operator = and.value.operator + value = and.value.value + dynamic "and" { + for_each = and.value.and == null ? [] : and.value.and + content { + description = and.value.description + property = and.value.property + operator = and.value.operator + } + } + dynamic "or" { + for_each = and.value.or == null ? [] : and.value.or + content { + description = or.value.description + property = or.value.property + operator = or.value.operator + } + } + } + } + dynamic "or" { + for_each = var.rules[count.index].required_config.or == null ? [] : var.rules[count.index].required_config.or + content { + description = or.value.description + property = or.value.property + operator = or.value.operator + value = or.value.value + dynamic "and" { + for_each = or.value.and == null ? [] : or.value.and + content { + description = and.value.description + property = and.value.property + operator = and.value.operator + } + } + dynamic "or" { + for_each = or.value.or == null ? [] : or.value.or + content { + description = or.value.description + property = or.value.property + operator = or.value.operator + } + } + } + } + } + + target { + service_name = var.rules[count.index].target.service_name + service_display_name = var.rules[count.index].target.service_display_name + resource_kind = var.rules[count.index].target.resource_kind + dynamic "additional_target_attributes" { + for_each = var.rules[count.index].target.additional_target_attributes + content { + name = additional_target_attributes.value.name + operator = additional_target_attributes.value.operator + value = additional_target_attributes.value.value + } + } + } + version = var.rules_version +} diff --git a/modules/rules/outputs.tf b/modules/rules/outputs.tf new file mode 100644 index 0000000..b1750e8 --- /dev/null +++ b/modules/rules/outputs.tf @@ -0,0 +1,8 @@ +output "rule_ids" { + description = "The unique identifier of the scc_rules created by this module." + value = [ + for rule in resource.ibm_scc_rule.scc_rule_instance : { + rule_id = rule.rule_id + } + ] +} diff --git a/modules/rules/variables.tf b/modules/rules/variables.tf new file mode 100644 index 0000000..29abccf --- /dev/null +++ b/modules/rules/variables.tf @@ -0,0 +1,91 @@ +variable "scc_instance_id" { + type = string + description = "ID of the SCC instance in which to create the rules." +} + +variable "rules_version" { + type = string + description = "The version number of a rule." +} + +variable "rules" { + description = "The rules to set for the SCC rules." + type = list(object({ + description = optional(string) + operator = optional(string) + property = optional(string) + value = optional(string) + import = object({ + parameters = list(object({ + name = optional(string) + display_name = optional(string) + description = optional(string) + type = optional(string) + })) + }) + required_config = object({ + description = optional(string) + operator = optional(string) + property = optional(string) + value = optional(string) + and = optional(list( + object({ + description = optional(string) + operator = string + property = string + value = optional(string) + and = optional(list( + object({ + description = optional(string) + operator = string + property = string + value = optional(string) + }) + )) + or = optional(list( + object({ + description = optional(string) + operator = string + property = string + value = optional(string) + }) + )) + }) + )) + or = optional(list( + object({ + description = optional(string) + operator = optional(string) + property = optional(string) + value = optional(string) + and = optional(list( + object({ + description = optional(string) + operator = string + property = string + value = optional(string) + }) + )) + or = optional(list( + object({ + description = optional(string) + operator = string + property = string + value = optional(string) + }) + )) + }) + )) + }) + target = object({ + service_name = optional(string) + service_display_name = optional(string) + resource_kind = optional(string) + additional_target_attributes = list(object({ + name = optional(string) + operator = optional(string) + value = optional(string) + })) + }) + })) +} diff --git a/modules/rules/version.tf b/modules/rules/version.tf new file mode 100644 index 0000000..615d3fe --- /dev/null +++ b/modules/rules/version.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0, <1.7.0" + + required_providers { + ibm = { + source = "IBM-Cloud/ibm" + version = ">=1.63.0, <2.0.0" + } + } +} diff --git a/tests/pr_test.go b/tests/pr_test.go index db0d058..cedb970 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -12,6 +12,7 @@ import ( const resourceGroup = "geretain-test-resources" // Use existing resource group const basicExampleDir = "examples/basic" const completeExampleDir = "examples/complete" +const customExampleDir = "examples/custom" func setupBasicExampleOptions(t *testing.T, prefix string, dir string) *testhelper.TestOptions { options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ @@ -55,6 +56,16 @@ func TestRunCompleteExample(t *testing.T) { assert.NotNil(t, output, "Expected some output") } +func TestRunCustomExample(t *testing.T) { + t.Parallel() + + options := setupCompleteExampleOptions(t, "scc-cust", customExampleDir) + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") +} + func TestRunBasicExample(t *testing.T) { t.Parallel()