From 3d3e82e53139ac4fac38417c55fddace9ca523ae Mon Sep 17 00:00:00 2001 From: Andre Correa Date: Mon, 3 Jul 2023 10:09:57 -0300 Subject: [PATCH] feat: initial modules set --- .gitignore | 11 + CONTRIBUTING.md | 23 ++ LICENSE | 35 +++ README.md | 39 +++ RELEASE-NOTES.md | 7 + cloud-guard/README.md | 165 ++++++++++++ cloud-guard/SPEC.md | 53 ++++ cloud-guard/data_sources.tf | 30 +++ .../examples/external_dependency/README.md | 48 ++++ .../input.auto.tfvars.template | 52 ++++ .../examples/external_dependency/main.tf | 21 ++ .../examples/external_dependency/outputs.tf | 26 ++ .../examples/external_dependency/providers.tf | 21 ++ .../examples/external_dependency/variables.tf | 39 +++ cloud-guard/examples/vision/README.md | 24 ++ .../vision/input.auto.tfvars.template | 43 +++ cloud-guard/examples/vision/main.tf | 8 + cloud-guard/examples/vision/outputs.tf | 26 ++ cloud-guard/examples/vision/providers.tf | 21 ++ cloud-guard/examples/vision/variables.tf | 31 +++ cloud-guard/main.tf | 96 +++++++ cloud-guard/metadata.tf | 7 + cloud-guard/outputs.tf | 32 +++ cloud-guard/providers.tf | 12 + cloud-guard/variables.tf | 54 ++++ landing_zone_300.png | Bin 0 -> 21302 bytes release.txt | 1 + security-zones/README.md | 214 +++++++++++++++ security-zones/SPEC.md | 43 +++ security-zones/data_sources.tf | 10 + .../examples/external_dependency/README.md | 46 ++++ .../input.auto.tfvars.template | 64 +++++ .../examples/external_dependency/main.tf | 21 ++ .../examples/external_dependency/outputs.tf | 14 + .../examples/external_dependency/providers.tf | 21 ++ .../examples/external_dependency/variables.tf | 50 ++++ security-zones/examples/vision/README.md | 24 ++ .../vision/input.auto.tfvars.template | 54 ++++ security-zones/examples/vision/main.tf | 7 + security-zones/examples/vision/outputs.tf | 14 + security-zones/examples/vision/providers.tf | 21 ++ security-zones/examples/vision/variables.tf | 42 +++ security-zones/main.tf | 61 +++++ security-zones/metadata.tf | 7 + security-zones/outputs.tf | 21 ++ security-zones/providers.tf | 12 + security-zones/variables.tf | 53 ++++ vaults/README.md | 214 +++++++++++++++ vaults/SPEC.md | 47 ++++ vaults/examples/external_dependency/README.md | 59 ++++ .../input.auto.tfvars.template | 65 +++++ vaults/examples/external_dependency/main.tf | 32 +++ .../examples/external_dependency/outputs.tf | 18 ++ .../examples/external_dependency/providers.tf | 32 +++ .../examples/external_dependency/variables.tf | 68 +++++ vaults/examples/vision/README.md | 22 ++ .../vision/input.auto.tfvars.template | 49 ++++ vaults/examples/vision/main.tf | 11 + vaults/examples/vision/outputs.tf | 18 ++ vaults/examples/vision/providers.tf | 32 +++ vaults/examples/vision/variables.tf | 52 ++++ vaults/main.tf | 125 +++++++++ vaults/metadata.tf | 7 + vaults/outputs.tf | 22 ++ vaults/providers.tf | 15 ++ vaults/variables.tf | 68 +++++ vss/README.md | 251 ++++++++++++++++++ vss/SPEC.md | 42 +++ vss/examples/external_dependency/README.md | 48 ++++ .../input.auto.tfvars.template | 70 +++++ vss/examples/external_dependency/main.tf | 20 ++ vss/examples/external_dependency/outputs.tf | 10 + vss/examples/external_dependency/providers.tf | 21 ++ vss/examples/external_dependency/variables.tf | 86 ++++++ vss/examples/vision/README.md | 24 ++ .../vision/input.auto.tfvars.template | 60 +++++ vss/examples/vision/main.tf | 7 + vss/examples/vision/outputs.tf | 10 + vss/examples/vision/providers.tf | 21 ++ vss/examples/vision/variables.tf | 78 ++++++ vss/main.tf | 84 ++++++ vss/metadata.tf | 7 + vss/outputs.tf | 22 ++ vss/providers.tf | 14 + vss/variables.tf | 89 +++++++ 85 files changed, 3644 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 RELEASE-NOTES.md create mode 100644 cloud-guard/README.md create mode 100644 cloud-guard/SPEC.md create mode 100644 cloud-guard/data_sources.tf create mode 100644 cloud-guard/examples/external_dependency/README.md create mode 100644 cloud-guard/examples/external_dependency/input.auto.tfvars.template create mode 100644 cloud-guard/examples/external_dependency/main.tf create mode 100644 cloud-guard/examples/external_dependency/outputs.tf create mode 100644 cloud-guard/examples/external_dependency/providers.tf create mode 100644 cloud-guard/examples/external_dependency/variables.tf create mode 100644 cloud-guard/examples/vision/README.md create mode 100644 cloud-guard/examples/vision/input.auto.tfvars.template create mode 100644 cloud-guard/examples/vision/main.tf create mode 100644 cloud-guard/examples/vision/outputs.tf create mode 100644 cloud-guard/examples/vision/providers.tf create mode 100644 cloud-guard/examples/vision/variables.tf create mode 100644 cloud-guard/main.tf create mode 100644 cloud-guard/metadata.tf create mode 100644 cloud-guard/outputs.tf create mode 100644 cloud-guard/providers.tf create mode 100644 cloud-guard/variables.tf create mode 100644 landing_zone_300.png create mode 100644 release.txt create mode 100644 security-zones/README.md create mode 100644 security-zones/SPEC.md create mode 100644 security-zones/data_sources.tf create mode 100644 security-zones/examples/external_dependency/README.md create mode 100644 security-zones/examples/external_dependency/input.auto.tfvars.template create mode 100644 security-zones/examples/external_dependency/main.tf create mode 100644 security-zones/examples/external_dependency/outputs.tf create mode 100644 security-zones/examples/external_dependency/providers.tf create mode 100644 security-zones/examples/external_dependency/variables.tf create mode 100644 security-zones/examples/vision/README.md create mode 100644 security-zones/examples/vision/input.auto.tfvars.template create mode 100644 security-zones/examples/vision/main.tf create mode 100644 security-zones/examples/vision/outputs.tf create mode 100644 security-zones/examples/vision/providers.tf create mode 100644 security-zones/examples/vision/variables.tf create mode 100644 security-zones/main.tf create mode 100644 security-zones/metadata.tf create mode 100644 security-zones/outputs.tf create mode 100644 security-zones/providers.tf create mode 100644 security-zones/variables.tf create mode 100644 vaults/README.md create mode 100644 vaults/SPEC.md create mode 100644 vaults/examples/external_dependency/README.md create mode 100644 vaults/examples/external_dependency/input.auto.tfvars.template create mode 100644 vaults/examples/external_dependency/main.tf create mode 100644 vaults/examples/external_dependency/outputs.tf create mode 100644 vaults/examples/external_dependency/providers.tf create mode 100644 vaults/examples/external_dependency/variables.tf create mode 100644 vaults/examples/vision/README.md create mode 100644 vaults/examples/vision/input.auto.tfvars.template create mode 100644 vaults/examples/vision/main.tf create mode 100644 vaults/examples/vision/outputs.tf create mode 100644 vaults/examples/vision/providers.tf create mode 100644 vaults/examples/vision/variables.tf create mode 100644 vaults/main.tf create mode 100644 vaults/metadata.tf create mode 100644 vaults/outputs.tf create mode 100644 vaults/providers.tf create mode 100644 vaults/variables.tf create mode 100644 vss/README.md create mode 100644 vss/SPEC.md create mode 100644 vss/examples/external_dependency/README.md create mode 100644 vss/examples/external_dependency/input.auto.tfvars.template create mode 100644 vss/examples/external_dependency/main.tf create mode 100644 vss/examples/external_dependency/outputs.tf create mode 100644 vss/examples/external_dependency/providers.tf create mode 100644 vss/examples/external_dependency/variables.tf create mode 100644 vss/examples/vision/README.md create mode 100644 vss/examples/vision/input.auto.tfvars.template create mode 100644 vss/examples/vision/main.tf create mode 100644 vss/examples/vision/outputs.tf create mode 100644 vss/examples/vision/providers.tf create mode 100644 vss/examples/vision/variables.tf create mode 100644 vss/main.tf create mode 100644 vss/metadata.tf create mode 100644 vss/outputs.tf create mode 100644 vss/providers.tf create mode 100644 vss/variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9232a95 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +**/*.tfstate* +**/*.out +**/.terraform/* +**/.terraform.* +**/.DS_Store +**/crash.log +**/*.pem +**/terraform_*.tfvars* +**/terraform.tfvars +**/input.auto.tfvars +**/*.pptx \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e774914 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,23 @@ +# Contributing to the CIS OCI Terraform Modules + +*Copyright (c) 2023, Oracle and/or its affiliates.* + +*Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.* + +To post feedback, submit feature ideas or report bugs, please use the Issues section in this repository. + +Pull requests can be made under [The Oracle Contributor Agreement](https://oca.opensource.oracle.com/) (OCA). + +For pull requests to be accepted, the bottom of your commit message must have the following line using your name and e-mail address as it appears in the OCA Signatories list. + +``` + Signed-off-by: Your Name +``` + +This can be automatically added to pull requests by committing with: + +```sh + git commit --signoff +``` + +Only pull requests from committers that can be verified as having signed the OCA can be accepted. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f36e4df --- /dev/null +++ b/LICENSE @@ -0,0 +1,35 @@ +Copyright (c) 2023 Oracle and/or its affiliates. + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8973eab --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# CIS OCI Landing Zone Security Modules + +![Landing Zone logo](./landing_zone_300.png) + +This repository contains Terraform OCI (Oracle Cloud Infrastructure) modules for security services that help customers align their OCI implementations with the CIS (Center for Internet Security) OCI Foundations Benchmark recommendations. + +The following modules are available: +- [Cloud Guard](./cloud-guard/) +- [Security Zones](./security-zones/) +- [Vaults](./vaults/) (a.k.a KMS) +- [Vulnerability Scanning](./vss/) + +Within each module you find an *examples* folder. Each example is a fully runnable Terraform configuration that you can quickly test and put to use by modifying the input data according to your own needs. + +## CIS OCI Foundations Benchmark Modules Collection + +This repository is part of a broader collection of repositories containing modules that help customers align their OCI implementations with the CIS OCI Foundations Benchmark recommendations: +- [Identity & Access Management](https://github.com/oracle-quickstart/terraform-oci-cis-landing-zone-iam) +- [Networking](https://github.com/oracle-quickstart/terraform-oci-cis-landing-zone-networking) +- [Governance](https://github.com/oracle-quickstart/terraform-oci-cis-landing-zone-governance) +- [Security](https://github.com/oracle-quickstart/terraform-oci-cis-landing-zone-security) - current repository +- [Observability & Monitoring](https://github.com/oracle-quickstart/terraform-oci-cis-landing-zone-observability) + +The modules in this collection are designed for flexibility, are straightforward to use, and enforce CIS OCI Foundations Benchmark recommendations when possible. + +Using these modules does not require a user extensive knowledge of Terraform or OCI resource types usage. Users declare a JSON object describing the OCI resources according to each module’s specification and minimal Terraform code to invoke the modules. The modules generate outputs that can be consumed by other modules as inputs, allowing for the creation of independently managed operational stacks to automate your entire OCI infrastructure. + +## Contributing +See [CONTRIBUTING.md](./CONTRIBUTING.md). + +## License +Copyright (c) 2023, Oracle and/or its affiliates. + +Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +See [LICENSE](./LICENSE) for more details. + +## Known Issues +None. \ No newline at end of file diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md new file mode 100644 index 0000000..f765cd5 --- /dev/null +++ b/RELEASE-NOTES.md @@ -0,0 +1,7 @@ +# July 03, 2023 Release Notes - 0.1.0 + +## New +1. [Initial Release](#0-1-0-initial) + +### Initial Release +Modules for Cloud Guard, Security Zones, Vault (a.k.a KMS), and Vulnerability Scanning services. \ No newline at end of file diff --git a/cloud-guard/README.md b/cloud-guard/README.md new file mode 100644 index 0000000..1618842 --- /dev/null +++ b/cloud-guard/README.md @@ -0,0 +1,165 @@ +# CIS OCI Landing Zone Cloud Guard Module + +![Landing Zone logo](../landing_zone_300.png) + +This module manages Cloud Guard service settings in Oracle Cloud Infrastructure (OCI) based on a single configuration object. OCI Cloud Guard helps customers monitor, identify, achieve, and maintain a strong security posture on Oracle Cloud. Use the service to examine your OCI resources for security weaknesses related to configuration, and your OCI operators and users for risky activities. Upon detection, Cloud Guard can suggest, assist, or take corrective actions, based on your configuration. + +Check [module specification](./SPEC.md) for a full description of module requirements, supported variables, managed resources and outputs. + +Check the [examples](./examples/) folder for fully runnable examples. + +- [Requirements](#requirements) +- [How to Invoke the Module](#invoke) +- [Module Functioning](#functioning) +- [Related Documentation](#related) +- [Known Issues](#issues) + +## Requirements +### IAM Permissions + +This module requires the following OCI IAM permission: + +``` +allow group to manage cloud-guard-family in tenancy +``` + +### Terraform Version < 1.3.x and Optional Object Type Attributes +This module relies on [Terraform Optional Object Type Attributes feature](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#optional-object-type-attributes), which is experimental from Terraform 0.14.x to 1.2.x. It shortens the amount of input values in complex object types, by having Terraform automatically inserting a default value for any missing optional attributes. The feature has been promoted and it is no longer experimental in Terraform 1.3.x. + +**As is, this module can only be used with Terraform versions up to 1.2.x**, because it can be consumed by other modules via [OCI Resource Manager service](https://docs.oracle.com/en-us/iaas/Content/ResourceManager/home.htm), that still does not support Terraform 1.3.x. + +Upon running *terraform plan* with Terraform versions prior to 1.3.x, Terraform displays the following warning: +``` +Warning: Experimental feature "module_variable_optional_attrs" is active +``` + +Note the warning is harmless. The code has been tested with Terraform 1.3.x and the implementation is fully compatible. + +If you really want to use Terraform 1.3.x, in [providers.tf](./providers.tf): +1. Change the terraform version requirement to: +``` +required_version = ">= 1.3.0" +``` +2. Remove the line: +``` +experiments = [module_variable_optional_attrs] +``` +## How to Invoke the Module + +Terraform modules can be invoked locally or remotely. + +For invoking the module locally, just set the module *source* attribute to the module file path (relative path works). The following example assumes the module is two folders up in the file system. +``` +module "cloud_guard" { + source = "../.." + cloud_guard_configuration = var.cloud_guard_configuration +} +``` + +For invoking the module remotely, set the module *source* attribute to the Cloud Guard module folder in this repository, as shown: +``` +module "cloud_guard" { + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//cloud-guard" + cloud_guard_configuration = var.cloud_guard_configuration +} +``` +For referring to a specific module version, append *ref=\* to the *source* attribute value, as in: +``` + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//cloud_guard?ref=v0.1.0" +``` + +## Module Functioning + +In this module, Cloud Guard settings are defined using the *cloud_guard_configuration* object, that supports the following attributes: +- **tenancy_ocid**: the tenancy OCID. +- **default_defined_tags**: the default defined tags that are applied to all resources managed by this module. It can be overriden by *defined_tags* attribute in each resource. +- **default_freeform_tags**: the default freeform tags that are applied to all resources managed by this module. It can be overriden by *freeform_tags* attribute in each resource. +- **reporting_region**: the Cloud Guard reporting region, where all API calls, except reads, are made on. You can choose the reporting region among the available regions when enabling Cloud Guard. After Cloud Guard is enabled, you cannot change the reporting region without disabling and re-enabling Cloud Guard. Setting this attribute is required if Cloud Guard is enabled by this module. +- **self_manage_resources**: whether Oracle managed resources are created by customers. Default: false. +- **cloned_recipes_prefix**: a prefix to cloned recipe names. Default: "oracle-cloned-". +- **targets**: the Cloud Guard targets. + +**Note**: The module enables the Cloud Guard service in the tenancy if Cloud Guard is not enabled. **It will not disable Cloud Guard under any circumstances**. For disabling Cloud Guard, use the OCI Console. + +### Defining Cloud Guard Targets + +Within *cloud_guard_configuration*, use the *targets* attribute to define Cloud Guard targets. Each target is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *targets* attribute supports the following attributes: +- **name**: the target name. +- **compartment_id**: the compartment where the target is created. It defaults to the value of *resource_id* if *resource_type* is "COMPARTMENT". This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **resource_type**: the resource type that Cloud Guard monitors. Valid values: "COMPARTMENT", "FACLOUD". Default: "COMPARTMENT". +- **resource_id**: the resource that Cloud Guard monitors. If the resource refers to a compartment, Cloud Guard monitors the compartment and all its subcompartments. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **use_cloned_recipes**: whether the target should use clones of Oracle provided recipes. Default: false, which means targets use the Oracle provided recipes by default. +- **defined_tags**: the target defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the target freeform tags. *default_freeform_tags* is used if undefined. + +**Note**: Regardless of using Oracle provided or cloned recipes, every Cloud Guard target gets configuration, activity and threat detector recipes as well as a responder recipe. + +## An Example + +The following snippet enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region and defining two targets. Both targets monitor compartments under *resource_ocid* compartment and are created in *resource_ocid* compartment. First target (*CLOUD-GUARD-TARGET-1*) uses Oracle provided recipes while the second one (*CLOUD-GUARD-TARGET-2*) uses cloned recipes. +``` +cloud_guard_configuration = { + tenancy_ocid = "ocid1.tenancy.oc1..aaaaaa...nuq" + reporting_region = "us-ashburn-1" + + targets = { + CLOUD-GUARD-TARGET-1 = { + name = "vision-cloud-guard-target-1" + resource_id = "ocid1.compartment.oc1..aaaaaa...xuq" + } + CLOUD-GUARD-TARGET-2 = { + name = "vision-cloud-guard-target-1" + resource_id = "ocid1.compartment.oc1..aaaaaa...y2a" + use_cloned_recipes = true + } + } +} +``` +### External Dependencies + +The example above has some dependencies. Specifically, it requires *tenancy_ocid* and *resource_id* values. These values need to be obtained somehow. In some cases, you can simply get them from the team that is managing compartments and operate on a manual copy-and-paste fashion. However, in the automation world, copying and pasting can be slow and error prone. More sophisticated automation approaches would get these dependencies from their producing Terraform configurations. With this scenario in mind, **the module overloads the attributes ending in *_id***. Note *tenancy_ocid* is immutable in the tenancy lifetime, hence the module expects that the literal tenancy OCID is used. The *\*_id* attributes can be assigned a literal OCID (as in the example above, for those whom copying and pasting is an acceptable approach) or a reference (a key) to an OCID. If a key to an OCID is given, the module requires a map of objects where the key and the OCID are expected to be found. This map of objects is passed to the module via the *compartments_dependency* attribute. + +Rewriting the example above with the external dependency: + +``` +cloud_guard_configuration = { + tenancy_ocid = "ocid1.tenancy.oc1..aaaaaa...nuq" + reporting_region = "us-ashburn-1" + + targets = { + CLOUD-GUARD-TARGET-1 = { + name = "vision-cloud-guard-target-1" + resource_id = "HR-CMP" + } + CLOUD-GUARD-TARGET-2 = { + name = "vision-cloud-guard-target-1" + resource_id = "SALES-CMP" + use_cloned_recipes = true + } + } +} + +compartments_dependency = { + "HR-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } + "SALES-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...y2a" + } +} +``` + +The example now relies on references to compartments (*HR-CMP* and *SALES-CMP* keys) rather than the literal compartment OCIDs. Those keys also need to be known somehow, but they are more readable than OCIDs and can have their naming standardized by DevOps, facilitating automation. + +The *compartments_dependency* map is typically the output of another Terraform configuration that gets published in a well-defined location for easy consumption. For instance, [this example](./examples/external_dependency/README.md) uses OCI Object Storage object for sharing dependencies across Terraform configurations. + +The external dependency approach helps with the creation of loosely coupled Terraform configurations with clearly defined dependencies between them, avoiding copying and pasting. + +## Related Documentation +- [OCI Cloud Guard](https://docs.oracle.com/en-us/iaas/cloud-guard/home.htm) +- [Cloud Guard in Terraform OCI Provider](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_target) + +## Known Issues +None. diff --git a/cloud-guard/SPEC.md b/cloud-guard/SPEC.md new file mode 100644 index 0000000..39b731c --- /dev/null +++ b/cloud-guard/SPEC.md @@ -0,0 +1,53 @@ +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | < 1.3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [oci](#provider\_oci) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [oci_cloud_guard_cloud_guard_configuration.this](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_cloud_guard_configuration) | resource | +| [oci_cloud_guard_detector_recipe.activity_cloned](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_detector_recipe) | resource | +| [oci_cloud_guard_detector_recipe.configuration_cloned](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_detector_recipe) | resource | +| [oci_cloud_guard_detector_recipe.threat_cloned](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_detector_recipe) | resource | +| [oci_cloud_guard_responder_recipe.responder_cloned](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_responder_recipe) | resource | +| [oci_cloud_guard_target.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_target) | resource | +| [oci_cloud_guard_cloud_guard_configuration.this](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_cloud_guard_configuration) | data source | +| [oci_cloud_guard_detector_recipes.activity](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_detector_recipes) | data source | +| [oci_cloud_guard_detector_recipes.configuration](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_detector_recipes) | data source | +| [oci_cloud_guard_detector_recipes.threat](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_detector_recipes) | data source | +| [oci_cloud_guard_responder_recipes.responder](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_responder_recipes) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cloud\_guard\_configuration](#input\_cloud\_guard\_configuration) | Cloud Guard settings, for managing Cloud Guard resources in OCI. Please see the comments within each attribute for details. |
object({
tenancy_ocid = string # the tenancy OCID.
default_defined_tags = optional(map(string)) # the default defined tags that are applied to all resources managed by this module. Overriden by defined_tags attribute in each resource.
default_freeform_tags = optional(map(string)) # the default freeform tags that are applied to all resources managed by this module. Overriden by freeform_tags attribute in each resource.
reporting_region = optional(string) # the reporting region. Required when enable=true.
self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false.
cloned_recipes_prefix = optional(string) # a prefix to add to cloned recipes. Default: "oracle-cloned-".

targets = optional(map(object({ # the Cloud Guard targets.
compartment_id = optional(string) # the compartment where the Cloud Guard is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. It defaults to resource_id if resource_type is "COMPARTMENT".
name = string # the Cloud Guard target name.
resource_type = optional(string) # the resource type that Cloud Guard monitors. Valid values: "COMPARTMENT", "FACLOUD". Default: "COMPARTMENT".
resource_id = string # the resource that Cloud Guard monitors. It can be either the resource OCID or a reference (a key) to a resource OCID. If the resource refers to a compartment, then Cloud Guard monitors the compartment and all its subcompartments.
use_cloned_recipes = optional(bool) # whether the target should use clones of Oracle provided recipes. Default: false.
defined_tags = optional(map(string)) # the target defined tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # the target freeform tags. default_freeform_tags is used if undefined.
})))
})
| n/a | yes | +| [compartments\_dependency](#input\_compartments\_dependency) | A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type. | `map(any)` | `null` | no | +| [detector\_recipes\_order](#input\_detector\_recipes\_order) | The order in which detector recipes are created. Use this to avoid any Cloud Guard recipe replacements due to the reordering of detector recipes. By default, the module creates threat, then configuration, then activity recipes. The order can be observed in the terraform plan output. | `list(string)` |
[
"threat",
"configuration",
"activity"
]
| no | +| [enable\_output](#input\_enable\_output) | Whether Terraform should enable module output. | `bool` | `true` | no | +| [module\_name](#input\_module\_name) | The module name. | `string` | `"cloud-guard"` | no | +| [responder\_recipes\_order](#input\_responder\_recipes\_order) | The order in which responder recipes are created. Use this to avoid any Cloud Guard recipe replacements due to the reordering of responder recipes. The order can be observed in the terraform plan output. | `list(string)` |
[
"default"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloned\_activity\_detector\_recipe](#output\_cloned\_activity\_detector\_recipe) | Cloned Cloud Guard activity detector recipe. | +| [cloned\_configuration\_detector\_recipe](#output\_cloned\_configuration\_detector\_recipe) | Cloned Cloud Guard configuration detector recipe. | +| [cloned\_responder\_recipe](#output\_cloned\_responder\_recipe) | Cloned Cloud Guard responder recipe. | +| [cloned\_threat\_detector\_recipe](#output\_cloned\_threat\_detector\_recipe) | Cloned Cloud Guard threat detector recipe. | +| [configuration](#output\_configuration) | Cloud Guard configuration information. | +| [targets](#output\_targets) | Cloud Guard target information. | diff --git a/cloud-guard/data_sources.tf b/cloud-guard/data_sources.tf new file mode 100644 index 0000000..33313ad --- /dev/null +++ b/cloud-guard/data_sources.tf @@ -0,0 +1,30 @@ +# Copyright (c) 2023, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_cloud_guard_cloud_guard_configuration" "this" { + compartment_id = var.cloud_guard_configuration.tenancy_ocid +} + +data "oci_cloud_guard_detector_recipes" "configuration" { + depends_on = [oci_cloud_guard_cloud_guard_configuration.this] + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "OCI Configuration Detector Recipe" +} + +data "oci_cloud_guard_detector_recipes" "threat" { + depends_on = [oci_cloud_guard_cloud_guard_configuration.this] + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "OCI Threat Detector Recipe" +} + +data "oci_cloud_guard_detector_recipes" "activity" { + depends_on = [oci_cloud_guard_cloud_guard_configuration.this] + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "OCI Activity Detector Recipe" +} + +data "oci_cloud_guard_responder_recipes" "responder" { + depends_on = [oci_cloud_guard_cloud_guard_configuration.this] + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "OCI Responder Recipe" +} \ No newline at end of file diff --git a/cloud-guard/examples/external_dependency/README.md b/cloud-guard/examples/external_dependency/README.md new file mode 100644 index 0000000..d02f1a4 --- /dev/null +++ b/cloud-guard/examples/external_dependency/README.md @@ -0,0 +1,48 @@ +# CIS OCI Cloud Guard Module Example - External Dependency + +## Introduction + +This example shows how to deploy Cloud Guard targets in OCI using the [Cloud Guard module](../..). It is functionally equivalent to [Vision example](../vision/), but it obtains its dependencies from OCI Object Storage object. + +The module enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region, and defines two targets. Both targets monitor compartments under *resource_ocid* compartment and are created in *resource_ocid* compartment. First target (*CLOUD-GUARD-TARGET-1*) uses Oracle provided recipes while the second one (*CLOUD-GUARD-TARGET-2*) uses cloned recipes. + +Because it needs to read from OCI Object Storage, the following permissions are required for the executing user, in addition to the permissions required by the [Cloud Guard module](../..) itself. + +``` +allow group to read objectstorage-namespaces in tenancy +allow group to read buckets in compartment +allow group to read objects in compartment where target.bucket.name = '' +``` + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *cloud_guard_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the tenancy OCID. + - Replace *\* placeholder by the actual reporting region name. + - Replace *\* placeholders by the appropriate target compartment references, expected to be found in the OCI Object Storage object referred by *\*. + - Replace *\* placeholder by the OCI Object Storage bucket that contains the object referred by *\*. + - Replace *\* placeholder by the OCI Object Storage object that has the compartment references. This object is supposedly stored on OCI Object Storage by the module that manages compartments. + +The OCI Object Storage object is expected to have a structure like this: +``` +{ + "HR-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } + "SALES-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...y2a" + } +} +``` + +Note the compartment OCIDs are referred by *HR-CMP* and *SALES-CMP* keys. These are the values that should be used when replacing *\* placeholders. + +Refer to [Cloud Guard module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/cloud-guard/examples/external_dependency/input.auto.tfvars.template b/cloud-guard/examples/external_dependency/input.auto.tfvars.template new file mode 100644 index 0000000..fd95a36 --- /dev/null +++ b/cloud-guard/examples/external_dependency/input.auto.tfvars.template @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#--------------------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +# 4. Replace placeholder by the actual reporting region name. +# 5. Replace placeholders by the appropriate target compartment references, expected to be found in the +# OCI Object Storage object referred by . +# 6. Replace placeholder by the OCI Object Storage bucket that contains the object referred by . +# 7. Replace placeholder by the OCI Object Storage object that has the compartment references. This object is supposedly +# stored in OCI Object Storage by the module that manages compartments. +#--------------------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +cloud_guard_configuration = { + tenancy_ocid = "" + enable = true + reporting_region = "" # example: "us-ashburn-1" + + targets = { + CLOUD-GUARD-TARGET-1 = { + name = "vision-cloud-guard-target-1" + resource_id = "" + } + CLOUD-GUARD-TARGET-2 = { + name = "vision-cloud-guard-target-1" + resource_id = "" + use_cloned_recipes = true + } + } +} + +oci_compartments_dependency = { + bucket = "" + object = "" +} \ No newline at end of file diff --git a/cloud-guard/examples/external_dependency/main.tf b/cloud-guard/examples/external_dependency/main.tf new file mode 100644 index 0000000..b27cd1c --- /dev/null +++ b/cloud-guard/examples/external_dependency/main.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_objectstorage_namespace" "this" { + count = var.oci_compartments_dependency != null ? 1 : 0 + compartment_id = var.cloud_guard_configuration.tenancy_ocid +} + +data "oci_objectstorage_object" "compartments" { + count = var.oci_compartments_dependency != null ? 1 : 0 + bucket = var.oci_compartments_dependency.bucket + namespace = data.oci_objectstorage_namespace.this[0].namespace + object = var.oci_compartments_dependency.object +} + +module "vision_cloud_guard" { + source = "../../" + cloud_guard_configuration = var.cloud_guard_configuration + enable_output = true + compartments_dependency = var.oci_compartments_dependency != null ? jsondecode(data.oci_objectstorage_object.compartments[0].content) : null +} diff --git a/cloud-guard/examples/external_dependency/outputs.tf b/cloud-guard/examples/external_dependency/outputs.tf new file mode 100644 index 0000000..de12131 --- /dev/null +++ b/cloud-guard/examples/external_dependency/outputs.tf @@ -0,0 +1,26 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "configuration" { + value = module.vision_cloud_guard.configuration +} + +/*output "targets" { + value = module.vision_cloud_guard.targets +} + +output "cloned_configuration_detector_recipe" { + value = module.vision_cloud_guard.cloned_configuration_detector_recipe +} + +output "cloned_activity_detector_recipe" { + value = module.vision_cloud_guard.cloned_activity_detector_recipe +} + +output "cloned_threat_detector_recipe" { + value = module.vision_cloud_guard.cloned_threat_detector_recipe +} + +output "cloned_responder_recipe" { + value = module.vision_cloud_guard.cloned_responder_recipe +} */ \ No newline at end of file diff --git a/cloud-guard/examples/external_dependency/providers.tf b/cloud-guard/examples/external_dependency/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/cloud-guard/examples/external_dependency/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/cloud-guard/examples/external_dependency/variables.tf b/cloud-guard/examples/external_dependency/variables.tf new file mode 100644 index 0000000..3e057d7 --- /dev/null +++ b/cloud-guard/examples/external_dependency/variables.tf @@ -0,0 +1,39 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "cloud_guard_configuration" { + description = "Cloud Guard settings, for managing Cloud Guard resources in OCI. Please see the comments within each attribute for details." + type = object({ + tenancy_ocid = string # the tenancy OCID. + default_defined_tags = optional(map(string)) # the default defined tags that are applied to all resources managed by this module. Overriden by defined_tags attribute in each resource. + default_freeform_tags = optional(map(string)) # the default freeform tags that are applied to all resources managed by this module. Overriden by freeform_tags attribute in each resource. + reporting_region = optional(string) # the reporting region. Required when enable=true. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + cloned_recipes_prefix = optional(string) # a prefix to add to cloned recipes. Default: "oracle-cloned-". + + targets = optional(map(object({ # the Cloud Guard targets. + compartment_id = optional(string) # the compartment where the Cloud Guard is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. It defaults to resource_id if resource_type is "COMPARTMENT". + name = string # the Cloud Guard target name. + resource_type = optional(string) # the resource type that Cloud Guard monitors. Valid values: "COMPARTMENT", "FACLOUD". Default: "COMPARTMENT". + resource_id = string # the resource that Cloud Guard monitors. It can be either the resource OCID or a reference (a key) to a resource OCID. If the resource refers to a compartment, then Cloud Guard monitors the compartment and all its subcompartments. + use_cloned_recipes = optional(bool) # whether the target should use clones of Oracle provided recipes. Default: false. + defined_tags = optional(map(string)) # the target defined tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # the target freeform tags. default_freeform_tags is used if undefined. + }))) + }) +} + +variable "oci_compartments_dependency" { + type = object({ + bucket = string + object = string + }) + default = null +} \ No newline at end of file diff --git a/cloud-guard/examples/vision/README.md b/cloud-guard/examples/vision/README.md new file mode 100644 index 0000000..577e837 --- /dev/null +++ b/cloud-guard/examples/vision/README.md @@ -0,0 +1,24 @@ +# CIS OCI Cloud Guard Module Example + +## Introduction + +This example shows how to deploy Cloud Guard targets in OCI using the [Cloud Guard module](../..). + +It enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region, and defines two targets. Both targets monitor compartments under *resource_ocid* compartment and are created in *resource_ocid* compartment. First target (*CLOUD-GUARD-TARGET-1*) uses Oracle provided recipes while the second one (*CLOUD-GUARD-TARGET-2*) uses cloned recipes. + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *cloud_guard_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the tenancy OCID. + - Replace *\* placeholder by the actual reporting region name. + - Replace *\* placeholders by the appropriate target compartment OCIDs. + +Refer to [Cloud Guard module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/cloud-guard/examples/vision/input.auto.tfvars.template b/cloud-guard/examples/vision/input.auto.tfvars.template new file mode 100644 index 0000000..0f1794f --- /dev/null +++ b/cloud-guard/examples/vision/input.auto.tfvars.template @@ -0,0 +1,43 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +# 4. Replace placeholder by the actual reporting region name. +# 4. Replace placeholders by the appropriate compartment OCIDs. +#-------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +cloud_guard_configuration = { + tenancy_ocid = "" + enable = true + reporting_region = "" # example: "us-ashburn-1" + + targets = { + CLOUD-GUARD-TARGET-1 = { + name = "vision-cloud-guard-target-1" + resource_id = "" + } + CLOUD-GUARD-TARGET-2 = { + name = "vision-cloud-guard-target-1" + resource_id = "" + use_cloned_recipes = true + } + } +} \ No newline at end of file diff --git a/cloud-guard/examples/vision/main.tf b/cloud-guard/examples/vision/main.tf new file mode 100644 index 0000000..50e8afd --- /dev/null +++ b/cloud-guard/examples/vision/main.tf @@ -0,0 +1,8 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +module "vision_cloud_guard" { + source = "../../" + cloud_guard_configuration = var.cloud_guard_configuration + enable_output = true +} diff --git a/cloud-guard/examples/vision/outputs.tf b/cloud-guard/examples/vision/outputs.tf new file mode 100644 index 0000000..de12131 --- /dev/null +++ b/cloud-guard/examples/vision/outputs.tf @@ -0,0 +1,26 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "configuration" { + value = module.vision_cloud_guard.configuration +} + +/*output "targets" { + value = module.vision_cloud_guard.targets +} + +output "cloned_configuration_detector_recipe" { + value = module.vision_cloud_guard.cloned_configuration_detector_recipe +} + +output "cloned_activity_detector_recipe" { + value = module.vision_cloud_guard.cloned_activity_detector_recipe +} + +output "cloned_threat_detector_recipe" { + value = module.vision_cloud_guard.cloned_threat_detector_recipe +} + +output "cloned_responder_recipe" { + value = module.vision_cloud_guard.cloned_responder_recipe +} */ \ No newline at end of file diff --git a/cloud-guard/examples/vision/providers.tf b/cloud-guard/examples/vision/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/cloud-guard/examples/vision/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/cloud-guard/examples/vision/variables.tf b/cloud-guard/examples/vision/variables.tf new file mode 100644 index 0000000..9b480a4 --- /dev/null +++ b/cloud-guard/examples/vision/variables.tf @@ -0,0 +1,31 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "cloud_guard_configuration" { + description = "Cloud Guard settings, for managing Cloud Guard resources in OCI. Please see the comments within each attribute for details." + type = object({ + tenancy_ocid = string # the tenancy OCID. + default_defined_tags = optional(map(string)) # the default defined tags that are applied to all resources managed by this module. Overriden by defined_tags attribute in each resource. + default_freeform_tags = optional(map(string)) # the default freeform tags that are applied to all resources managed by this module. Overriden by freeform_tags attribute in each resource. + reporting_region = optional(string) # the reporting region. Required when enable=true. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + cloned_recipes_prefix = optional(string) # a prefix to add to cloned recipes. Default: "oracle-cloned-". + + targets = optional(map(object({ # the Cloud Guard targets. + compartment_id = optional(string) # the compartment where the Cloud Guard is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. It defaults to resource_id if resource_type is "COMPARTMENT". + name = string # the Cloud Guard target name. + resource_type = optional(string) # the resource type that Cloud Guard monitors. Valid values: "COMPARTMENT", "FACLOUD". Default: "COMPARTMENT". + resource_id = string # the resource that Cloud Guard monitors. It can be either the resource OCID or a reference (a key) to a resource OCID. If the resource refers to a compartment, then Cloud Guard monitors the compartment and all its subcompartments. + use_cloned_recipes = optional(bool) # whether the target should use clones of Oracle provided recipes. Default: false. + defined_tags = optional(map(string)) # the target defined tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # the target freeform tags. default_freeform_tags is used if undefined. + }))) + }) +} \ No newline at end of file diff --git a/cloud-guard/main.tf b/cloud-guard/main.tf new file mode 100644 index 0000000..5c0cace --- /dev/null +++ b/cloud-guard/main.tf @@ -0,0 +1,96 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +locals { + is_cloud_guard_enabled = data.oci_cloud_guard_cloud_guard_configuration.this.status == "ENABLED" ? true : length(oci_cloud_guard_cloud_guard_configuration.this) > 0 ? (oci_cloud_guard_cloud_guard_configuration.this[0].status == "ENABLED" ? true : false) : false + cloned_recipes_prefix = var.cloud_guard_configuration.cloned_recipes_prefix != null ? var.cloud_guard_configuration.cloned_recipes_prefix : "oracle-cloned" + + is_create_cloned_recipes = length(flatten([ + for target_value in (var.cloud_guard_configuration.targets != null ? var.cloud_guard_configuration.targets : {}) : [target_value.use_cloned_recipes] + if coalesce(target_value.use_cloned_recipes,false) == true ])) > 0 +} + +resource "oci_cloud_guard_cloud_guard_configuration" "this" { + lifecycle { + precondition { + condition = data.oci_cloud_guard_cloud_guard_configuration.this.status == "ENABLED" ? true : var.cloud_guard_configuration.reporting_region != null ? true : false + error_message = "VALIDATION FAILURE: reporting_region must be provided when enabling Cloud Guard." + } + } + count = data.oci_cloud_guard_cloud_guard_configuration.this.status == "DISABLED" ? 1 : 0 # we only manage this resource if Cloud Guard is currently disabled, which means we have to enable it. + compartment_id = var.cloud_guard_configuration.tenancy_ocid + reporting_region = var.cloud_guard_configuration.reporting_region + status = "ENABLED" + self_manage_resources = var.cloud_guard_configuration.self_manage_resources != null ? (var.cloud_guard_configuration.self_manage_resources == true ? true : false) : false +} + +resource "oci_cloud_guard_target" "these" { + for_each = var.cloud_guard_configuration.targets != null ? var.cloud_guard_configuration.targets : {} + #-- If resource_type is null or resource_type == "COMPARTMENT", the compartment_ocid defaults to target_resource_ocid. + compartment_id = each.value.resource_type != null ? (each.value.resource_type == "COMPARTMENT" ? (length(regexall("^ocid1.*$", each.value.resource_id)) > 0 ? each.value.resource_id : var.compartments_dependency[each.value.resource_id].id) : (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id)) : (length(regexall("^ocid1.*$", each.value.resource_id)) > 0 ? each.value.resource_id : var.compartments_dependency[each.value.resource_id].id) + display_name = each.value.name + target_resource_id = length(regexall("^ocid1.*$", each.value.resource_id)) > 0 ? each.value.resource_id : var.compartments_dependency[each.value.resource_id].id + target_resource_type = each.value.resource_type != null ? each.value.resource_type : "COMPARTMENT" + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.cloud_guard_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.cloud_guard_configuration.default_freeform_tags) + + dynamic "target_detector_recipes" { + for_each = { + "${index(var.detector_recipes_order,"threat")}_threat" : {"id" : each.value.use_cloned_recipes != null ? (each.value.use_cloned_recipes ? oci_cloud_guard_detector_recipe.threat_cloned[0].id : data.oci_cloud_guard_detector_recipes.threat.detector_recipe_collection[0].items[0].id) : data.oci_cloud_guard_detector_recipes.threat.detector_recipe_collection[0].items[0].id }, + "${index(var.detector_recipes_order,"configuration")}_configuration" : {"id" : each.value.use_cloned_recipes != null ? (each.value.use_cloned_recipes ? oci_cloud_guard_detector_recipe.configuration_cloned[0].id : data.oci_cloud_guard_detector_recipes.configuration.detector_recipe_collection[0].items[0].id) : data.oci_cloud_guard_detector_recipes.configuration.detector_recipe_collection[0].items[0].id }, + "${index(var.detector_recipes_order,"activity")}_activity" : {"id" : each.value.use_cloned_recipes != null ? (each.value.use_cloned_recipes ? oci_cloud_guard_detector_recipe.activity_cloned[0].id : data.oci_cloud_guard_detector_recipes.activity.detector_recipe_collection[0].items[0].id) : data.oci_cloud_guard_detector_recipes.activity.detector_recipe_collection[0].items[0].id } + } + content { + detector_recipe_id = target_detector_recipes.value["id"] + } + } + + dynamic "target_responder_recipes" { + for_each = { + "${index(var.responder_recipes_order,"default")}_default" : {"id" : each.value.use_cloned_recipes != null ? (each.value.use_cloned_recipes ? oci_cloud_guard_responder_recipe.responder_cloned[0].id : data.oci_cloud_guard_responder_recipes.responder.responder_recipe_collection[0].items[0].id) : data.oci_cloud_guard_responder_recipes.responder.responder_recipe_collection[0].items[0].id } + } + content { + responder_recipe_id = target_responder_recipes.value["id"] + } + } +} + +resource "oci_cloud_guard_detector_recipe" "configuration_cloned" { + count = local.is_create_cloned_recipes == true ? 1 : 0 + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "${local.cloned_recipes_prefix}-configuration-detector-recipe" + description = "CIS Landing Zone configuration detector recipe (cloned from Oracle managed recipe)." + defined_tags = var.cloud_guard_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, var.cloud_guard_configuration.default_freeform_tags) + source_detector_recipe_id = data.oci_cloud_guard_detector_recipes.configuration.detector_recipe_collection[0].items[0].id +} + +resource "oci_cloud_guard_detector_recipe" "activity_cloned" { + count = local.is_create_cloned_recipes == true ? 1 : 0 + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "${local.cloned_recipes_prefix}-activity-detector-recipe" + description = "CIS Landing Zone activity detector recipe (cloned from Oracle managed recipe)." + defined_tags = var.cloud_guard_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, var.cloud_guard_configuration.default_freeform_tags) + source_detector_recipe_id = data.oci_cloud_guard_detector_recipes.activity.detector_recipe_collection[0].items[0].id +} + +resource "oci_cloud_guard_detector_recipe" "threat_cloned" { + count = local.is_create_cloned_recipes == true ? 1 : 0 + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "${local.cloned_recipes_prefix}-threat-detector-recipe" + description = "CIS Landing Zone threat detector recipe (cloned from Oracle managed recipe)." + defined_tags = var.cloud_guard_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, var.cloud_guard_configuration.default_freeform_tags) + source_detector_recipe_id = data.oci_cloud_guard_detector_recipes.threat.detector_recipe_collection[0].items[0].id +} + +resource "oci_cloud_guard_responder_recipe" "responder_cloned" { + count = local.is_create_cloned_recipes == true ? 1 : 0 + compartment_id = var.cloud_guard_configuration.tenancy_ocid + display_name = "${local.cloned_recipes_prefix}-responder-recipe" + description = "CIS Landing Zone responder recipe (cloned from Oracle managed recipe)." + defined_tags = var.cloud_guard_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, var.cloud_guard_configuration.default_freeform_tags) + source_responder_recipe_id = data.oci_cloud_guard_responder_recipes.responder.responder_recipe_collection[0].items[0].id +} \ No newline at end of file diff --git a/cloud-guard/metadata.tf b/cloud-guard/metadata.tf new file mode 100644 index 0000000..b2d9c2d --- /dev/null +++ b/cloud-guard/metadata.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-- Used to inform module and release number. +locals { + cislz_module_tag = {"cislz-terraform-module" : fileexists("${path.module}/../release.txt") ? "${var.module_name}/${file("${path.module}/../release.txt")}" : "${var.module_name}"} +} \ No newline at end of file diff --git a/cloud-guard/outputs.tf b/cloud-guard/outputs.tf new file mode 100644 index 0000000..73b6499 --- /dev/null +++ b/cloud-guard/outputs.tf @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output configuration { + description = "Cloud Guard configuration information." + value = var.enable_output ? (length(oci_cloud_guard_cloud_guard_configuration.this) > 0 ? oci_cloud_guard_cloud_guard_configuration.this[0] : null) : null +} + +output targets { + description = "Cloud Guard target information." + value = var.enable_output ? (length(oci_cloud_guard_target.these) > 0 ? oci_cloud_guard_target.these : null) : null +} + +output "cloned_configuration_detector_recipe" { + description = "Cloned Cloud Guard configuration detector recipe." + value = var.enable_output ? (length(oci_cloud_guard_detector_recipe.configuration_cloned) > 0 ? oci_cloud_guard_detector_recipe.configuration_cloned[0] : null) : null +} + +output "cloned_activity_detector_recipe" { + description = "Cloned Cloud Guard activity detector recipe." + value = var.enable_output ? (length(oci_cloud_guard_detector_recipe.activity_cloned) > 0 ? oci_cloud_guard_detector_recipe.activity_cloned[0] : null) : null +} + +output "cloned_threat_detector_recipe" { + description = "Cloned Cloud Guard threat detector recipe." + value = var.enable_output ? (length(oci_cloud_guard_detector_recipe.threat_cloned) > 0 ? oci_cloud_guard_detector_recipe.threat_cloned[0] : null) : null +} + +output "cloned_responder_recipe" { + description = "Cloned Cloud Guard responder recipe." + value = var.enable_output ? (length(oci_cloud_guard_responder_recipe.responder_cloned) > 0 ? oci_cloud_guard_responder_recipe.responder_cloned[0] : null) : null +} \ No newline at end of file diff --git a/cloud-guard/providers.tf b/cloud-guard/providers.tf new file mode 100644 index 0000000..a13a431 --- /dev/null +++ b/cloud-guard/providers.tf @@ -0,0 +1,12 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +terraform { + required_version = " < 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/cloud-guard/variables.tf b/cloud-guard/variables.tf new file mode 100644 index 0000000..e0c591e --- /dev/null +++ b/cloud-guard/variables.tf @@ -0,0 +1,54 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "cloud_guard_configuration" { + description = "Cloud Guard settings, for managing Cloud Guard resources in OCI. Please see the comments within each attribute for details." + type = object({ + tenancy_ocid = string # the tenancy OCID. + default_defined_tags = optional(map(string)) # the default defined tags that are applied to all resources managed by this module. Overriden by defined_tags attribute in each resource. + default_freeform_tags = optional(map(string)) # the default freeform tags that are applied to all resources managed by this module. Overriden by freeform_tags attribute in each resource. + reporting_region = optional(string) # the reporting region. Required when enable=true. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + cloned_recipes_prefix = optional(string) # a prefix to add to cloned recipes. Default: "oracle-cloned-". + + targets = optional(map(object({ # the Cloud Guard targets. + compartment_id = optional(string) # the compartment where the Cloud Guard is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. It defaults to resource_id if resource_type is "COMPARTMENT". + name = string # the Cloud Guard target name. + resource_type = optional(string) # the resource type that Cloud Guard monitors. Valid values: "COMPARTMENT", "FACLOUD". Default: "COMPARTMENT". + resource_id = string # the resource that Cloud Guard monitors. It can be either the resource OCID or a reference (a key) to a resource OCID. If the resource refers to a compartment, then Cloud Guard monitors the compartment and all its subcompartments. + use_cloned_recipes = optional(bool) # whether the target should use clones of Oracle provided recipes. Default: false. + defined_tags = optional(map(string)) # the target defined tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # the target freeform tags. default_freeform_tags is used if undefined. + }))) + }) +} + +variable enable_output { + description = "Whether Terraform should enable module output." + type = bool + default = true +} + +variable module_name { + description = "The module name." + type = string + default = "cloud-guard" +} + +variable compartments_dependency { + description = "A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type." + type = map(any) + default = null +} + +variable "detector_recipes_order" { + description = "The order in which detector recipes are created. Use this to avoid any Cloud Guard recipe replacements due to the reordering of detector recipes. By default, the module creates threat, then configuration, then activity recipes. The order can be observed in the terraform plan output." + type = list(string) + default = ["threat","configuration","activity"] +} + +variable "responder_recipes_order" { + description = "The order in which responder recipes are created. Use this to avoid any Cloud Guard recipe replacements due to the reordering of responder recipes. The order can be observed in the terraform plan output." + type = list(string) + default = ["default"] +} \ No newline at end of file diff --git a/landing_zone_300.png b/landing_zone_300.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ccd5a54be1415fbba4b496bbb35c4f41fd45e1 GIT binary patch literal 21302 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 +### IAM Permissions + +This module requires the following OCI IAM permission: + +``` +allow group to manage cloud-guard-family in tenancy +``` + +### Terraform Version < 1.3.x and Optional Object Type Attributes +This module relies on [Terraform Optional Object Type Attributes feature](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#optional-object-type-attributes), which is experimental from Terraform 0.14.x to 1.2.x. It shortens the amount of input values in complex object types, by having Terraform automatically inserting a default value for any missing optional attributes. The feature has been promoted and it is no longer experimental in Terraform 1.3.x. + +**As is, this module can only be used with Terraform versions up to 1.2.x**, because it can be consumed by other modules via [OCI Resource Manager service](https://docs.oracle.com/en-us/iaas/Content/ResourceManager/home.htm), that still does not support Terraform 1.3.x. + +Upon running *terraform plan* with Terraform versions prior to 1.3.x, Terraform displays the following warning: +``` +Warning: Experimental feature "module_variable_optional_attrs" is active +``` + +Note the warning is harmless. The code has been tested with Terraform 1.3.x and the implementation is fully compatible. + +If you really want to use Terraform 1.3.x, in [providers.tf](./providers.tf): +1. Change the terraform version requirement to: +``` +required_version = ">= 1.3.0" +``` +2. Remove the line: +``` +experiments = [module_variable_optional_attrs] +``` +## How to Invoke the Module + +Terraform modules can be invoked locally or remotely. + +For invoking the module locally, just set the module *source* attribute to the module file path (relative path works). The following example assumes the module is two folders up in the file system. +``` +module "security_zones" { + source = "../.." + security_zones_configuration = var.security_zones_configuration +} +``` + +For invoking the module remotely, set the module *source* attribute to the Security Zones module folder in this repository, as shown: +``` +module "security_zones" { + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//security_zones" + security_zones_configuration = var.security_zones_configuration +} +``` +For referring to a specific module version, append *ref=\* to the *source* attribute value, as in: +``` + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//security_zones?ref=v0.1.0" +``` + +## Module Functioning + +In this module, Security Zones settings are defined using the *security_zones_configuration* object, that supports the following attributes: +- **tenancy_ocid**: the tenancy OCID. +- **default_cis_level**: the default CIS level setting for all recipes with an unspecified *cis_level* attribute. Valid values: "1" and "2". Default: "1". See [CIS Level Setting](#cis_level_setting) for details. +- **default_defined_tags**: the default defined tags that are applied to all resources managed by this module. It can be overriden by *defined_tags* attribute in each resource. +- **default_freeform_tags**: the default freeform tags that are applied to all resources managed by this module. It can be overriden by *freeform_tags* attribute in each resource. +- **default_security_policies_ocids**: a list of default security zone policies OCIDs for all recipes with an unspecified *security_policies_ocids* attribute. These are merged with CIS security zone policies driven off *cis_level* attribute. +- **reporting_region**: the Cloud Guard reporting region, where all API calls, except reads, are made on. You can choose the reporting region among the available regions when enabling Cloud Guard. After Cloud Guard is enabled, you cannot change the reporting region without disabling and re-enabling Cloud Guard. Setting this attribute is required if Cloud Guard is enabled by this module. +- **self_manage_resources**: whether Oracle managed resources are created by customers. Default: false. +- **recipes**: the Security Zone recipes. A recipe is a set of policies. +- **security_zones**: the Security Zones. + +**Note**: The module enables the Cloud Guard service in the tenancy if Cloud Guard is not enabled. **It will not disable Cloud Guard under any circumstances**. For disabling Cloud Guard, use the OCI Console. + +### Defining Recipes + +Within *security_zones_configuration*, use the *recipes* attribute to define Security Zone recipes. Each recipe is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *recipes* attribute supports the following attributes: +- **name**: the recipe name. +- **description**: the recipe description. It defaults to *name* if undefined. +- **compartment_id**: the compartment where the Security Zone Recipe is created. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **cis_level**: the CIS level setting, driving the policies that are added to the recipe. Valid values: "1" and "2". Default: "1". See [CIS Level Setting](#cis_level_setting) for details. +- **security_policies_ocids**: a list of existing policis OCIDs that should be added to to the recipe. +- **defined_tags**: the recipe defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the recipe freeform tags. *default_freeform_tags* is used if undefined. + +#### CIS Level Setting + +CIS level is a concept in the CIS Benchmark, determining the scope and strictness of security controls. Level 1 covers basic security settings, while level 2 extends level 1 and are intended for use cases where security is more critical than manageability and usability. From Security Zones perspective, they influence on the policies that are applied to Security Zones. + +##### Level 1 Policies + +- Deny public buckets (*deny public_buckets*) +- Deny public access to database instances (*deny db_instance_public_access*) + +##### Level 2 Policies + +- Level 1 policies + +- Deny Block Vulume without a customer managed key (*deny block_volume_without_vault_key*) +- Deny Book Volume without a customer managed key (*deny boot_volume_without_vault_key*) +- Deny buckets without a customer managed key (*deny buckets_without_vault_key*) +- Deny file system without a customer managed key (*deny file_system_without_vault_key*) + +### Defining Security Zones + +Within *security_zones_configuration*, use the *security_zones* attribute to define the security zones. Each security zone is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *security_zones* attribute supports the following attributes: +- **name**: the security zone name. +- **description**: the security zone description. It defaults to *name* if undefined. +- **compartment_id**: the compartment where the Security Zone is created. Any existing Cloud Guard target for this compartment is replaced with the security zone. The security zone includes the default Oracle-managed configuration and activity detector recipes in Cloud Guard, and also scans resources in the zone for policy violations. The security zone applies to the compartment and all its sub-compartments. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **recipe_key**: the recipe index name in *recipes* attribute. +- **defined_tags**: the security zone defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the security zone freeform tags. *default_freeform_tags* is used if undefined. + +## An Example + +The following snippet enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region, defining two recipes and one security zone. The recipes are stored in the same *compartment_ocid*. The first recipe (*CIS-L1-RECIPE*) is a CIS level 1 recipe (*cis_level = "1"*) while the second (*CIS-L2-RECIPE*) is a CIS level 2 recipe (*cis_level = "2"*). The security zone is defined for *compartment_ocid* and is associated with *CIS-l1-RECIPE*. *CIS-L2-RECIPE* is not associated with a security zone. + +``` +security_zones_configuration = { + tenancy_ocid = "ocid1.tenancy.oc1..aaaaaa...nuq" + reporting_region = "us-ashburn-1" + + recipes = { + CIS-L1-RECIPE = { + name = "vision-security-zone-cis-level-1-recipe" + description = "CIS Level 1 recipe" + compartment_id = "ocid1.compartment.oc1..aaaaaa...epa" + cis_level = "1" + } + CIS-L2-RECIPE = { + name = "vision-security-zone-cis-level-2-recipe" + description = "CIS Level 2 recipe" + compartment_id = "ocid1.compartment.oc1..aaaaaa...epa" + cis_level = "2" + } + } + + security_zones = { + SECURITY-ZONE = { + name = "vision-security-zone" + compartment_id = "ocid1.compartment.oc1..aaaaaa...ufq" + recipe_key = "CIS-L1-RECIPE" + } + } +} +``` +### External Dependencies + +The example above has some dependencies. Specifically, it requires *tenancy_ocid* and *compartment_id* values. These values need to be obtained somehow. In some cases, you can simply get them from the team that is managing compartments and operate on a manual copy-and-paste fashion. However, in the automation world, copying and pasting can be slow and error prone. More sophisticated automation approaches would get these dependencies from their producing Terraform configurations. With this scenario in mind, **the module overloads the attributes ending in *_id***. Note *tenancy_ocid* is immutable in the tenancy lifetime, hence the module expects that the literal tenancy OCID is used. The *\*_id* attributes can be assigned a literal OCID (as in the example above, for those whom copying and pasting is an acceptable approach) or a reference (a key) to an OCID. If a key to an OCID is given, the module requires a map of objects where the key and the OCID are expected to be found. This map of objects is passed to the module via the *compartments_dependency* attribute. + +Rewriting the example above with the external dependency: + +``` +security_zones_configuration = { + tenancy_ocid = "ocid1.tenancy.oc1..aaaaaa...nuq" + reporting_region = "us-ashburn-1" + + recipes = { + CIS-L1-RECIPE = { + name = "vision-security-zone-cis-level-1-recipe" + description = "CIS Level 1 recipe" + compartment_id = "CIS-LANDING-ZONE-CMP" + cis_level = "1" + } + CIS-L2-RECIPE = { + name = "vision-security-zone-cis-level-2-recipe" + description = "CIS Level 2 recipe" + compartment_id = "CIS-LANDING-ZONE-CMP" + cis_level = "2" + } + } + + security_zones = { + SECURITY-ZONE = { + name = "vision-security-zone" + compartment_id = "CIS-LANDING-ZONE-CMP" + recipe_key = "CIS-L1-RECIPE" + } + } +} + +compartments_dependency = { + "CIS-LANDING-ZONE-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } +} +``` + +The example now relies on a reference to a compartment (*CIS-LANDING-ZONE-CMP* key) rather than a literal compartment OCID. This key also need to be known somehow, but it is more readable than a OCID and can have its name standardized by DevOps, facilitating automation. + +The *compartments_dependency* map is typically the output of another Terraform configuration that gets published in a well-defined location for easy consumption. For instance, [this example](./examples/external_dependency/README.md) uses OCI Object Storage object for sharing dependencies across Terraform configurations. + +The external dependency approach helps with the creation of loosely coupled Terraform configurations with clearly defined dependencies between them, avoiding copying and pasting. + +## Related Documentation +- [OCI Security Zones](https://docs.oracle.com/en-us/iaas/security-zone/home.htm) +- [Security Zones in Terraform OCI Provider](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_security_zone) + +## Known Issues +None. diff --git a/security-zones/SPEC.md b/security-zones/SPEC.md new file mode 100644 index 0000000..29134e7 --- /dev/null +++ b/security-zones/SPEC.md @@ -0,0 +1,43 @@ +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | < 1.3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [oci](#provider\_oci) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [oci_cloud_guard_cloud_guard_configuration.this](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_cloud_guard_configuration) | resource | +| [oci_cloud_guard_security_recipe.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_security_recipe) | resource | +| [oci_cloud_guard_security_zone.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/cloud_guard_security_zone) | resource | +| [oci_cloud_guard_cloud_guard_configuration.this](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_cloud_guard_configuration) | data source | +| [oci_cloud_guard_security_policies.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/cloud_guard_security_policies) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [compartments\_dependency](#input\_compartments\_dependency) | A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type. | `map(any)` | `null` | no | +| [enable\_output](#input\_enable\_output) | Whether Terraform should enable module output. | `bool` | `true` | no | +| [module\_name](#input\_module\_name) | The module name. | `string` | `"security-zones"` | no | +| [security\_zones\_configuration](#input\_security\_zones\_configuration) | Security Zones configuration. |
object({
tenancy_ocid = string # The tenancy OCID
default_cis_level = optional(string) # The default CIS level for all recipes with an unspecified cis_level. Valid values: "1" and "2". Default: "1"
default_security_policies_ocids = optional(list(string)) # The list of default Security Zone policies OCIDs for all recipes with an unspecified security_policies_ocids. These are merged with CIS Security Zone policies driven off cis_level.
default_defined_tags = optional(map(string))
default_freeform_tags = optional(map(string))
reporting_region = optional(string) # the reporting region.
self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false.

recipes = optional(map(object({
name = string
compartment_id = string # the compartment where the Security Zone Recipe is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID.
description = optional(string)
cis_level = optional(string) # Valid values: "1" and "2". Default: "1"
security_policies_ocids = optional(list(string)) # List of default Security Zone policies OCIDs that are merged with CIS Security Zone policies. These are merged with CIS Security Zone policies driven off cis_level.
defined_tags = optional(map(string))
freeform_tags = optional(map(string))
})))

security_zones = map(object({
name = string # The Security Zone name.
compartment_id = string # The Security Zone compartment. It can be either the compartment OCID or a reference (a key) to the compartment OCID. Any existing Cloud Guard target for this compartment is replaced with the security zone. The security zone includes the default Oracle-managed configuration and activity detector recipes in Cloud Guard, and also scans resources in the zone for policy violations.
recipe_key = string # The recipe key in recipes attribute.
description = optional(string) # The security zone description.
defined_tags = optional(map(string))
freeform_tags = optional(map(string))
}))

})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloud\_guard\_config\_status\_before\_apply](#output\_cloud\_guard\_config\_status\_before\_apply) | n/a | +| [configuration](#output\_configuration) | Cloud Guard configuration information. | +| [recipes](#output\_recipes) | The security zones recipes. | +| [security\_zones](#output\_security\_zones) | The security zones. | diff --git a/security-zones/data_sources.tf b/security-zones/data_sources.tf new file mode 100644 index 0000000..d50811f --- /dev/null +++ b/security-zones/data_sources.tf @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_cloud_guard_security_policies" "these" { + compartment_id = var.security_zones_configuration.tenancy_ocid +} + +data "oci_cloud_guard_cloud_guard_configuration" "this" { + compartment_id = var.security_zones_configuration.tenancy_ocid +} \ No newline at end of file diff --git a/security-zones/examples/external_dependency/README.md b/security-zones/examples/external_dependency/README.md new file mode 100644 index 0000000..2675054 --- /dev/null +++ b/security-zones/examples/external_dependency/README.md @@ -0,0 +1,46 @@ +# CIS OCI Security Zones Module Example - External Dependency + +## Introduction + +This example shows how to deploy Security Zones in OCI using the [Security Zones module](../..). It is functionally equivalent to [Vision example](../vision/), but it obtains its dependencies from OCI Object Storage object, specified in *oci_compartments_dependency* variable settings. + +It enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region, and defines two recipes and one security zone. The recipes are stored in the same *compartment_id*. The first recipe (*CIS-L1-RECIPE*) is a CIS level 1 recipe (*cis_level = "1"*) while the second (*CIS-L2-RECIPE*) is a CIS level 2 recipe (*cis_level = "2"*). The security zone is defined for *compartment_id* and is associated with *CIS-l1-RECIPE*. *CIS-L2-RECIPE* is not associated with a security zone. + +Because it needs to read from OCI Object Storage, the following permissions are required for the executing user, in addition to the permissions required by the [Security Zone module](../..) itself. + +``` +allow group to read objectstorage-namespaces in tenancy +allow group to read buckets in compartment +allow group to read objects in compartment where target.bucket.name = '' +``` + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *security_zones_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the tenancy OCID. + - Replace *\* placeholder by the actual reporting region name. Example: "us-ashburn-1". + - Replace *\* placeholder by the appropriate security zone compartment reference, expected to be found in the OCI Object Storage object referred by *\*. + - Replace *\* placeholders by the appropriate security zone recipe compartment references, expected to be found in the OCI Object Storage object referred by *\*. + - Replace *\* placeholder by the OCI Object Storage bucket that contains the object referred by *\*. + - Replace *\* placeholder by the OCI Object Storage object that has the compartment references. This object is supposedly stored on OCI Object Storage by the module that manages compartments. + +The OCI Object Storage object is expected to have a structure like this: +``` +{ + "CIS-LANDING-ZONE-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } +} +``` + +Note the compartment OCID is referred by *CIS-LANDING-ZONE-CMP* key. This is the value that should be used when replacing *\* and *\* placeholders. + +Refer to [Security Zone module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/security-zones/examples/external_dependency/input.auto.tfvars.template b/security-zones/examples/external_dependency/input.auto.tfvars.template new file mode 100644 index 0000000..5639810 --- /dev/null +++ b/security-zones/examples/external_dependency/input.auto.tfvars.template @@ -0,0 +1,64 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#--------------------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +# 4. Replace placeholder by the reporting region name. Example: "us-ashburn-1". +# 5. Replace placeholder by the appropriate security zone compartment reference, +# expected to be found in the OCI Object Storage object referred by . +# 6. Replace placeholders by the appropriate security zone recipe compartment references, +# expected to be found in the OCI Object Storage object referred by . +# 7. Replace placeholder by the OCI Object Storage bucket that contains the object referred by . +# 8. Replace placeholder by the OCI Object Storage object that has the compartment references. This object is supposedly +# stored in OCI Object Storage by the module that manages compartments. +#--------------------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +security_zones_configuration = { + tenancy_ocid = "" + reporting_region = "" + + security_zones = { + SECURITY-ZONE = { + name = "vision-security-zone" + compartment_id = "" + recipe_key = "CIS-L1-RECIPE" + } + } + + recipes = { + CIS-L1-RECIPE = { + name = "vision-security-zone-cis-level-1-recipe" + description = "CIS Level 1 recipe" + compartment_id = "" + cis_level = "1" + } + CIS-L2-RECIPE = { + name = "vision-security-zone-cis-level-2-recipe" + description = "CIS Level 2 recipe" + compartment_id = "" + cis_level = "2" + } + } +} + +oci_compartments_dependency = { + bucket = "" + object = "" +} \ No newline at end of file diff --git a/security-zones/examples/external_dependency/main.tf b/security-zones/examples/external_dependency/main.tf new file mode 100644 index 0000000..afaa744 --- /dev/null +++ b/security-zones/examples/external_dependency/main.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_objectstorage_namespace" "this" { + count = var.oci_compartments_dependency != null ? 1 : 0 + compartment_id = var.security_zones_configuration.tenancy_ocid +} + +data "oci_objectstorage_object" "compartments" { + count = var.oci_compartments_dependency != null ? 1 : 0 + bucket = var.oci_compartments_dependency.bucket + namespace = data.oci_objectstorage_namespace.this[0].namespace + object = var.oci_compartments_dependency.object +} + +module "vision_security_zones" { + source = "../../" + security_zones_configuration = var.security_zones_configuration + enable_output = true + compartments_dependency = var.oci_compartments_dependency != null ? jsondecode(data.oci_objectstorage_object.compartments[0].content) : null +} diff --git a/security-zones/examples/external_dependency/outputs.tf b/security-zones/examples/external_dependency/outputs.tf new file mode 100644 index 0000000..9b0a4f4 --- /dev/null +++ b/security-zones/examples/external_dependency/outputs.tf @@ -0,0 +1,14 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "cloud_guard_config_status_after_apply" { + value = module.vision_security_zones.configuration +} + +output "security_zones" { + value = module.vision_security_zones.security_zones +} + +output "security_zones_recipes" { + value = module.vision_security_zones.recipes +} \ No newline at end of file diff --git a/security-zones/examples/external_dependency/providers.tf b/security-zones/examples/external_dependency/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/security-zones/examples/external_dependency/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/security-zones/examples/external_dependency/variables.tf b/security-zones/examples/external_dependency/variables.tf new file mode 100644 index 0000000..6bd74ec --- /dev/null +++ b/security-zones/examples/external_dependency/variables.tf @@ -0,0 +1,50 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "security_zones_configuration" { + description = "Security Zones configuration." + type = object({ + tenancy_ocid = string # The tenancy OCID + default_cis_level = optional(string) # The default CIS level for all recipes with an unspecified cis_level. Valid values: "1" and "2". Default: "1" + default_security_policies_ocids = optional(list(string)) # The list of default Security Zone policies OCIDs for all recipes with an unspecified security_policies_ocids. These are merged with CIS Security Zone policies driven off cis_level. + default_defined_tags = optional(map(string)) + default_freeform_tags = optional(map(string)) + reporting_region = optional(string) # the reporting region. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + + recipes = optional(map(object({ + name = string + compartment_id = string # the compartment where the Security Zone Recipe is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. + description = optional(string) + cis_level = optional(string) # Valid values: "1" and "2". Default: "1" + security_policies_ocids = optional(list(string)) # List of default Security Zone policies OCIDs that are merged with CIS Security Zone policies. These are merged with CIS Security Zone policies driven off cis_level. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + }))) + + security_zones = map(object({ + name = string # The Security Zone name. + compartment_id = string # The Security Zone compartment. It can be either the compartment OCID or a reference (a key) to the compartment OCID. Any existing Cloud Guard target for this compartment is replaced with the security zone. The security zone includes the default Oracle-managed configuration and activity detector recipes in Cloud Guard, and also scans resources in the zone for policy violations. + recipe_key = string # The recipe key in recipes attribute. + description = optional(string) # The security zone description. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + })) + + }) +} + +variable "oci_compartments_dependency" { + type = object({ + bucket = string + object = string + }) + default = null +} \ No newline at end of file diff --git a/security-zones/examples/vision/README.md b/security-zones/examples/vision/README.md new file mode 100644 index 0000000..4f00f88 --- /dev/null +++ b/security-zones/examples/vision/README.md @@ -0,0 +1,24 @@ +# CIS OCI Security Zones Module Example + +## Introduction + +This example shows how to deploy Security Zones in OCI using the [Security Zones module](../..). + +It enables Cloud Guard service (if not already enabled), setting Ashburn as the reporting region, and defines two recipes and one security zone. The recipes are stored in the same *compartment_ocid*. The first recipe (*CIS-L1-RECIPE*) is a CIS level 1 recipe (*cis_level = "1"*) while the second (*CIS-L2-RECIPE*) is a CIS level 2 recipe (*cis_level = "2"*). The security zone is defined for *compartment_ocid* and is associated with *CIS-l1-RECIPE*. *CIS-L2-RECIPE* is not associated with a security zone. + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *security_zones_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the tenancy OCID. + - Replace *\* placeholder by the appropriate compartment OCID. + - Replace *\* placeholders by the appropriate compartment OCIDs. + +Refer to [Security Zones module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/security-zones/examples/vision/input.auto.tfvars.template b/security-zones/examples/vision/input.auto.tfvars.template new file mode 100644 index 0000000..f1380d2 --- /dev/null +++ b/security-zones/examples/vision/input.auto.tfvars.template @@ -0,0 +1,54 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +# 4. Replace placeholder by the reporting region name. Example: "us-ashburn-1". +# 5. Replace placeholder by the appropriate compartment OCID. +# 6. Replace placeholders by the appropriate compartment OCIDs. +#-------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +security_zones_configuration = { + tenancy_ocid = "" + reporting_region = "" + + security_zones = { + SECURITY-ZONE = { + name = "vision-security-zone" + compartment_id = "" + recipe_key = "CIS-L1-RECIPE" + } + } + + recipes = { + CIS-L1-RECIPE = { + name = "vision-security-zone-cis-level-1-recipe" + description = "CIS Level 1 recipe" + compartment_id = "" + cis_level = "1" + } + CIS-L2-RECIPE = { + name = "vision-security-zone-cis-level-2-recipe" + description = "CIS Level 2 recipe" + compartment_id = "" + cis_level = "2" + } + } +} \ No newline at end of file diff --git a/security-zones/examples/vision/main.tf b/security-zones/examples/vision/main.tf new file mode 100644 index 0000000..a2128e4 --- /dev/null +++ b/security-zones/examples/vision/main.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +module "vision_security_zones" { + source = "../../" + security_zones_configuration = var.security_zones_configuration +} diff --git a/security-zones/examples/vision/outputs.tf b/security-zones/examples/vision/outputs.tf new file mode 100644 index 0000000..9b0a4f4 --- /dev/null +++ b/security-zones/examples/vision/outputs.tf @@ -0,0 +1,14 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "cloud_guard_config_status_after_apply" { + value = module.vision_security_zones.configuration +} + +output "security_zones" { + value = module.vision_security_zones.security_zones +} + +output "security_zones_recipes" { + value = module.vision_security_zones.recipes +} \ No newline at end of file diff --git a/security-zones/examples/vision/providers.tf b/security-zones/examples/vision/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/security-zones/examples/vision/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/security-zones/examples/vision/variables.tf b/security-zones/examples/vision/variables.tf new file mode 100644 index 0000000..6757179 --- /dev/null +++ b/security-zones/examples/vision/variables.tf @@ -0,0 +1,42 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "security_zones_configuration" { + description = "Security Zones configuration." + type = object({ + tenancy_ocid = string # The tenancy OCID + default_cis_level = optional(string) # The default CIS level for all recipes with an unspecified cis_level. Valid values: "1" and "2". Default: "1" + default_security_policies_ocids = optional(list(string)) # The list of default Security Zone policies OCIDs for all recipes with an unspecified security_policies_ocids. These are merged with CIS Security Zone policies driven off cis_level. + default_defined_tags = optional(map(string)) + default_freeform_tags = optional(map(string)) + reporting_region = optional(string) # the reporting region. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + + recipes = optional(map(object({ + name = string + compartment_id = string # the compartment where the Security Zone Recipe is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. + description = optional(string) + cis_level = optional(string) # Valid values: "1" and "2". Default: "1" + security_policies_ocids = optional(list(string)) # List of default Security Zone policies OCIDs that are merged with CIS Security Zone policies. These are merged with CIS Security Zone policies driven off cis_level. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + }))) + + security_zones = map(object({ + name = string # The Security Zone name. + compartment_id = string # The Security Zone compartment. It can be either the compartment OCID or a reference (a key) to the compartment OCID. Any existing Cloud Guard target for this compartment is replaced with the security zone. The security zone includes the default Oracle-managed configuration and activity detector recipes in Cloud Guard, and also scans resources in the zone for policy violations. + recipe_key = string # The recipe key in recipes attribute. + description = optional(string) # The security zone description. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + })) + + }) +} \ No newline at end of file diff --git a/security-zones/main.tf b/security-zones/main.tf new file mode 100644 index 0000000..9d4af25 --- /dev/null +++ b/security-zones/main.tf @@ -0,0 +1,61 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +locals { + + # Security Zone recipes aligned to CIS 1.2 Level 1 + cis_1_2_l1_policy_names = ["deny public_buckets", "deny db_instance_public_access"] + # Security Zone recipes aligned to CIS 1.2 Level 2 + cis_1_2_l2_policy_names = ["deny block_volume_without_vault_key", "deny boot_volume_without_vault_key", "deny buckets_without_vault_key", "deny file_system_without_vault_key"] + + sz_policies = {for policy in data.oci_cloud_guard_security_policies.these.security_policy_collection[0].items : policy.friendly_name => policy.id} + + cis_1_2_l1_policy_ocids = [for name in local.cis_1_2_l1_policy_names : local.sz_policies[name]] + cis_1_2_l2_policy_ocids = [for name in local.cis_1_2_l2_policy_names : local.sz_policies[name]] + + # For reference, below are the OCIDs for the policy names above in commercial realm regions + # CIS 1.2 Level 1 + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaa5ocyo7jqjzgjenvccch46buhpaaofplzxlp3xbxfcdwwk2tyrwqa" + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaauoi2xnbusvfd4yffdjaaazk64gndp4flumaw3r7vedwndqd6vmrq" + # CIS 1.2 Level 2 + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaa7pgtjyod3pze6wuylgmts6ensywmeplabsxqq2bk4ighps4fqq4a" + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaaxxs63ulmtcnxqmcvy6eaozh5jdtiaa2bk7wll5bbdsbnmmoczp5a" + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaaqmq4jqcxqbjj5cjzb7t5ira66dctyypq2m2o4psxmx6atp45lyda" + # "ocid1.securityzonessecuritypolicy.oc1..aaaaaaaaff6n52aojbgdg46jpm3kn7nizmh6iwvr7myez7svtfxsfs7irigq" + + is_cloud_guard_enabled = data.oci_cloud_guard_cloud_guard_configuration.this.status == "ENABLED" ? true : length(oci_cloud_guard_cloud_guard_configuration.this) > 0 ? (oci_cloud_guard_cloud_guard_configuration.this[0].status == "ENABLED" ? true : false) : false +} + +resource "oci_cloud_guard_cloud_guard_configuration" "this" { + lifecycle { + precondition { + condition = data.oci_cloud_guard_cloud_guard_configuration.this.status == "ENABLED" ? true : var.security_zones_configuration.reporting_region != null ? true : false + error_message = "VALIDATION FAILURE: reporting_region must be provided when enabling Cloud Guard." + } + } + count = data.oci_cloud_guard_cloud_guard_configuration.this.status == "DISABLED" ? 1 : 0 # we only manage this resource if Cloud Guard is currently disabled, which means we have to enable it. + compartment_id = var.security_zones_configuration.tenancy_ocid + reporting_region = var.security_zones_configuration.reporting_region + status = "ENABLED" + self_manage_resources = var.security_zones_configuration.self_manage_resources != null ? (var.security_zones_configuration.self_manage_resources == true ? true : false) : false +} + +resource "oci_cloud_guard_security_recipe" "these" { + for_each = var.security_zones_configuration.recipes != null ? var.security_zones_configuration.recipes : {} + compartment_id = length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id + display_name = each.value.name + description = each.value.description != null ? each.value.description : each.value.name + security_policies = coalesce(each.value.cis_level, var.security_zones_configuration.default_cis_level, "1") == "1" ? setunion(local.cis_1_2_l1_policy_ocids, coalesce(each.value.security_policies_ocids, var.security_zones_configuration.default_security_policies_ocids, [])) : setunion(local.cis_1_2_l2_policy_ocids, local.cis_1_2_l1_policy_ocids, coalesce(each.value.security_policies_ocids, var.security_zones_configuration.default_security_policies_ocids, [])) + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.security_zones_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.security_zones_configuration.default_freeform_tags) +} + +resource "oci_cloud_guard_security_zone" "these" { + for_each = var.security_zones_configuration.security_zones + compartment_id = length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id + display_name = each.value.name + description = each.value.description != null ? each.value.description : each.value.name + security_zone_recipe_id = oci_cloud_guard_security_recipe.these[each.value.recipe_key].id + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.security_zones_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.security_zones_configuration.default_freeform_tags) +} \ No newline at end of file diff --git a/security-zones/metadata.tf b/security-zones/metadata.tf new file mode 100644 index 0000000..b2d9c2d --- /dev/null +++ b/security-zones/metadata.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-- Used to inform module and release number. +locals { + cislz_module_tag = {"cislz-terraform-module" : fileexists("${path.module}/../release.txt") ? "${var.module_name}/${file("${path.module}/../release.txt")}" : "${var.module_name}"} +} \ No newline at end of file diff --git a/security-zones/outputs.tf b/security-zones/outputs.tf new file mode 100644 index 0000000..edc24d2 --- /dev/null +++ b/security-zones/outputs.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2022 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output configuration { + description = "Cloud Guard configuration information." + value = var.enable_output ? oci_cloud_guard_cloud_guard_configuration.this : null +} + +output "recipes" { + description = "The security zones recipes." + value = var.enable_output ? oci_cloud_guard_security_recipe.these : null +} + +output "security_zones" { + description = "The security zones." + value = var.enable_output ? oci_cloud_guard_security_zone.these : null +} + +output "cloud_guard_config_status_before_apply" { + value = var.enable_output ? data.oci_cloud_guard_cloud_guard_configuration.this : null +} \ No newline at end of file diff --git a/security-zones/providers.tf b/security-zones/providers.tf new file mode 100644 index 0000000..a13a431 --- /dev/null +++ b/security-zones/providers.tf @@ -0,0 +1,12 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +terraform { + required_version = " < 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/security-zones/variables.tf b/security-zones/variables.tf new file mode 100644 index 0000000..925d836 --- /dev/null +++ b/security-zones/variables.tf @@ -0,0 +1,53 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "security_zones_configuration" { + description = "Security Zones configuration." + type = object({ + tenancy_ocid = string # The tenancy OCID + default_cis_level = optional(string) # The default CIS level for all recipes with an unspecified cis_level. Valid values: "1" and "2". Default: "1" + default_security_policies_ocids = optional(list(string)) # The list of default Security Zone policies OCIDs for all recipes with an unspecified security_policies_ocids. These are merged with CIS Security Zone policies driven off cis_level. + default_defined_tags = optional(map(string)) + default_freeform_tags = optional(map(string)) + reporting_region = optional(string) # the reporting region. + self_manage_resources = optional(bool) # whether Oracle managed resources are created by customers. Default: false. + + recipes = optional(map(object({ + name = string + compartment_id = string # the compartment where the Security Zone Recipe is created. It can be either the compartment OCID or a reference (a key) to the compartment OCID. + description = optional(string) + cis_level = optional(string) # Valid values: "1" and "2". Default: "1" + security_policies_ocids = optional(list(string)) # List of default Security Zone policies OCIDs that are merged with CIS Security Zone policies. These are merged with CIS Security Zone policies driven off cis_level. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + }))) + + security_zones = map(object({ + name = string # The Security Zone name. + compartment_id = string # The Security Zone compartment. It can be either the compartment OCID or a reference (a key) to the compartment OCID. Any existing Cloud Guard target for this compartment is replaced with the security zone. The security zone includes the default Oracle-managed configuration and activity detector recipes in Cloud Guard, and also scans resources in the zone for policy violations. + recipe_key = string # The recipe key in recipes attribute. + description = optional(string) # The security zone description. + defined_tags = optional(map(string)) + freeform_tags = optional(map(string)) + })) + + }) +} + +variable compartments_dependency { + description = "A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type." + type = map(any) + default = null +} + +variable enable_output { + description = "Whether Terraform should enable module output." + type = bool + default = true +} + +variable module_name { + description = "The module name." + type = string + default = "security-zones" +} \ No newline at end of file diff --git a/vaults/README.md b/vaults/README.md new file mode 100644 index 0000000..d2096e4 --- /dev/null +++ b/vaults/README.md @@ -0,0 +1,214 @@ +# CIS OCI Landing Zone Vaults (a.k.a. KMS) Module + +![Landing Zone logo](../landing_zone_300.png) + +This module manages vaults and keys in Oracle Cloud Infrastructure (OCI) based on a single configuration object. OCI Vault is a key management service that stores and manages master encryption keys and secrets for secure access to resources. + +Check [module specification](./SPEC.md) for a full description of module requirements, supported variables, managed resources and outputs. + +Check the [examples](./examples/) folder for fully runnable examples. + +- [Requirements](#requirements) +- [How to Invoke the Module](#invoke) +- [Module Functioning](#functioning) +- [Related Documentation](#related) +- [Known Issues](#issues) + +## Requirements +### IAM Permissions + +This module requires the following OCI IAM permissions in the compartments where vaults and keys are defined. + +``` +allow group to manage vaults in compartment +allow group to manage keys in compartment +allow group to manage policies in compartment +allow group to inspect compartments in tenancy +``` + +### Terraform Version < 1.3.x and Optional Object Type Attributes +This module relies on [Terraform Optional Object Type Attributes feature](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#optional-object-type-attributes), which is experimental from Terraform 0.14.x to 1.2.x. It shortens the amount of input values in complex object types, by having Terraform automatically inserting a default value for any missing optional attributes. The feature has been promoted and it is no longer experimental in Terraform 1.3.x. + +**As is, this module can only be used with Terraform versions up to 1.2.x**, because it can be consumed by other modules via [OCI Resource Manager service](https://docs.oracle.com/en-us/iaas/Content/ResourceManager/home.htm), that still does not support Terraform 1.3.x. + +Upon running *terraform plan* with Terraform versions prior to 1.3.x, Terraform displays the following warning: +``` +Warning: Experimental feature "module_variable_optional_attrs" is active +``` + +Note the warning is harmless. The code has been tested with Terraform 1.3.x and the implementation is fully compatible. + +If you really want to use Terraform 1.3.x, in [providers.tf](./providers.tf): +1. Change the terraform version requirement to: +``` +required_version = ">= 1.3.0" +``` +2. Remove the line: +``` +experiments = [module_variable_optional_attrs] +``` +## How to Invoke the Module + +Terraform modules can be invoked locally or remotely. + +For invoking the module locally, just set the module *source* attribute to the module file path (relative path works). The following example assumes the module is two folders up in the file system. +``` +module "vaults" { + source = "../.." + vaults_configuration = var.vaults_configuration +} +``` + +For invoking the module remotely, set the module *source* attribute to the vaults module folder in this repository, as shown: +``` +module "vaults" { + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//vaults" + vaults_configuration = var.vaults_configuration +} +``` +For referring to a specific module version, append *ref=\* to the *source* attribute value, as in: +``` + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//vaults?ref=v0.1.0" +``` + +## Module Functioning + +In this module, vaults and keys are defined using the *vaults_configuration* object, that supports the following attributes: +- **default_compartment_id**: the default compartment for all resources managed by this module. It can be overriden by *compartment_id* attribute in each resource. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **default_defined_tags**: the default defined tags that are applied to all resources managed by this module. It can be overriden by *defined_tags* attribute in each resource. +- **default_freeform_tags**: the default freeform tags that are applied to all resources managed by this module. It can be overriden by *freeform_tags* attribute in each resource. +- **vaults**: the vaults. +- **keys**: the encryption keys. +- **existing_keys_grants**: any existing keys to which grants should be assigned. + +### Defining Vaults + +Within *vaults_configuration*, use the *vaults* attribute to define the vaults managed by this module. Each vault is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *vaults* attribute supports the following attributes: +- **name**: the vault name. +- **compartment_id**: the OCID of the compartment where the vault is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **type**: the vault type. Valid values are "DEFAULT" and "VIRTUAL_PRIVATE". Default is "DEFAULT", a regular virtual vault, in a shared HSM partition. For an isolated HSM partition, use "VIRTUAL_PRIVATE". +- **defined_tags**: the vault defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the vault freeform tags. *default_freeform_tags* is used if undefined. + +### Defining Keys + +Within *vaults_configuration*, use the *keys* attribute to define the keys managed by this module. Each key is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *keys* attribute supports the following attributes: +- **name**: the key name. +- **compartment_id**: the compartment where the key is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **vault_key**: the index name within the vaults attribute where this key belongs to. +- **vault_management_endpoint**: the vault management endpoint where this key belongs to. If provided, this value takes precedence over *vault_key*. **Use this attribute to add this key to a Vault that is managed elsewhere**. This attribute is overloaded. It can be assigned either a literal endpoint URL or a reference (a key) to an endpoint URL. See [External Dependencies](#ext_dep) for details. +- **algorithm**: the key algorithm. Valid values: "AES", "RSA", "ECDSA". Default: "AES". +- **length**: key length in bytes. "AES" lengths: 16, 24, 32. "RSA" lengths: 256, 384, 512. "ECDSA" lengths: 32, 48, 66. Default: 32. +- **curve_id**: curve id for "ECDSA" keys. +- **protection_mode**: indicates how the key is persisted and where crypto operations are performed. Valid values: "HSM" and "SOFTWARE". Default: "HSM". +- **service_grantees**: the OCI service names allowed to use the key. +- **group_grantees**: the IAM group names allowed to use the key-delegate. +- **versions**: a list of strings representing key versions. Use this to rotate keys. +- **defined_tags**: the key defined_tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the key freeform_tags. *default_freeform_tags* is used if undefined. + +### Defining Grants for Existing Keys + +The module also allows for granting access to specified OCI services and IAM groups over **existing keys**. Such feature is enabled by *existing_keys_grants* attribute, that supports the following attributes: +- **key_id**: the existing key. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **compartment_id**: the existing key compartment. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **service_grantees**: the OCI service names allowed to use the key. +- **group_grantees**: the IAM group names allowed to use the key-delegate. + + +## An Example + +The following snippet defines a vault and two keys. +- The vault (*VISION-VAULT*) is created in a shared HSM partition (per module default value). +- Both keys are AES 32-byte keys protected by HSM (per module default values). +- The first key (*VISION-BUCKET-KEY*) is created in the *VISION-VAULT*, per *vault_key* value. It is created in the same compartment as the vault, as it does not assign *compartment_id*. It is granted access by Object Storage service in Ashburn region and by *vision-appdev-admin-group* IAM group. It has been rotated twice, per *versions* setting. +- The second key (*VISION-BLOCK-VOLUME-KEY*) is created in an externally managed Vault identified by *vault_management_endpoint* URL. It is created in the same compartment as *VISION-VAULT* vault, as it does not assign *compartment_id*. It is granted access by Block Storage service and by *vision-appdev-admin-group* IAM group. It hasn't been rotated. + +``` +vaults_configuration = { + default_compartment_id = "ocid1.compartment.oc1..aaaaaa...4ja" + + vaults = { + VISION-VAULT = { + name = "vision-vault" + } + } + keys = { + VISION-BUCKET-KEY = { + name = "vision-buckey-key" + vault_key = "VISION-VAULT" + service_grantees = ["objectstorage-us-ashburn-1"] + group_grantees = ["vision-appdev-admin-group"] + versions = ["1","2"] + } + VISION-BLOCK-VOLUME-KEY = { + name = "vision-block-volume-key" + vault_management_endpoint = "https://dvs...lw...fe-management.kms.us-ashburn-1.oraclecloud.com" + service_grantees = ["blockstorage"] + group_grantees = ["vision-appdev-admin-group"] + } + } +} +``` + +### External Dependencies + +The example above has some dependencies. Specifically, it requires *default_compartment_id* and *vault_management_endpoint*. These value needs to be obtained somehow. In some cases, you can simply get them from the teams that are managing those resources and operate on a manual copy-and-paste fashion. However, in the automation world, copying and pasting can be slow and error prone. More sophisticated automation approaches would get these dependencies from their producing Terraform configurations. With this scenario in mind, **the module overloads the attributes ending in *_id* and vault_management_endpoint attribute**. The *\*_id* attributes can be assigned a literal OCID (as in the example above, for those whom copying and pasting is an acceptable approach) or a reference (a key) to an OCID. The *vault_management_endpoint* attribute can be assigned a literal endpoint URL or a reference (a key) to an endpoint URL. If a reference is given, the module looks for compartments references in *compartments_dependency* and vaults references in *vaults_dependency* maps. + +Rewriting the example above with the external dependency: + +``` +vaults_configuration = { + default_compartment_id = "SECURITY-CMP" + + vaults = { + VISION-VAULT = { + name = "vision-vault" + } + } + keys = { + VISION-BUCKET-KEY = { + name = "vision-buckey-key" + vault_key = "VISION-VAULT" + service_grantees = ["objectstorage-us-ashburn-1"] + group_grantees = ["vision-appdev-admin-group"] + versions = ["1","2"] + } + VISION-BLOCK-VOLUME-KEY = { + name = "vision-block-volume-key" + vault_management_endpoint = "SHARED-VIRTUAL-PRIVATE-VAULT" + service_grantees = ["blockstorage"] + group_grantees = ["vision-appdev-admin-group"] + } + } +} + +compartments_dependency = { + "SECURITY-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } +} + +vaults_dependency = { + "SHARED-VIRTUAL-PRIVATE-VAULT" : { + "management_endpoint" : "https://dvs...lw...fe-management.kms.us-ashburn-1.oraclecloud.com" + } +} +``` + +The example now relies on a reference to a compartment (*SECURITY-CMP* key) rather than a literal compartment OCID and on a reference to a management endpoint (*SHARED-VIRTUAL-PRIVATE-VAULT* key) rather than a literal endpoint URL. These keys also need to be known somehow, but they are more readable than OCIDs/URLs and can have their naming standardized by DevOps, facilitating automation. + +The *compartments_dependency* and *vaults_dependency* maps are typically the output of other Terraform configurations that get published in a well-defined location for easy consumption. For instance, [this example](./examples/external_dependency/README.md) uses OCI Object Storage object for sharing dependencies across Terraform configurations. + +The external dependency approach helps with the creation of loosely coupled Terraform configurations with clearly defined dependencies between them, avoiding copying and pasting. + +## Related Documentation +- [Overview of Vault](https://docs.oracle.com/en-us/iaas/Content/KeyManagement/Concepts/keyoverview.htm) +- [Vaults in Terraform OCI Provider](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/kms_vault) + +## Known Issues +None. diff --git a/vaults/SPEC.md b/vaults/SPEC.md new file mode 100644 index 0000000..dae0a84 --- /dev/null +++ b/vaults/SPEC.md @@ -0,0 +1,47 @@ +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | < 1.3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [oci](#provider\_oci) | n/a | +| [oci.home](#provider\_oci.home) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [oci_identity_policy.existing_keys](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_policy) | resource | +| [oci_identity_policy.managed_keys](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/identity_policy) | resource | +| [oci_kms_key.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/kms_key) | resource | +| [oci_kms_key_version.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/kms_key_version) | resource | +| [oci_kms_vault.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/kms_vault) | resource | +| [oci_identity_compartment.existing_keys](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/identity_compartment) | data source | +| [oci_identity_compartment.managed_keys](https://registry.terraform.io/providers/oracle/oci/latest/docs/data-sources/identity_compartment) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [compartments\_dependency](#input\_compartments\_dependency) | A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type. | `map(any)` | `null` | no | +| [enable\_output](#input\_enable\_output) | Whether Terraform should enable module output. | `bool` | `true` | no | +| [module\_name](#input\_module\_name) | The module name. | `string` | `"vaults"` | no | +| [vaults\_configuration](#input\_vaults\_configuration) | Vaults configuration settings, defining all aspects to manage vaults and keys in OCI. Please see the comments within each attribute for details. |
object({

default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object.
default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object.

vaults = optional(map(object({ # the vaults to manage in this configuration.
compartment_id = optional(string) # the compartment where the vault is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string # vault name.
type = optional(string) # vault type. Default is "DEFAULT", a regular virtual vault, in shared HSM partition. For an isolated partition, use "VIRTUAL_PRIVATE".
defined_tags = optional(map(string)) # vault defined_tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # vault freeform_tags. default_freeform_tags is used if undefined.
})))

keys = optional(map(object({
compartment_id = optional(string) # the compartment where the key is created. The vault compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string # key name.
vault_key = optional(string) # the index name (key) in the vaults attribute where this key belongs to.
vault_management_endpoint = optional(string) # the vault management endpoint where this key belongs to. If provided, this value takes precedence over vault_key. Use this attribute to add this key to a Vault that is managed elsewhere. It can be assigned either a literal endpoint URL or a reference (a key) to an endpoint URL.
algorithm = optional(string) # key encryption algorithm. Valid values: "AES", "RSA", and "ECDSA". Defaults is "AES".
length = optional(number) # key length in bytes. "AES" lengths: 16, 24, 32. "RSA" lengths: 256, 384, 512. ECDSA lengths: 32, 48, 66. Default is 32.
curve_id = optional(string) # curve id for "ECDSA" keys.
protection_mode = optional(string) # indicates how the key is persisted and where crypto operations are performed. Valid values: "HSM" and "SOFTWARE". Default is "HSM".
service_grantees = optional(list(string)) # the OCI service names allowed to use the key.
group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate.
defined_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined.
freeform_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined.
versions = optional(list(string)) # a list of strings representing key versions. Use this to rotate keys.
})))

existing_keys_grants = optional(map(object({ # Use this attribute to create IAM policies for existing keys if needed
key_id = string # the existing key. It can be either a key OCID or a reference (a key) to the key OCID.
compartment_id = string # the compartment of the existing key. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
service_grantees = optional(list(string)) # the OCI service names allowed to use the key.
group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate.
})))

})
| n/a | yes | +| [vaults\_dependency](#input\_vaults\_dependency) | A map of objects containing the externally managed vaults this module may depend on. All map objects must have the same type and must contain at least a 'management\_endpoint' attribute (representing the management endpoint URL) of string type. | `map(any)` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [keys](#output\_keys) | The keys. | +| [keys\_versions](#output\_keys\_versions) | The keys versions. | +| [policies](#output\_policies) | The policies. | +| [vaults](#output\_vaults) | The vaults. | diff --git a/vaults/examples/external_dependency/README.md b/vaults/examples/external_dependency/README.md new file mode 100644 index 0000000..8708bd5 --- /dev/null +++ b/vaults/examples/external_dependency/README.md @@ -0,0 +1,59 @@ +# CIS OCI Vaults Module Example - External Dependency + +## Introduction + +This example shows how to deploy Vaults and Keys in OCI using the [Vaults module](../..). It is functionally equivalent to [Vision example](../vision/), but it obtains its dependencies from OCI Object Storage objects, specified in *oci_compartments_dependency* and *oci_vaults_dependency* variables settings. + +It defines a vault and two keys. +- The vault (*VISION-VAULT*) is created in a shared HSM partition (per module default value). +- Both keys are AES 32-byte keys protected by HSM (per module default values). +- The first key (*VISION-BUCKET-KEY*) is created in the *VISION-VAULT*, per *vault_key* value. It is created in the same compartment as the vault, as it does not assign *compartment_id*. It is granted access by Object Storage service in Ashburn region and by *vision-appdev-admin-group* IAM group. It has been rotated twice, per *versions* setting. +- The second key (*VISION-BLOCK-VOLUME-KEY*) is created in an externally managed Vault identified by *vault_management_endpoint* URL. It is created in the same compartment as *VISION-VAULT* vault, as it does not assign *compartment_id*. It is granted access by Block Storage service and by *vision-appdev-admin-group* IAM group. It hasn't been rotated. + +Because it needs to read from OCI Object Storage, the following permissions are required for the executing user, in addition to the permissions required by the [Vaults module](../..) itself. + +``` +allow group to read objectstorage-namespaces in tenancy +allow group to read buckets in compartment +allow group to read objects in compartment where target.bucket.name = '' +``` + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *vaults_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the appropriate compartment reference, expected to be found in the OCI Object Storage object referred by *\* within the object pointed by oci_compartments_dependency. + - Replace *\* by the appropriate vault reference, expected to be found in the OCI Object Storage object referred by *\* within the object pointed by oci_vaults_dependency. + - Replace *\* placeholders by the OCI Object Storage buckets that contain the object referred by *\*. + - Replace *\* placeholders by the OCI Object Storage objects with the compartments and vaults references. These objects are supposedly stored in OCI Object Storage by the modules that manage compartments and vaults. + +The OCI Object Storage object with compartments dependencies (*oci_compartments_dependency*) is expected to have a structure like this: +``` +{ + "SECURITY-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...utp" + } +} +``` + +Note the compartment OCID is referred by *SECURITY-CMP* key. This is the value that should be used when replacing *\* placeholder. + +The OCI Object Storage object with vaults dependencies (*oci_vaults_dependency*) is expected to have a structure like this: +``` +{ + "SHARED-VIRTUAL-PRIVATE-VAULT" : { + "management_endpoint" : "https://dvs...lw...fe-management.kms.us-ashburn-1.oraclecloud.com" + } +} +``` + +Note the management endpoint is referred by *SHARED-VIRTUAL-PRIVATE-VAULT* key. This is the value that should be used when replacing *\* placeholder. + +Refer to [Vaults module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/vaults/examples/external_dependency/input.auto.tfvars.template b/vaults/examples/external_dependency/input.auto.tfvars.template new file mode 100644 index 0000000..3192d79 --- /dev/null +++ b/vaults/examples/external_dependency/input.auto.tfvars.template @@ -0,0 +1,65 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#------------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment reference, expected to be found in the +# OCI Object Storage object referred by within the object pointed by oci_compartments_dependency. +# 4. Replace by the appropriate vault reference, expected to be found in the +# OCI Object Storage object referred by within the object pointed by oci_vaults_dependency. +# 5. Replace placeholders by the OCI Object Storage buckets that contain the object referred by . +# 6. Replace placeholders by the OCI Object Storage objects with the compartments and vaults references. These +# objects are supposedly stored in OCI Object Storage by the modules that manage compartments and vaults. +#------------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side). +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. +home_region = "" + +#--------------------------------------- +# Input variable +#--------------------------------------- + +vaults_configuration = { + default_compartment_id = "" + + vaults = { + VISION-VAULT = { + name = "vision-vault" + } + } + keys = { + VISION-BUCKET-KEY = { + name = "vision-buckey-key" + vault_key = "VISION-VAULT" + service_grantees = ["objectstorage-us-ashburn-1"] + group_grantees = ["vision-appdev-admin-group"] + versions = ["1","2"] + } + VISION-BLOCK-VOLUME-KEY = { + name = "vision-block-volume-key" + vault_management_endpoint = "" + service_grantees = ["blockstorage"] + group_grantees = ["vision-appdev-admin-group"] + } + } +} + +oci_compartments_dependency = { + bucket = "" + object = "" +} + +oci_vaults_dependency = { + bucket = "" + object = "" +} \ No newline at end of file diff --git a/vaults/examples/external_dependency/main.tf b/vaults/examples/external_dependency/main.tf new file mode 100644 index 0000000..3d208ac --- /dev/null +++ b/vaults/examples/external_dependency/main.tf @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_objectstorage_namespace" "this" { + count = var.oci_compartments_dependency != null || var.oci_vaults_dependency != null ? 1 : 0 + compartment_id = var.tenancy_ocid +} + +data "oci_objectstorage_object" "compartments" { + count = var.oci_compartments_dependency != null ? 1 : 0 + bucket = var.oci_compartments_dependency.bucket + namespace = data.oci_objectstorage_namespace.this[0].namespace + object = var.oci_compartments_dependency.object +} + +data "oci_objectstorage_object" "vaults" { + count = var.oci_vaults_dependency != null ? 1 : 0 + bucket = var.oci_vaults_dependency.bucket + namespace = data.oci_objectstorage_namespace.this[0].namespace + object = var.oci_vaults_dependency.object +} + +module "vision_vaults" { + source = "../../" + providers = { + oci = oci + oci.home = oci.home + } + vaults_configuration = var.vaults_configuration + compartments_dependency = var.oci_compartments_dependency != null ? jsondecode(data.oci_objectstorage_object.compartments[0].content) : null + vaults_dependency = var.oci_vaults_dependency != null ? jsondecode(data.oci_objectstorage_object.vaults[0].content) : null +} diff --git a/vaults/examples/external_dependency/outputs.tf b/vaults/examples/external_dependency/outputs.tf new file mode 100644 index 0000000..0f67f6c --- /dev/null +++ b/vaults/examples/external_dependency/outputs.tf @@ -0,0 +1,18 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "vaults" { + value = module.vision_vaults.vaults +} + +output "keys" { + value = module.vision_vaults.keys +} + +output "keys_versions" { + value = module.vision_vaults.keys_versions +} + +output "policies" { + value = module.vision_vaults.policies +} diff --git a/vaults/examples/external_dependency/providers.tf b/vaults/examples/external_dependency/providers.tf new file mode 100644 index 0000000..01c243c --- /dev/null +++ b/vaults/examples/external_dependency/providers.tf @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +provider "oci" { + alias = "home" + region = var.home_region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + configuration_aliases = [oci.home] + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/vaults/examples/external_dependency/variables.tf b/vaults/examples/external_dependency/variables.tf new file mode 100644 index 0000000..50364b9 --- /dev/null +++ b/vaults/examples/external_dependency/variables.tf @@ -0,0 +1,68 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "home_region" {description = "Your tenancy home region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "vaults_configuration" { + description = "Vaults configuration settings, defining all aspects to manage vaults and keys in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + vaults = optional(map(object({ # the vaults to manage in this configuration. + compartment_id = optional(string) # the compartment where the vault is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # vault name. + type = optional(string) # vault type. Default is "DEFAULT", a regular virtual vault. + defined_tags = optional(map(string)) # vault defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # vault freeform_tags. default_freeform_tags is used if undefined. + }))) + + keys = optional(map(object({ + compartment_id = optional(string) # the compartment where the key is created. The vault compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # key name. + vault_key = optional(string) # the index name (key) in the vaults attribute where this key belongs to. + vault_management_endpoint = optional(string) # the vault management endpoint where this key belongs to. If provided, this value takes precedence over vault_key. Use this attribute to add this key to a Vault that is managed elsewhere. It can be assigned either a literal endpoint URL or a reference (a key) to an endpoint URL. + algorithm = optional(string) # key encryption algorithm. Valid values: "AES", "RSA", and "ECDSA". Defaults is "AES". + length = optional(number) # key length in bytes. "AES" lengths: 16, 24, 32. "RSA" lengths: 256, 384, 512. ECDSA lengths: 32, 48, 66. Default is 32. + curve_id = optional(string) # curve id for "ECDSA" keys. + protection_mode = optional(string) # indicates how the key is persisted and where crypto operations are performed. Valid values: "HSM" and "SOFTWARE". Default is "HSM". + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + defined_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + freeform_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + versions = optional(list(string)) # a list of strings representing key versions. Use this to rotate keys. + }))) + + existing_keys_grants = optional(map(object({ # Use this attribute to create IAM policies for existing keys if needed + key_id = string # the existing key. It can be either a key OCID or a reference (a key) to the key OCID. + compartment_id = string # the compartment of the existing key. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + }))) + + }) +} + +variable "oci_compartments_dependency" { + type = object({ + bucket = string + object = string + }) + default = null +} + +variable "oci_vaults_dependency" { + type = object({ + bucket = string + object = string + }) + default = null +} \ No newline at end of file diff --git a/vaults/examples/vision/README.md b/vaults/examples/vision/README.md new file mode 100644 index 0000000..5ca596b --- /dev/null +++ b/vaults/examples/vision/README.md @@ -0,0 +1,22 @@ +# CIS OCI Vaults Module Example + +## Introduction + +This example shows how to deploy Vaults and Keys in OCI using the [Vaults module](../..). + +It defines a vault with two keys. The vault is created in a shared HSM partition. Both keys are AES 32-byte keys protected by HSM (per default values) and are created in the same compartment as the vault. The first key (*VISION-BUCKET-KEY*) is granted access by Object Storage service in Ashburn region and by *vision-appdev-admin-group* IAM group. Additionally, it has been rotated twice (per *versions* setting). The second key (*VISION-BLOCK-VOLUME-KEY*) is granted access by Block Storage service and by *vision-appdev-admin-group* IAM group. It hasn't been rotated. + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *security_zones_configuration* input variable, by making the appropriate substitutions: + - Replace *\* placeholder by the appropriate compartment OCID.iate compartment OCIDs. + +Refer to [Vaults module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/vaults/examples/vision/input.auto.tfvars.template b/vaults/examples/vision/input.auto.tfvars.template new file mode 100644 index 0000000..15ef155 --- /dev/null +++ b/vaults/examples/vision/input.auto.tfvars.template @@ -0,0 +1,49 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +#-------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side). +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. +home_region = "" + +#--------------------------------------- +# Input variable +#--------------------------------------- + +vaults_configuration = { + default_compartment_id = "" + + vaults = { + VISION-VAULT = { + name = "vision-vault" + } + } + keys = { + VISION-BUCKET-KEY = { + name = "vision-buckey-key" + vault_key = "VISION-VAULT" + service_grantees = ["objectstorage-us-ashburn-1"] + group_grantees = ["vision-appdev-admin-group"] + versions = ["1","2"] + } + VISION-BLOCK-VOLUME-KEY = { + name = "vision-block-volume-key" + vault_key = "VISION-VAULT" + service_grantees = ["blockstorage"] + group_grantees = ["vision-appdev-admin-group"] + } + } +} \ No newline at end of file diff --git a/vaults/examples/vision/main.tf b/vaults/examples/vision/main.tf new file mode 100644 index 0000000..28bd5db --- /dev/null +++ b/vaults/examples/vision/main.tf @@ -0,0 +1,11 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +module "vision_vaults" { + source = "../../" + providers = { + oci = oci + oci.home = oci.home + } + vaults_configuration = var.vaults_configuration +} diff --git a/vaults/examples/vision/outputs.tf b/vaults/examples/vision/outputs.tf new file mode 100644 index 0000000..0f67f6c --- /dev/null +++ b/vaults/examples/vision/outputs.tf @@ -0,0 +1,18 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "vaults" { + value = module.vision_vaults.vaults +} + +output "keys" { + value = module.vision_vaults.keys +} + +output "keys_versions" { + value = module.vision_vaults.keys_versions +} + +output "policies" { + value = module.vision_vaults.policies +} diff --git a/vaults/examples/vision/providers.tf b/vaults/examples/vision/providers.tf new file mode 100644 index 0000000..01c243c --- /dev/null +++ b/vaults/examples/vision/providers.tf @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +provider "oci" { + alias = "home" + region = var.home_region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + configuration_aliases = [oci.home] + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/vaults/examples/vision/variables.tf b/vaults/examples/vision/variables.tf new file mode 100644 index 0000000..87997dc --- /dev/null +++ b/vaults/examples/vision/variables.tf @@ -0,0 +1,52 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "home_region" {description = "Your tenancy home region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "vaults_configuration" { + description = "Vaults configuration settings, defining all aspects to manage vaults and keys in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + vaults = optional(map(object({ # the vaults to manage in this configuration. + compartment_id = optional(string) # the compartment where the vault is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # vault name. + type = optional(string) # vault type. Default is "DEFAULT", a regular virtual vault. + defined_tags = optional(map(string)) # vault defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # vault freeform_tags. default_freeform_tags is used if undefined. + }))) + + keys = optional(map(object({ + compartment_id = optional(string) # the compartment where the key is created. The vault compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # key name. + vault_key = optional(string) # the index name (key) in the vaults attribute where this key belongs to. + vault_management_endpoint = optional(string) # the vault management endpoint where this key belongs to. If provided, this value takes precedence over vault_key. Use this attribute to add this key to a Vault that is managed elsewhere. It can be assigned either a literal endpoint URL or a reference (a key) to an endpoint URL. + algorithm = optional(string) # key encryption algorithm. Valid values: "AES", "RSA", and "ECDSA". Defaults is "AES". + length = optional(number) # key length in bytes. "AES" lengths: 16, 24, 32. "RSA" lengths: 256, 384, 512. ECDSA lengths: 32, 48, 66. Default is 32. + curve_id = optional(string) # curve id for "ECDSA" keys. + protection_mode = optional(string) # indicates how the key is persisted and where crypto operations are performed. Valid values: "HSM" and "SOFTWARE". Default is "HSM". + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + defined_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + freeform_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + versions = optional(list(string)) # a list of strings representing key versions. Use this to rotate keys. + }))) + + existing_keys_grants = optional(map(object({ # Use this attribute to create IAM policies for existing keys if needed + key_id = string # the existing key. It can be either a key OCID or a reference (a key) to the key OCID. + compartment_id = string # the compartment of the existing key. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + }))) + + }) +} \ No newline at end of file diff --git a/vaults/main.tf b/vaults/main.tf new file mode 100644 index 0000000..cc5acb6 --- /dev/null +++ b/vaults/main.tf @@ -0,0 +1,125 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +locals { + + keys_policies = flatten([ + for key_key, key_value in (var.vaults_configuration.keys != null ? var.vaults_configuration.keys : {}) : { + key = key_key + compartment_id = key_value.compartment_id != null ? (length(regexall("^ocid1.*$", key_value.compartment_id)) > 0 ? key_value.compartment_id : var.compartments_dependency[key_value.compartment_id].id) : (length(regexall("^ocid1.*$", var.vaults_configuration.default_compartment_id)) > 0 ? var.vaults_configuration.default_compartment_id : var.compartments_dependency[var.vaults_configuration.default_compartment_id].id) + name = "${key_value.name}-policy" + description = "CIS Landing Zone policy allowing access to ${key_value.name} Key in the Vault service." + statements = concat(key_value.service_grantees != null ? [for sg in key_value.service_grantees : "Allow service ${sg} to use keys in compartment ${data.oci_identity_compartment.managed_keys[key_key].name} where target.key.id = '${oci_kms_key.these[key_key].id}'"] : [], + key_value.group_grantees != null ? [for gg in key_value.group_grantees : "Allow group ${gg} to use key-delegate in compartment ${data.oci_identity_compartment.managed_keys[key_key].name} where target.key.id = '${oci_kms_key.these[key_key].id}'"] : []) + defined_tags = key_value.defined_tags != null ? key_value.defined_tags : var.vaults_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, key_value.freeform_tags != null ? key_value.freeform_tags : key_value.freeform_tags != null ? key_value.freeform_tags : var.vaults_configuration.default_freeform_tags) + } + ]) + + keys_versions = flatten([ + for key_key, key_value in (var.vaults_configuration.keys != null ? var.vaults_configuration.keys : {}) : [ + for version in (key_value.versions != null ? key_value.versions : []) : { + key_key = key_key + version_key = "${key_key}.${version}" + } + ] + ]) + + algorithms = ["AES","RSA","ECDSA"] +} + +#-- Used for retrieving the compartment name to use in policy statements +data "oci_identity_compartment" "managed_keys" { + provider = oci.home + for_each = { for k, v in var.vaults_configuration.keys : k => {compartment_id = v.compartment_id != null ? (length(regexall("^ocid1.*$", v.compartment_id)) > 0 ? v.compartment_id : var.compartments_dependency[v.compartment_id].id) : (length(regexall("^ocid1.*$", var.vaults_configuration.default_compartment_id)) > 0 ? var.vaults_configuration.default_compartment_id : var.compartments_dependency[var.vaults_configuration.default_compartment_id].id) } } + id = each.value.compartment_id +} + +resource "oci_kms_vault" "these" { + provider = oci + for_each = var.vaults_configuration.vaults != null ? var.vaults_configuration.vaults : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.vaults_configuration.default_compartment_id)) > 0 ? var.vaults_configuration.default_compartment_id : var.compartments_dependency[var.vaults_configuration.default_compartment_id].id) + display_name = each.value.name + vault_type = upper(coalesce(each.value.type,"DEFAULT")) + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.vaults_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.vaults_configuration.default_freeform_tags) +} + +resource "oci_kms_key" "these" { + provider = oci + #-- create_before_destroy makes Terraform to first update any resources that depend on these keys before destroying the keys. + #-- This helps with Object Storage encrypted buckets, when updated with a new encryption key. + lifecycle { + create_before_destroy = true + precondition { + condition = contains(local.algorithms,upper(coalesce(each.value.algorithm,"AES"))) + error_message = "VALIDATION FAILURE : \"${upper(coalesce(each.value.algorithm,"AES"))}\" value is invalid for \"algorithm\" attribute in ${each.key} key definition. Valid values are ${join(", ",local.algorithms)} (case insensitive)." + } + precondition { + condition = each.value.vault_key != null || each.value.vault_management_endpoint != null + error_message = "VALIDATION FAILURE : either vault_key or vault_management_endpoint must be provided. Otherwise it is not possible to know which vault the key belongs to." + } + } + for_each = var.vaults_configuration.keys != null ? var.vaults_configuration.keys : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.vaults_configuration.default_compartment_id)) > 0 ? var.vaults_configuration.default_compartment_id : var.compartments_dependency[var.vaults_configuration.default_compartment_id].id) + display_name = each.value.name + management_endpoint = each.value.vault_management_endpoint != null ? length(regexall("^https://.*$", each.value.vault_management_endpoint)) > 0 ? each.value.vault_management_endpoint : var.vaults_dependency[each.value.vault_management_endpoint].management_endpoint : oci_kms_vault.these[each.value.vault_key].management_endpoint + protection_mode = upper(coalesce(each.value.protection_mode,"HSM")) + key_shape { + algorithm = upper(coalesce(each.value.algorithm,"AES")) + length = coalesce(each.value.length,32) + curve_id = each.value.curve_id + } + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.vaults_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.vaults_configuration.default_freeform_tags) +} + +resource "oci_kms_key_version" "these" { + provider = oci + lifecycle { + create_before_destroy = true + } + for_each = { for version in local.keys_versions : version.version_key => {key_id = oci_kms_key.these[version.key_key].id, + management_endpoint = oci_kms_key.these[version.key_key].management_endpoint }} + key_id = each.value.key_id + management_endpoint = each.value.management_endpoint +} + +resource "oci_identity_policy" "managed_keys" { + provider = oci.home + lifecycle { + create_before_destroy = true + } + for_each = { for k in local.keys_policies : k.key => {compartment_id = k.compartment_id, + name = k.name, + description = k.description, + statements = k.statements, + defined_tags = k.defined_tags, + freeform_tags = k.freeform_tags} if length(k.statements) > 0} + name = each.value.name + description = each.value.description + compartment_id = each.value.compartment_id + statements = each.value.statements + defined_tags = each.value.defined_tags + freeform_tags = each.value.freeform_tags +} + +#-- Used for retrieving the compartment name to use in policy statements +data "oci_identity_compartment" "existing_keys" { + provider = oci.home + for_each = var.vaults_configuration.existing_keys_grants != null ? var.vaults_configuration.existing_keys_grants : {} + id = each.value.compartment_ocid +} + +resource "oci_identity_policy" "existing_keys" { + provider = oci.home + for_each = var.vaults_configuration.existing_keys_grants != null ? var.vaults_configuration.existing_keys_grants : {} + name = "${lower(each.key)}-policy" + description = "CIS Landing Zone policy allowing access to keys in the Vault service." + compartment_id = length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id + statements = concat( + each.value.service_grantees != null ? [for sg in each.value.service_grantees : "Allow service ${sg} to use keys in compartment ${data.oci_identity_compartment.existing_keys[each.key].name} where target.key.id = '${length(regexall("^ocid1.*$", each.value.key_id)) > 0 ? each.value.key_id : var.vaults_dependency[each.value.key_id].id}'"] : [], + each.value.group_grantees != null ? [for gg in each.value.group_grantees : "Allow group ${gg} to use key-delegate in compartment ${data.oci_identity_compartment.existing_keys[each.key].name} where target.key.id = '${length(regexall("^ocid1.*$", each.value.key_id)) > 0 ? each.value.key_id : var.vaults_dependency[each.value.key_id].id}'"] : []) + defined_tags = var.vaults_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, var.vaults_configuration.default_freeform_tags) +} \ No newline at end of file diff --git a/vaults/metadata.tf b/vaults/metadata.tf new file mode 100644 index 0000000..b2d9c2d --- /dev/null +++ b/vaults/metadata.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-- Used to inform module and release number. +locals { + cislz_module_tag = {"cislz-terraform-module" : fileexists("${path.module}/../release.txt") ? "${var.module_name}/${file("${path.module}/../release.txt")}" : "${var.module_name}"} +} \ No newline at end of file diff --git a/vaults/outputs.tf b/vaults/outputs.tf new file mode 100644 index 0000000..d9ed3c7 --- /dev/null +++ b/vaults/outputs.tf @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "vaults" { + description = "The vaults." + value = var.enable_output ? oci_kms_vault.these : null +} + +output "keys" { + description = "The keys." + value = var.enable_output ? oci_kms_key.these : null +} + +output "keys_versions" { + description = "The keys versions." + value = var.enable_output ? oci_kms_key_version.these : null +} + +output "policies" { + description = "The policies." + value = var.enable_output ? merge(oci_identity_policy.managed_keys, oci_identity_policy.existing_keys) : null +} diff --git a/vaults/providers.tf b/vaults/providers.tf new file mode 100644 index 0000000..0c55fdc --- /dev/null +++ b/vaults/providers.tf @@ -0,0 +1,15 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +terraform { + required_version = " < 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + configuration_aliases = [ oci, oci.home ] + } + } + experiments = [module_variable_optional_attrs] +} + + diff --git a/vaults/variables.tf b/vaults/variables.tf new file mode 100644 index 0000000..d395ff7 --- /dev/null +++ b/vaults/variables.tf @@ -0,0 +1,68 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "vaults_configuration" { + description = "Vaults configuration settings, defining all aspects to manage vaults and keys in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + vaults = optional(map(object({ # the vaults to manage in this configuration. + compartment_id = optional(string) # the compartment where the vault is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # vault name. + type = optional(string) # vault type. Default is "DEFAULT", a regular virtual vault, in shared HSM partition. For an isolated partition, use "VIRTUAL_PRIVATE". + defined_tags = optional(map(string)) # vault defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # vault freeform_tags. default_freeform_tags is used if undefined. + }))) + + keys = optional(map(object({ + compartment_id = optional(string) # the compartment where the key is created. The vault compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # key name. + vault_key = optional(string) # the index name (key) in the vaults attribute where this key belongs to. + vault_management_endpoint = optional(string) # the vault management endpoint where this key belongs to. If provided, this value takes precedence over vault_key. Use this attribute to add this key to a Vault that is managed elsewhere. It can be assigned either a literal endpoint URL or a reference (a key) to an endpoint URL. + algorithm = optional(string) # key encryption algorithm. Valid values: "AES", "RSA", and "ECDSA". Defaults is "AES". + length = optional(number) # key length in bytes. "AES" lengths: 16, 24, 32. "RSA" lengths: 256, 384, 512. ECDSA lengths: 32, 48, 66. Default is 32. + curve_id = optional(string) # curve id for "ECDSA" keys. + protection_mode = optional(string) # indicates how the key is persisted and where crypto operations are performed. Valid values: "HSM" and "SOFTWARE". Default is "HSM". + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + defined_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + freeform_tags = optional(map(string)) # key freeform_tags. The vault freeform_tags is used if undefined. + versions = optional(list(string)) # a list of strings representing key versions. Use this to rotate keys. + }))) + + existing_keys_grants = optional(map(object({ # Use this attribute to create IAM policies for existing keys if needed + key_id = string # the existing key. It can be either a key OCID or a reference (a key) to the key OCID. + compartment_id = string # the compartment of the existing key. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + service_grantees = optional(list(string)) # the OCI service names allowed to use the key. + group_grantees = optional(list(string)) # the IAM group names allowed to use the key-delegate. + }))) + + }) +} + +variable compartments_dependency { + description = "A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type." + type = map(any) + default = null +} + +variable vaults_dependency { + description = "A map of objects containing the externally managed vaults this module may depend on. All map objects must have the same type and must contain at least a 'management_endpoint' attribute (representing the management endpoint URL) of string type." + type = map(any) + default = null +} + +variable enable_output { + description = "Whether Terraform should enable module output." + type = bool + default = true +} + +variable module_name { + description = "The module name." + type = string + default = "vaults" +} \ No newline at end of file diff --git a/vss/README.md b/vss/README.md new file mode 100644 index 0000000..1dcda9e --- /dev/null +++ b/vss/README.md @@ -0,0 +1,251 @@ +# CIS OCI Landing Zone Vulnerability Scanning Module + +![Landing Zone logo](../landing_zone_300.png) + +This module manages vulnerability scanning settings in Oracle Cloud Infrastructure (OCI) based on a single configuration object. OCI Vulnerability Scanning Service (VSS) helps improve a tenancy security posture by routinely checking hosts and container images for potential vulnerabilities. + +Check [module specification](./SPEC.md) for a full description of module requirements, supported variables, managed resources and outputs. + +Check the [examples](./examples/) folder for fully runnable examples. + +- [Requirements](#requirements) +- [How to Invoke the Module](#invoke) +- [Module Functioning](#functioning) +- [Related Documentation](#related) +- [Known Issues](#issues) + +## Requirements +### IAM Permissions + +This module requires the following OCI IAM permissions in the compartments where VSS resources (recipes and targets) are managed. + +``` +allow group to manage vss-family in compartment +allow group to use instances in compartment +allow group to read repos in compartment +``` + +### Terraform Version < 1.3.x and Optional Object Type Attributes +This module relies on [Terraform Optional Object Type Attributes feature](https://developer.hashicorp.com/terraform/language/expressions/type-constraints#optional-object-type-attributes), which is experimental from Terraform 0.14.x to 1.2.x. It shortens the amount of input values in complex object types, by having Terraform automatically inserting a default value for any missing optional attributes. The feature has been promoted and it is no longer experimental in Terraform 1.3.x. + +**As is, this module can only be used with Terraform versions up to 1.2.x**, because it can be consumed by other modules via [OCI Resource Manager service](https://docs.oracle.com/en-us/iaas/Content/ResourceManager/home.htm), that still does not support Terraform 1.3.x. + +Upon running *terraform plan* with Terraform versions prior to 1.3.x, Terraform displays the following warning: +``` +Warning: Experimental feature "module_variable_optional_attrs" is active +``` + +Note the warning is harmless. The code has been tested with Terraform 1.3.x and the implementation is fully compatible. + +If you really want to use Terraform 1.3.x, in [providers.tf](./providers.tf): +1. Change the terraform version requirement to: +``` +required_version = ">= 1.3.0" +``` +2. Remove the line: +``` +experiments = [module_variable_optional_attrs] +``` +## How to Invoke the Module + +Terraform modules can be invoked locally or remotely. + +For invoking the module locally, just set the module *source* attribute to the module file path (relative path works). The following example assumes the module is two folders up in the file system. +``` +module "scanning" { + source = "../.." + scanning_configuration = var.scanning_configuration +} +``` + +For invoking the module remotely, set the module *source* attribute to the VSS module folder in this repository, as shown: +``` +module "scanning" { + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//vss" + scanning_configuration = var.scanning_configuration +} +``` +For referring to a specific module version, append *ref=\* to the *source* attribute value, as in: +``` + source = "git@github.com:oracle-quickstart/terraform-oci-cis-landing-zone-security.git//vss?ref=v0.1.0" +``` + +## Module Functioning + +In this module, scanning recipes and targets are defined using the *scanning_configuration* object, that supports the following attributes: +- **default_compartment_id**: the default compartment for all resources managed by this module. It can be overriden by *compartment_id* attribute in each resource. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **default_defined_tags**: the default defined tags that are applied to all resources managed by this module. It can be overriden by *defined_tags* attribute in each resource. +- **default_freeform_tags**: the default freeform tags that are applied to all resources managed by this module. It can be overriden by *freeform_tags* attribute in each resource. +- **host_recipes**: the scanning recipes applicable to Compute instances. +- **host_targets**: the scanning target Compute instances. +- **container_recipes**: the scanning recipes applicable to container images. +- **container_targets**: the scanning target container images. + +### Defining Host Recipes + +Within *scanning_configuration*, use the *host_recipes* attribute to define the scanning recipes applicable to Compute instances. Each recipe is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *host_recipes* attribute supports the following attributes: +- **name**: the recipe name. +- **compartment_id**: the compartment where the host recipe is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **port_scan_level**: the port scan level. Valid values: "STANDARD", "LIGHT", "NONE". "STANDARD" checks the 1000 most common port numbers. "LIGHT" checks the 100 most common port numbers. "NONE" does not check for open ports. Default: "STANDARD". +- **schedule_settings**: the schedule settings for host scans. + - **type**: how often the scan occurs. Valid values: "WEEKLY", "DAILY". Default: "WEEKLY". + - **day_of_week**: day of week the scan occurs. Only valid for "WEEKLY" scans. Default: "SUNDAY". +- **agent_settings**: agent scan settings + - **scan_level**: the agent scan level. Valid values: "STANDARD", "NONE". "STANDARD" enables agent-based scanning. "NONE" disables agent-based scanning. Default: "STANDARD". + - **vendor**: the vendor for host scan. Valid values: "OCI". + - **cis_benchmark_scan_level**: the scan level for CIS benchmark. Valid values: "STRICT", "MEDIUM", "LIGHTWEIGHT", "NONE". "STRICT": if more than 20% of the CIS benchmarks fail, then the target is assigned a risk level of Critical. "MEDIUM": if more than 40% of the CIS benchmarks fail, then the target is assigned a risk level of High. "LIGHTWEIGHT": if more than 80% of the CIS benchmarks fail, then the target is assigned a risk level of High. "NONE": disables CIS benchmark scanning. Default: "STRICT". +- **file_scan_settings**: the file scan settings for host scans + - **enable**: whether file scans are enabled. + - **scan_recurrence**: scan recurrences in RFC-5545 section 3.3.10 format. Default: bi-weekly scans. Occurs on *schedule_settings*' *day_of_week* if *type* is "WEEKLY". Occurs on sundays if *schedule_settings*' *type* is "DAILY" ("FREQ=WEEKLY;INTERVAL=2;WKST=SU"). + - **folders_to_scan**: a list of folders to scan. Default: "/". + - **operating_system**: the target operating system for the file scan. Valid values: "LINUX", "WINDOWS". Default: "LINUX". +- **defined_tags**: the recipe defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the recipe freeform tags. *default_freeform_tags* is used if undefined. + +### Defining Host Targets + +Within *scanning_configuration*, use the *host_targets* attribute to define the Compute instances scanning targets. Each target is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *host_targets* attribute supports the following attributes: +- **name**: the target name. +- **description**: the target description. +- **compartment_id**: the compartment where the host target is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **target_compartment_id**: the target compartment containing the Compute instances to scan. All instances in the target compartment are scan targets. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **target_instance_ids**: a list of target Compute instances to scan within *target_compartment_id*. If unset, all instances within *target_compartment_id* are scan targets. This attribute is overloaded. It can be assigned either literal OCIDs or references (keys) to OCIDs, mixed and matched. See [External Dependencies](#ext_dep) for details. +- **host_recipe_key**: the key (index name) within *host_recipes* that identifies the recipe to apply to the target. +- **defined_tags**: the host target defined_tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the host target freeform_tags. *default_freeform_tags* is used if undefined. + +### Defining Container Recipes + +Within *scanning_configuration*, use the *container_recipes* attribute to define the scanning recipes applicable to container images. Each recipe is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *container_recipes* attribute supports the following attributes: +- **name**: the recipe name. +- **compartment_id**: the compartment where the container recipe is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **scan_level**: the scan level. Default: "STANDARD". +- **image_count**: the number of images to scan initially when the recipe is created. Default: 0. +- **defined_tags**: the recipe defined tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the recipe freeform tags. *default_freeform_tags* is used if undefined. + +### Defining Container Targets + +Within *scanning_configuration*, use the *container_targets* attribute to define the container images scanning targets. Each target is defined as an object whose index name must be unique and must not be changed once defined. As a convention, use uppercase strings for the index names. + +The *container_targets* attribute supports the following attributes: +- **name**: the target name. +- **description**: the target description. +- **compartment_id**: the compartment where the container target is created. *default_compartment_id* is used if undefined. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. +- **target_registry**: the target image registry settings. + - **compartment_id**: the registry target compartment for container images. All repositories in this compartment are scan targets. This attribute is overloaded. It can be assigned either a literal OCID or a reference (a key) to an OCID. See [External Dependencies](#ext_dep) for details. + - **type**: the registry type. Default: "OCIR". + - **repositories**: a list of repositories to scan images. If undefined, the target defaults to scanning all repositories in the *compartment_id*. + - **url**: the URL of the registry. Required for non-OCI registry types (for OCI registry types, it is inferred from the tenancy). +- **container_recipe_key**: the key (index name) within *container_recipes* that identifies the recipe to apply to the target. +- **defined_tags**: the container target defined_tags. *default_defined_tags* is used if undefined. +- **freeform_tags**: the container target freeform_tags. *default_freeform_tags* is used if undefined. + +## An Example + +The following snippet defines a host recipe (*VISION-HOST-RECIPE*), a host target (*VISION-HOST-TARGET*), a container recipe (*VISION-CONTAINER-RECIPE*) and a container target (*VISION-CONTAINER-TARGET*), all created in the same compartment defined by *default_compartment_id*. The example uses module defaults and only defines the minimum required attributes. *VISION-HOST-RECIPE* recipe is used by *VISION-HOST-TARGET* target, while *VISION-CONTAINER-RECIPE* recipe is used by *VISION-CONTAINER-TARGET* target. + +``` +scanning_configuration = { + default_compartment_id = "ocid1.compartment.oc1..aaaaaa...4ja" + host_recipes = { + VISION-HOST-RECIPE = { + name = "vision-host-scan-recipe" + } + } + + host_targets = { + VISION-HOST-TARGET = { + name = "vision-host-scan-target" + target_compartment_id = "ocid1.compartment.oc1..aaaaaa...kzq" + host_recipe_key = "VISION-HOST-RECIPE" # this is a reference to the recipe defined in host_recipes attribute. + } + } + + container_recipes = { + VISION-CONTAINER-RECIPE = { + name = "vision-container-scan-recipe" + } + } + + container_targets = { + VISION-CONTAINER-TARGET = { + name = "vision-container-scan-target" + target_registry = { + compartment_id = "ocid1.compartment.oc1..aaaaaa...kzq" + } + container_recipe_key = "VISION-CONTAINER-RECIPE" # this is a reference to the recipe defined in container_recipes attribute. + } + } +} +``` + +### External Dependencies + +The example above has some dependencies. Specifically, it requires *default_compartment_id*, *target_compartment_id* and *target_registry/compartment_id*. These values need to be obtained somehow. In some cases, you can simply get them from the teams that are managing those resources and operate on a manual copy-and-paste fashion. However, in the automation world, copying and pasting can be slow and error prone. More sophisticated automation approaches would get these dependencies from their producing Terraform configurations. With this scenario in mind, **the module overloads the attributes ending in *_id* and *_ids***. They can be assigned a literal OCID (as in the example above, for those whom copying and pasting is an acceptable approach) or a reference (a key) to an OCID. + +Rewriting the example above with the external dependency: + +``` +scanning_configuration = { + default_compartment_id = "SECURITY-CMP" + host_recipes = { + VISION-HOST-RECIPE = { + name = "vision-host-scan-recipe" + } + } + + host_targets = { + VISION-HOST-TARGET = { + name = "vision-host-scan-target" + target_compartment_id = "APPLICATION-CMP" + host_recipe_key = "VISION-HOST-RECIPE" # this is a reference to the recipe defined in host_recipes attribute. + } + } + + container_recipes = { + VISION-CONTAINER-RECIPE = { + name = "vision-container-scan-recipe" + } + } + + container_targets = { + VISION-CONTAINER-TARGET = { + name = "vision-container-scan-target" + target_registry = { + compartment_id = "APPLICATION-CMP" + } + container_recipe_key = "VISION-CONTAINER-RECIPE" # this is a reference to the recipe defined in container_recipes attribute. + } + } +} + +compartments_dependency = { + "SECURITY-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } + "APPLICATION-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...zrt" + } +} + +``` + +The example now relies on references to compartments (*SECURITY-CMP* and *APPLICATION-CMP* keys) rather than literal compartment OCIDs. These keys also need to be known somehow, but they are more readable than OCIDs and can have their naming standardized by DevOps, facilitating automation. + +The *compartments_dependency* map is typically the output of another Terraform configuration that gets published in a well-defined location for easy consumption. For instance, [this example](./examples/external_dependency/README.md) uses OCI Object Storage object for sharing dependencies across Terraform configurations. + +The external dependency approach helps with the creation of loosely coupled Terraform configurations with clearly defined dependencies between them, avoiding copying and pasting. + +## Related Documentation +- [Overview of Scanning](https://docs.oracle.com/en-us/iaas/scanning/using/overview.htm) +- [Scanning in Terraform OCI Provider](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/vulnerability_scanning_host_scan_target) + +## Known Issues +None. diff --git a/vss/SPEC.md b/vss/SPEC.md new file mode 100644 index 0000000..3ae5e09 --- /dev/null +++ b/vss/SPEC.md @@ -0,0 +1,42 @@ +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | < 1.3.0 | + +## Providers + +| Name | Version | +|------|---------| +| [oci](#provider\_oci) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [oci_vulnerability_scanning_container_scan_recipe.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/vulnerability_scanning_container_scan_recipe) | resource | +| [oci_vulnerability_scanning_container_scan_target.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/vulnerability_scanning_container_scan_target) | resource | +| [oci_vulnerability_scanning_host_scan_recipe.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/vulnerability_scanning_host_scan_recipe) | resource | +| [oci_vulnerability_scanning_host_scan_target.these](https://registry.terraform.io/providers/oracle/oci/latest/docs/resources/vulnerability_scanning_host_scan_target) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [compartments\_dependency](#input\_compartments\_dependency) | A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type. | `map(any)` | `null` | no | +| [enable\_output](#input\_enable\_output) | Whether Terraform should enable module output. | `bool` | `true` | no | +| [module\_name](#input\_module\_name) | The module name. | `string` | `"vss"` | no | +| [scanning\_configuration](#input\_scanning\_configuration) | Vulnerability scanning configuration settings, defining all aspects to manage scanning aspects in OCI. Please see the comments within each attribute for details. |
object({

default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object.
default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object.

host_recipes = optional(map(object({ # the host recipes to manage in this configuration.
compartment_id = optional(string) # the compartment where the host recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string # recipe name.
port_scan_level = optional(string)
schedule_settings = optional(object({
type = string
day_of_week = optional(string)
}))
agent_settings = optional(object({
scan_level = optional(string)
vendor = optional(string)
cis_benchmark_scan_level = optional(string)
}))
file_scan_settings = optional(object({
enable = optional(bool)
scan_recurrence = optional(string)
folders_to_scan = optional(list(string))
operating_system = optional(string)
}))
defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined.
})))

host_targets = optional(map(object({
compartment_id = optional(string) # the compartment where the host target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string
target_compartment_id = string # the target compartment. All hosts (instances) in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
target_instance_ids = optional(list(string)) # the specific hosts (instances) to scan in the target compartment. Leave unset to scan all instances. It can be either instances OCIDs or references (keys) to instances OCIDs.
host_recipe_key = string # the recipe key within host_recipes attribute to use for the target.
description = optional(string)
defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined.
})))

container_recipes = optional(map(object({ # the container recipes to manage in this configuration.
compartment_id = optional(string) # the compartment where the container recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string # recipe name.
scan_level = optional(string) # the scan level. Default: "STANDARD".
image_count = optional(number) # the number of images to scan initially when the recipe is created. Default: 0
defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined.
})))

container_targets = optional(map(object({
compartment_id = optional(string) # the compartment where the container target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
name = string
container_recipe_key = string # the recipe key within container_recipes attribute to use for the target.
description = optional(string)
target_registry = object({
compartment_id = string # the registry target compartment. All containers in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID.
type = optional(string) # the registry type. Default: "OCIR".
repositories = optional(list(string)) # list of repositories to scan images. If undefined, the target defaults to scanning all repos in the compartment_ocid.
url = optional(string) # URL of the registry. Required for non-OCI registry types (for OCI registry types, it can be inferred from the tenancy).
})
defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined.
freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined.
})))

})
| n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [scanning\_container\_recipes](#output\_scanning\_container\_recipes) | The VSS container recipes. | +| [scanning\_container\_targets](#output\_scanning\_container\_targets) | The VSS container targets. | +| [scanning\_host\_recipes](#output\_scanning\_host\_recipes) | The VSS host (instance) recipes. | +| [scanning\_host\_targets](#output\_scanning\_host\_targets) | The VSS host (instance) targets. | diff --git a/vss/examples/external_dependency/README.md b/vss/examples/external_dependency/README.md new file mode 100644 index 0000000..3c5eb77 --- /dev/null +++ b/vss/examples/external_dependency/README.md @@ -0,0 +1,48 @@ +# CIS OCI Vulnerability Scanning Module Example - External Dependency + +## Introduction + +This example shows how to deploy Vulnerability Scanning resources in OCI using the [VSS module](../..). It is functionally equivalent to [Vision example](../vision/), but it obtains its dependencies from OCI Object Storage objects, specified in *oci_compartments_dependency* and *oci_vaults_dependency* variables settings. + +It defines a host recipe (*VISION-HOST-RECIPE*), a host target (*VISION-HOST-TARGET*), a container recipe (*VISION-CONTAINER-RECIPE*) and a container target (*VISION-CONTAINER-TARGET*), all created in the same compartment defined by *default_compartment_ocid*. The example uses module defaults and only defines the minimum required attributes. *VISION-HOST-RECIPE* recipe is used by *VISION-HOST-TARGET* target, while *VISION-CONTAINER-RECIPE* recipe is used by *VISION-CONTAINER-TARGET* target. + +Because it needs to read from OCI Object Storage, the following permissions are required for the executing user, in addition to the permissions required by the [VSS module](../..) itself. + +``` +allow group to read objectstorage-namespaces in tenancy +allow group to read buckets in compartment +allow group to read objects in compartment where target.bucket.name = '' +``` + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *security_zones_configuration* input variable, by making the appropriate substitutions: + - Replace \** placeholder by the appropriate compartment reference, expected to be found in the OCI Object Storage object referred by *\* within the object pointed by *oci_compartments_dependency*. + - Replace \** placeholder by the appropriate target compartment reference, expected to be found in the OCI Object Storage object referred by *\* within the object pointed by *oci_compartments_dependency*. + - Replace \** placeholder by the appropriate target registry compartment reference, expected to be found in the OCI Object Storage object referred by *\* within the object pointed by *oci_compartments_dependency*. + - Replace *\* placeholders by the OCI Object Storage buckets that contain the object referred by *\*. + - Replace *\* placeholders by the OCI Object Storage objects with the compartments references. This object is supposedly stored in OCI Object Storage by the module that manages compartments. + +The OCI Object Storage object with compartments dependencies (*oci_compartments_dependency*) is expected to have a structure like this: +``` +{ + "SECURITY-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...xuq" + } + "APPLICATION-CMP" : { + "id" : "ocid1.compartment.oc1..aaaaaa...zrt" + } +} +``` + +Note the compartment OCIDs are referred by *SECURITY-CMP* and *APPLICATION-CMP* keys. These are the values that should be used when replacing *\*, \** and \** placeholders. + +Refer to [VSS module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/vss/examples/external_dependency/input.auto.tfvars.template b/vss/examples/external_dependency/input.auto.tfvars.template new file mode 100644 index 0000000..0d6026e --- /dev/null +++ b/vss/examples/external_dependency/input.auto.tfvars.template @@ -0,0 +1,70 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#------------------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment reference, expected to be found in the +# OCI Object Storage object referred by within the object pointed by oci_compartments_dependency. +# 4. Replace placeholder by the appropriate compartment reference, expected to be found in the +# OCI Object Storage object referred by within the object pointed by oci_compartments_dependency. +# 5. Replace placeholder by the appropriate compartment reference, expected to be found in the +# OCI Object Storage object referred by within the object pointed by oci_compartments_dependency. +# 6. Replace placeholders by the OCI Object Storage buckets that contain the object referred by . +# 7. Replace placeholders by the OCI Object Storage objects with the compartments references. This +# object is supposedly stored in OCI Object Storage by the module that manages compartments. +#-------------------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +scanning_configuration = { + default_compartment_id = "" + + host_recipes = { + VISION-HOST-RECIPE = { + name = "vision-host-scan-recipe" + } + } + + host_targets = { + VISION-HOST-TARGET = { + name = "vision-host-scan-target" + target_compartment_id = "" + host_recipe_key = "VISION-HOST-RECIPE" # this is a reference to the recipe defined in host_recipes attribute. + } + } + + container_recipes = { + VISION-CONTAINER-RECIPE = { + name = "vision-container-scan-recipe" + } + } + + container_targets = { + VISION-CONTAINER-TARGET = { + name = "vision-container-scan-target" + target_registry = { + compartment_id = "" + } + container_recipe_key = "VISION-CONTAINER-RECIPE" # this is a reference to the recipe defined in container_recipes attribute. + } + } +} + +oci_compartments_dependency = { + bucket = "" + object = "" +} \ No newline at end of file diff --git a/vss/examples/external_dependency/main.tf b/vss/examples/external_dependency/main.tf new file mode 100644 index 0000000..7280d48 --- /dev/null +++ b/vss/examples/external_dependency/main.tf @@ -0,0 +1,20 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +data "oci_objectstorage_namespace" "this" { + count = var.oci_compartments_dependency != null ? 1 : 0 + compartment_id = var.tenancy_ocid +} + +data "oci_objectstorage_object" "compartments" { + count = var.oci_compartments_dependency != null ? 1 : 0 + bucket = var.oci_compartments_dependency.bucket + namespace = data.oci_objectstorage_namespace.this[0].namespace + object = var.oci_compartments_dependency.object +} + +module "vision_scanning" { + source = "../../" + scanning_configuration = var.scanning_configuration + compartments_dependency = var.oci_compartments_dependency != null ? jsondecode(data.oci_objectstorage_object.compartments[0].content) : null +} diff --git a/vss/examples/external_dependency/outputs.tf b/vss/examples/external_dependency/outputs.tf new file mode 100644 index 0000000..a2684c9 --- /dev/null +++ b/vss/examples/external_dependency/outputs.tf @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "host_recipes" { + value = module.vision_scanning.scanning_host_recipes +} + +output "host_targets" { + value = module.vision_scanning.scanning_host_targets +} \ No newline at end of file diff --git a/vss/examples/external_dependency/providers.tf b/vss/examples/external_dependency/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/vss/examples/external_dependency/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/vss/examples/external_dependency/variables.tf b/vss/examples/external_dependency/variables.tf new file mode 100644 index 0000000..16a5ad4 --- /dev/null +++ b/vss/examples/external_dependency/variables.tf @@ -0,0 +1,86 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "scanning_configuration" { + description = "Vulnerability scanning configuration settings, defining all aspects to manage scanning aspects in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + host_recipes = optional(map(object({ # the host recipes to manage in this configuration. + compartment_id = optional(string) # the compartment where the host recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # recipe name. + port_scan_level = optional(string) + schedule_settings = optional(object({ + type = string + day_of_week = optional(string) + })) + agent_settings = optional(object({ + scan_level = optional(string) + vendor = optional(string) + cis_benchmark_scan_level = optional(string) + })) + file_scan_settings = optional(object({ + enable = optional(bool) + scan_recurrence = optional(string) + folders_to_scan = optional(list(string)) + operating_system = optional(string) + })) + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + host_targets = optional(map(object({ + compartment_id = optional(string) # the compartment where the host target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string + target_compartment_id = string # the target compartment. All hosts (instances) in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + target_instance_ids = optional(list(string)) # the specific hosts (instances) to scan in the target compartment. Leave unset to scan all instances. It can be either instances OCIDs or references (keys) to instances OCIDs. + host_recipe_key = string # the recipe key within host_recipes attribute to use for the target. + description = optional(string) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_recipes = optional(map(object({ # the container recipes to manage in this configuration. + compartment_id = optional(string) # the compartment where the container recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # recipe name. + scan_level = optional(string) # the scan level. Default: "STANDARD". + image_count = optional(number) # the number of images to scan initially when the recipe is created. Default: 0 + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_targets = optional(map(object({ + compartment_id = optional(string) # the compartment where the container target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string + container_recipe_key = string # the recipe key within container_recipes attribute to use for the target. + description = optional(string) + target_registry = object({ + compartment_id = string # the registry target compartment. All containers in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + type = optional(string) # the registry type. Default: "OCIR". + repositories = optional(list(string)) # list of repositories to scan images. If undefined, the target defaults to scanning all repos in the compartment_ocid. + url = optional(string) # URL of the registry. Required for non-OCI registry types (for OCI registry types, it can be inferred from the tenancy). + }) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + }) +} + +variable "oci_compartments_dependency" { + type = object({ + bucket = string + object = string + }) + default = null +} \ No newline at end of file diff --git a/vss/examples/vision/README.md b/vss/examples/vision/README.md new file mode 100644 index 0000000..14c7c2c --- /dev/null +++ b/vss/examples/vision/README.md @@ -0,0 +1,24 @@ +# CIS OCI Vulnerability Scanning Module Example + +## Introduction + +This example shows how to deploy Vulnerability Scanning resources in OCI using the [VSS module](../..). + +It defines a host recipe (*VISION-HOST-RECIPE*), a host target (*VISION-HOST-TARGET*), a container recipe (*VISION-CONTAINER-RECIPE*) and a container target (*VISION-CONTAINER-TARGET*), all created in the same compartment defined by *default_compartment_ocid*. The example uses module defaults and only defines the minimum required attributes. + +## Using this example +1. Rename *input.auto.tfvars.template* to *\.auto.tfvars*, where *\* is any name of your choice. + +2. Within *\.auto.tfvars*, provide tenancy connectivity information and adjust the *security_zones_configuration* input variable, by making the appropriate substitutions: + - Replace \** placeholder by the appropriate compartment OCID. + - Replace \** placeholder by the appropriate compartment OCID. + - Replace \** placeholder by the appropriate compartment OCIDs, where images registries are stored. + +Refer to [VSS module README.md](../../README.md) for overall attributes usage. + +3. In this folder, run the typical Terraform workflow: +``` +terraform init +terraform plan -out plan.out +terraform apply plan.out +``` \ No newline at end of file diff --git a/vss/examples/vision/input.auto.tfvars.template b/vss/examples/vision/input.auto.tfvars.template new file mode 100644 index 0000000..b0783a1 --- /dev/null +++ b/vss/examples/vision/input.auto.tfvars.template @@ -0,0 +1,60 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-------------------------------------------------------------------------------------------------------------------------------------- +# 1. Rename this file to .auto.tfvars, where is a name of your choice. +# 2. Provide values for "Tenancy Connectivity Variables". +# 3. Replace placeholder by the appropriate compartment OCID. +# 4. Replace placeholder by the appropriate compartment OCID. +# 5. Replace placeholder by the appropriate compartment OCIDs, +# where images registries are stored. +#-------------------------------------------------------------------------------------------------------------------------------------- + +#--------------------------------------- +# Tenancy Connectivity Variables +#--------------------------------------- + +tenancy_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "Tenancy: "). +user_ocid = "" # Get this from OCI Console (after logging in, go to top-right-most menu item and click option "My profile"). +fingerprint = "" # The fingerprint can be gathered from your user account. In the "My profile page, click "API keys" on the menu in left hand side. +private_key_path = "" # This is the full path on your local system to the API signing private key. +private_key_password = "" # This is the password that protects the private key, if any. +region = "" # This is your region, where all other events are created. It can be the same as home_region. + +#--------------------------------------- +# Input variable +#--------------------------------------- + +scanning_configuration = { + default_compartment_ocid = "" + + host_recipes = { + VISION-HOST-RECIPE = { + name = "vision-host-scan-recipe" + } + } + + host_targets = { + VISION-HOST-TARGET = { + name = "vision-host-scan-target" + target_compartment_ocid = "" + host_recipe_key = "VISION-HOST-RECIPE" # this is a reference to the recipe defined in host_recipes attribute. + } + } + + container_recipes = { + VISION-CONTAINER-RECIPE = { + name = "vision-container-scan-recipe" + } + } + + container_targets = { + VISION-CONTAINER-TARGET = { + name = "vision-container-scan-target" + target_registry = { + compartment_ocid = "" + } + container_recipe_key = "VISION-CONTAINER-RECIPE" # this is a reference to the recipe defined in container_recipes attribute. + } + } +} \ No newline at end of file diff --git a/vss/examples/vision/main.tf b/vss/examples/vision/main.tf new file mode 100644 index 0000000..826907a --- /dev/null +++ b/vss/examples/vision/main.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +module "vision_scanning" { + source = "../../" + scanning_configuration = var.scanning_configuration +} diff --git a/vss/examples/vision/outputs.tf b/vss/examples/vision/outputs.tf new file mode 100644 index 0000000..a2684c9 --- /dev/null +++ b/vss/examples/vision/outputs.tf @@ -0,0 +1,10 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "host_recipes" { + value = module.vision_scanning.scanning_host_recipes +} + +output "host_targets" { + value = module.vision_scanning.scanning_host_targets +} \ No newline at end of file diff --git a/vss/examples/vision/providers.tf b/vss/examples/vision/providers.tf new file mode 100644 index 0000000..5061cb6 --- /dev/null +++ b/vss/examples/vision/providers.tf @@ -0,0 +1,21 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +provider "oci" { + region = var.region + tenancy_ocid = var.tenancy_ocid + user_ocid = var.user_ocid + fingerprint = var.fingerprint + private_key_path = var.private_key_path + private_key_password = var.private_key_password +} + +terraform { + required_version = "< 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} \ No newline at end of file diff --git a/vss/examples/vision/variables.tf b/vss/examples/vision/variables.tf new file mode 100644 index 0000000..f4ce35a --- /dev/null +++ b/vss/examples/vision/variables.tf @@ -0,0 +1,78 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "tenancy_ocid" {} +variable "region" {description = "Your tenancy region"} +variable "user_ocid" {default = ""} +variable "fingerprint" {default = ""} +variable "private_key_path" {default = ""} +variable "private_key_password" {default = ""} + +variable "scanning_configuration" { + description = "Vulnerability scanning configuration settings, defining all aspects to manage scanning aspects in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_ocid = string, # the default compartment where all resources are defined. It's overriden by the compartment_ocid attribute within each object. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + host_recipes = optional(map(object({ # the host recipes to manage in this configuration. + compartment_ocid = optional(string) # the compartment where the recipe is created. default_compartment_ocid is used if undefined. + name = string # recipe name. + port_scan_level = optional(string) + schedule_settings = optional(object({ + type = string + day_of_week = optional(string) + })) + agent_settings = optional(object({ + scan_level = optional(string) + vendor = optional(string) + cis_benchmark_scan_level = optional(string) + })) + file_scan_settings = optional(object({ + enable = optional(bool) + scan_recurrence = optional(string) + folders_to_scan = optional(list(string)) + operating_system = optional(string) + })) + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + host_targets = optional(map(object({ + compartment_ocid = optional(string) + name = string + target_compartment_ocid = string + target_instance_ocids = optional(list(string)) + host_recipe_key = string + description = optional(string) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_recipes = optional(map(object({ # the container recipes to manage in this configuration. + compartment_ocid = optional(string) # the compartment where the recipe is created. default_compartment_ocid is used if undefined. + name = string # recipe name. + scan_level = optional(string) + image_count = optional(number) + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_targets = optional(map(object({ + compartment_ocid = optional(string) + name = string + container_recipe_key = string + description = optional(string) + target_registry = object({ + compartment_ocid = string + type = optional(string) + repositories = optional(list(string)) # List of repositories to scan images in. If left empty, the target defaults to scanning all repos in the compartment_ocid. + url = optional(string) # URL of the registry. Required for non-OCIR registry types (for OCIR registry types, it can be inferred from the tenancy). + }) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + }) +} \ No newline at end of file diff --git a/vss/main.tf b/vss/main.tf new file mode 100644 index 0000000..8d8df94 --- /dev/null +++ b/vss/main.tf @@ -0,0 +1,84 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-- Host recipes +resource "oci_vulnerability_scanning_host_scan_recipe" "these" { + lifecycle { + create_before_destroy = true + } + for_each = var.scanning_configuration.host_recipes != null ? var.scanning_configuration.host_recipes : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.scanning_configuration.default_compartment_id)) > 0 ? var.scanning_configuration.default_compartment_id : var.compartments_dependency[var.scanning_configuration.default_compartment_id].id) + display_name = each.value.name + port_settings { + scan_level = each.value.port_scan_level != null ? upper(each.value.port_scan_level) : "STANDARD" + } + schedule { + type = each.value.schedule_settings != null ? (each.value.schedule_settings.type != null ? upper(each.value.schedule_settings.type) : "WEEKLY") : "WEEKLY" + day_of_week = each.value.schedule_settings != null ? (each.value.schedule_settings.day_of_week != null ? upper(each.value.schedule_settings.day_of_week) : "SUNDAY") : "SUNDAY" + } + agent_settings { + scan_level = each.value.agent_settings != null ? (each.value.agent_settings.scan_level != null ? upper(each.value.agent_settings.scan_level) : "STANDARD") : "STANDARD" + agent_configuration { + vendor = each.value.agent_settings != null ? (each.value.agent_settings.vendor != null ? upper(each.value.agent_settings.vendor) : "OCI") : "OCI" + cis_benchmark_settings { + scan_level = each.value.agent_settings != null ? (each.value.agent_settings.cis_benchmark_scan_level != null ? upper(each.value.agent_settings.cis_benchmark_scan_level) : "STRICT") : "STRICT" + } + } + } + application_settings { + application_scan_recurrence = each.value.file_scan_settings != null ? (each.value.file_scan_settings.scan_recurrence != null ? upper(each.value.file_scan_settings.scan_recurrence) : (each.value.schedule_settings != null ? (each.value.schedule_settings.scan_type != null ? (each.value.schedule_settings.scan_type == "WEEKLY" ? "FREQ=WEEKLY;INTERVAL=2;WKST=${substr(upper(each.value.schedule_settings.day_of_week != null ? each.value.schedule_settings.day_of_week : "SUNDAY"),0,2)}" : "FREQ=WEEKLY;INTERVAL=2;WKST=SU") : "FREQ=WEEKLY;INTERVAL=2;WKST=SU") : "FREQ=WEEKLY;INTERVAL=2;WKST=SU")) : "FREQ=WEEKLY;INTERVAL=2;WKST=SU" + folders_to_scan { + folder = each.value.file_scan_settings != null ? (each.value.file_scan_settings.folders_to_scan != null ? join(";", each.value.file_scan_settings.folders_to_scan) : "/") : "/" + operatingsystem = each.value.file_scan_settings != null ? (each.value.file_scan_settings.operating_system != null ? upper(each.value.file_scan_settings.operating_system) : "LINUX") : "LINUX" + } + is_enabled = each.value.file_scan_settings != null ? (each.value.file_scan_settings.enable != null ? each.value.file_scan_settings.enable : true) : true + } + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.scanning_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.scanning_configuration.default_freeform_tags) +} + +#-- Host targets +resource "oci_vulnerability_scanning_host_scan_target" "these" { + for_each = var.scanning_configuration.host_targets != null ? var.scanning_configuration.host_targets : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.scanning_configuration.default_compartment_id)) > 0 ? var.scanning_configuration.default_compartment_id : var.compartments_dependency[var.scanning_configuration.default_compartment_id].id) + display_name = each.value.name + description = each.value.description != null ? each.value.description : each.value.name + host_scan_recipe_id = oci_vulnerability_scanning_host_scan_recipe.these[each.value.host_recipe_key].id + target_compartment_id = length(regexall("^ocid1.*$", each.value.target_compartment_id)) > 0 ? each.value.target_compartment_id : var.compartments_dependency[each.value.target_compartment_id].id + instance_ids = each.value.target_instance_ids != null ? [ for id in each.value.target_instance_ids : length(regexall("^ocid1.*$", id)) > 0 ? id : var.compartments_dependency[id].id ] : null + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.scanning_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.scanning_configuration.default_freeform_tags) +} + +#-- Container recipes +resource "oci_vulnerability_scanning_container_scan_recipe" "these" { + lifecycle { + create_before_destroy = true + } + for_each = var.scanning_configuration.container_recipes != null ? var.scanning_configuration.container_recipes : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.scanning_configuration.default_compartment_id)) > 0 ? var.scanning_configuration.default_compartment_id : var.compartments_dependency[var.scanning_configuration.default_compartment_id].id) + display_name = each.value.name + scan_settings { + scan_level = each.value.scan_level != null ? upper(each.value.scan_level) : "STANDARD" + } + image_count = each.value.image_count != null ? each.value.image_count : 0 + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.scanning_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.scanning_configuration.default_freeform_tags) +} + +#-- Container targets +resource "oci_vulnerability_scanning_container_scan_target" "these" { + for_each = var.scanning_configuration.container_targets != null ? var.scanning_configuration.container_targets : {} + compartment_id = each.value.compartment_id != null ? (length(regexall("^ocid1.*$", each.value.compartment_id)) > 0 ? each.value.compartment_id : var.compartments_dependency[each.value.compartment_id].id) : (length(regexall("^ocid1.*$", var.scanning_configuration.default_compartment_id)) > 0 ? var.scanning_configuration.default_compartment_id : var.compartments_dependency[var.scanning_configuration.default_compartment_id].id) + display_name = each.value.name + description = each.value.description != null ? each.value.description : each.value.name + container_scan_recipe_id = oci_vulnerability_scanning_container_scan_recipe.these[each.value.container_recipe_key].id + target_registry { + compartment_id = length(regexall("^ocid1.*$", each.value.target_registry.compartment_id)) > 0 ? each.value.target_registry.compartment_id : var.compartments_dependency[each.value.target_registry.compartment_id].id + type = each.value.target_registry.type != null ? upper(each.value.target_registry.type) : "OCIR" + repositories = each.value.target_registry.repositories + url = each.value.target_registry.url + } + defined_tags = each.value.defined_tags != null ? each.value.defined_tags : var.scanning_configuration.default_defined_tags + freeform_tags = merge(local.cislz_module_tag, each.value.freeform_tags != null ? each.value.freeform_tags : var.scanning_configuration.default_freeform_tags) +} \ No newline at end of file diff --git a/vss/metadata.tf b/vss/metadata.tf new file mode 100644 index 0000000..b2d9c2d --- /dev/null +++ b/vss/metadata.tf @@ -0,0 +1,7 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +#-- Used to inform module and release number. +locals { + cislz_module_tag = {"cislz-terraform-module" : fileexists("${path.module}/../release.txt") ? "${var.module_name}/${file("${path.module}/../release.txt")}" : "${var.module_name}"} +} \ No newline at end of file diff --git a/vss/outputs.tf b/vss/outputs.tf new file mode 100644 index 0000000..ce18bd6 --- /dev/null +++ b/vss/outputs.tf @@ -0,0 +1,22 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +output "scanning_host_recipes" { + description = "The VSS host (instance) recipes." + value = var.enable_output ? oci_vulnerability_scanning_host_scan_recipe.these : null +} + +output "scanning_container_recipes" { + description = "The VSS container recipes." + value = var.enable_output ? oci_vulnerability_scanning_container_scan_recipe.these : null +} + +output "scanning_host_targets" { + description = "The VSS host (instance) targets." + value = var.enable_output ? oci_vulnerability_scanning_host_scan_target.these : null +} + +output "scanning_container_targets" { + description = "The VSS container targets." + value = var.enable_output ? oci_vulnerability_scanning_container_scan_target.these : null +} \ No newline at end of file diff --git a/vss/providers.tf b/vss/providers.tf new file mode 100644 index 0000000..0486bd6 --- /dev/null +++ b/vss/providers.tf @@ -0,0 +1,14 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +terraform { + required_version = " < 1.3.0" + required_providers { + oci = { + source = "oracle/oci" + } + } + experiments = [module_variable_optional_attrs] +} + + diff --git a/vss/variables.tf b/vss/variables.tf new file mode 100644 index 0000000..86a8a93 --- /dev/null +++ b/vss/variables.tf @@ -0,0 +1,89 @@ +# Copyright (c) 2023 Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +variable "scanning_configuration" { + description = "Vulnerability scanning configuration settings, defining all aspects to manage scanning aspects in OCI. Please see the comments within each attribute for details." + type = object({ + + default_compartment_id = string, # the default compartment where all resources are defined. It's overriden by the compartment_id attribute within vaults and keys attributes. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + default_defined_tags = optional(map(string)), # the default defined tags. It's overriden by the defined_tags attribute within each object. + default_freeform_tags = optional(map(string)), # the default freeform tags. It's overriden by the frreform_tags attribute within each object. + + host_recipes = optional(map(object({ # the host recipes to manage in this configuration. + compartment_id = optional(string) # the compartment where the host recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # recipe name. + port_scan_level = optional(string) + schedule_settings = optional(object({ + type = string + day_of_week = optional(string) + })) + agent_settings = optional(object({ + scan_level = optional(string) + vendor = optional(string) + cis_benchmark_scan_level = optional(string) + })) + file_scan_settings = optional(object({ + enable = optional(bool) + scan_recurrence = optional(string) + folders_to_scan = optional(list(string)) + operating_system = optional(string) + })) + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + host_targets = optional(map(object({ + compartment_id = optional(string) # the compartment where the host target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string + target_compartment_id = string # the target compartment. All hosts (instances) in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + target_instance_ids = optional(list(string)) # the specific hosts (instances) to scan in the target compartment. Leave unset to scan all instances. It can be either instances OCIDs or references (keys) to instances OCIDs. + host_recipe_key = string # the recipe key within host_recipes attribute to use for the target. + description = optional(string) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_recipes = optional(map(object({ # the container recipes to manage in this configuration. + compartment_id = optional(string) # the compartment where the container recipe is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string # recipe name. + scan_level = optional(string) # the scan level. Default: "STANDARD". + image_count = optional(number) # the number of images to scan initially when the recipe is created. Default: 0 + defined_tags = optional(map(string)) # recipe defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # recipe freeform_tags. default_freeform_tags is used if undefined. + }))) + + container_targets = optional(map(object({ + compartment_id = optional(string) # the compartment where the container target is created. default_compartment_id is used if undefined. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + name = string + container_recipe_key = string # the recipe key within container_recipes attribute to use for the target. + description = optional(string) + target_registry = object({ + compartment_id = string # the registry target compartment. All containers in the compartment are scanning targets. It can be either a compartment OCID or a reference (a key) to the compartment OCID. + type = optional(string) # the registry type. Default: "OCIR". + repositories = optional(list(string)) # list of repositories to scan images. If undefined, the target defaults to scanning all repos in the compartment_ocid. + url = optional(string) # URL of the registry. Required for non-OCI registry types (for OCI registry types, it can be inferred from the tenancy). + }) + defined_tags = optional(map(string)) # target defined_tags. default_defined_tags is used if undefined. + freeform_tags = optional(map(string)) # target freeform_tags. default_freeform_tags is used if undefined. + }))) + + }) +} + +variable compartments_dependency { + description = "A map of objects containing the externally managed compartments this module may depend on. All map objects must have the same type and must contain at least an 'id' attribute (representing the compartment OCID) of string type." + type = map(any) + default = null +} + +variable enable_output { + description = "Whether Terraform should enable module output." + type = bool + default = true +} + +variable module_name { + description = "The module name." + type = string + default = "vss" +} \ No newline at end of file