From 8f1081078f9ac8c9daf5ef991ea66861e939a7ad Mon Sep 17 00:00:00 2001 From: rajatagarwal-ibm <108140212+rajatagarwal-ibm@users.noreply.github.com> Date: Fri, 9 Jun 2023 17:52:12 +0100 Subject: [PATCH] feat: added new variable `kms_encryption_enabled` which now needs to be set to `true` to enable KMS encryption
- added support to create KMS auth policy along with variable `skip_iam_authorization_policy` to toggle policy creation on or off
- added extra validation around the use of KMS variables
- added a new FSCloud profile terraform submodule (see `profiles/fscloud`) and an example on how to use it (see `examples/profiles`) (#467) --- README.md | 7 +- examples/default/variables.tf | 2 +- examples/fscloud/README.md | 15 ++ examples/fscloud/main.tf | 79 +++++++++ examples/fscloud/outputs.tf | 9 + examples/fscloud/provider.tf | 4 + examples/fscloud/variables.tf | 114 +++++++++++++ examples/fscloud/version.tf | 15 ++ main.tf | 24 ++- module-metadata.json | 88 ++++++++-- profiles/fscloud/README.md | 54 ++++++ profiles/fscloud/main.tf | 24 +++ profiles/fscloud/outputs.tf | 4 + profiles/fscloud/variables.tf | 312 ++++++++++++++++++++++++++++++++++ profiles/fscloud/version.tf | 3 + storage.tf | 2 +- tests/other_test.go | 17 ++ tests/pr_test.go | 33 ++-- variables.tf | 19 +++ 19 files changed, 792 insertions(+), 33 deletions(-) create mode 100644 examples/fscloud/README.md create mode 100644 examples/fscloud/main.tf create mode 100644 examples/fscloud/outputs.tf create mode 100644 examples/fscloud/provider.tf create mode 100644 examples/fscloud/variables.tf create mode 100644 examples/fscloud/version.tf create mode 100644 profiles/fscloud/README.md create mode 100644 profiles/fscloud/main.tf create mode 100644 profiles/fscloud/outputs.tf create mode 100644 profiles/fscloud/variables.tf create mode 100644 profiles/fscloud/version.tf create mode 100644 tests/other_test.go diff --git a/README.md b/README.md index 1c19f11a..ead02fcd 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ You need the following permissions to run this module. ## Examples - [ End to end example with default values](examples/default) +- [ Financial Services Cloud profile example](examples/fscloud) --- @@ -126,6 +127,7 @@ No modules. | Name | Type | |------|------| +| [ibm_iam_authorization_policy.block_storage_policy](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/iam_authorization_policy) | resource | | [ibm_is_floating_ip.secondary_fip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_floating_ip) | resource | | [ibm_is_floating_ip.vsi_fip](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_floating_ip) | resource | | [ibm_is_instance.vsi](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/is_instance) | resource | @@ -146,10 +148,12 @@ No modules. | [access\_tags](#input\_access\_tags) | A list of access tags to apply to the VSI resources created by the module. For more information, see https://cloud.ibm.com/docs/account?topic=account-access-tags-tutorial. | `list(string)` | `[]` | no | | [allow\_ip\_spoofing](#input\_allow\_ip\_spoofing) | Allow IP spoofing on the primary network interface | `bool` | `false` | no | | [block\_storage\_volumes](#input\_block\_storage\_volumes) | List describing the block storage volumes that will be attached to each vsi |
list(
object({
name = string
profile = string
capacity = optional(number)
iops = optional(number)
encryption_key = optional(string)
})
)
| `[]` | no | -| [boot\_volume\_encryption\_key](#input\_boot\_volume\_encryption\_key) | CRN of boot volume encryption key | `string` | n/a | yes | +| [boot\_volume\_encryption\_key](#input\_boot\_volume\_encryption\_key) | CRN of boot volume encryption key | `string` | `null` | no | | [create\_security\_group](#input\_create\_security\_group) | Create security group for VSI. If this is passed as false, the default will be used | `bool` | n/a | yes | | [enable\_floating\_ip](#input\_enable\_floating\_ip) | Create a floating IP for each virtual server created | `bool` | `false` | no | +| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect Crypto Services instance in which the key specified in var.boot\_volume\_encryption\_key is coming from. | `string` | `null` | no | | [image\_id](#input\_image\_id) | Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region | `string` | n/a | yes | +| [kms\_encryption\_enabled](#input\_kms\_encryption\_enabled) | Set this to true to control the encryption keys used to encrypt the data that for the block storage volumes for VPC. If set to false, the data is encrypted by using randomly generated keys. For more info on encrypting block storage volumes, see https://cloud.ibm.com/docs/vpc?topic=vpc-creating-instances-byok | `bool` | `false` | no | | [load\_balancers](#input\_load\_balancers) | Load balancers to add to VSI |
list(
object({
name = string
type = string
listener_port = number
listener_protocol = string
connection_limit = number
algorithm = string
protocol = string
health_delay = number
health_retries = number
health_timeout = number
health_type = string
pool_member_port = string
security_group = optional(
object({
name = string
rules = list(
object({
name = string
direction = string
source = string
tcp = optional(
object({
port_max = number
port_min = number
})
)
udp = optional(
object({
port_max = number
port_min = number
})
)
icmp = optional(
object({
type = number
code = number
})
)
})
)
})
)
})
)
| `[]` | no | | [machine\_type](#input\_machine\_type) | VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles | `string` | n/a | yes | | [prefix](#input\_prefix) | The IBM Cloud platform API key needed to deploy IAM enabled resources | `string` | n/a | yes | @@ -161,6 +165,7 @@ No modules. | [secondary\_use\_vsi\_security\_group](#input\_secondary\_use\_vsi\_security\_group) | Use the security group created by this module in the secondary interface | `bool` | `false` | no | | [security\_group](#input\_security\_group) | Security group created for VSI |
object({
name = string
rules = list(
object({
name = string
direction = string
source = string
tcp = optional(
object({
port_max = number
port_min = number
})
)
udp = optional(
object({
port_max = number
port_min = number
})
)
icmp = optional(
object({
type = number
code = number
})
)
})
)
})
| n/a | yes | | [security\_group\_ids](#input\_security\_group\_ids) | IDs of additional security groups to be added to VSI deployment primary interface. A VSI interface can have a maximum of 5 security groups. | `list(string)` | `[]` | no | +| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of an IAM authorization policy that permits all Storage Blocks to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing\_kms\_instance\_guid variable. In addition, no policy is created if var.kms\_encryption\_enabled is set to false. | `bool` | `false` | no | | [ssh\_key\_ids](#input\_ssh\_key\_ids) | ssh key ids to use in creating vsi | `list(string)` | n/a | yes | | [subnets](#input\_subnets) | A list of subnet IDs where VSI will be deployed |
list(
object({
name = string
id = string
zone = string
cidr = string
})
)
| n/a | yes | | [tags](#input\_tags) | List of tags to apply to resources created by this module. | `list(string)` | `[]` | no | diff --git a/examples/default/variables.tf b/examples/default/variables.tf index 29d36553..a4505086 100644 --- a/examples/default/variables.tf +++ b/examples/default/variables.tf @@ -19,7 +19,7 @@ variable "region" { variable "prefix" { description = "The prefix that you would like to append to your resources" type = string - default = "test-landing-zone-vsi" + default = "slz-vsi" } variable "resource_tags" { diff --git a/examples/fscloud/README.md b/examples/fscloud/README.md new file mode 100644 index 00000000..7e6808da --- /dev/null +++ b/examples/fscloud/README.md @@ -0,0 +1,15 @@ +# Financial Services Cloud profile example + +An end-to-end example that uses the [Profile for IBM Cloud Framework for Financial Services](../../profiles/fscloud/) to deploy a VSI. + +The example uses the IBM Cloud Terraform provider to create the following infrastructure: +* A resource group, if one is not passed in. +* An SSH Key, if one is not passed in. +* A Secure Landing Zone virtual private cloud (VPC). +* An IBM Cloud VSI instance with Hyper Protect Crypto Services root key that is passed in for encrypting block storage. + +:exclamation: **Important:** In this example, only the VSI instance complies with the IBM Cloud Framework for Financial Services. Other parts of the infrastructure do not necessarily comply. + +## Before you begin + +- You need a Hyper Protect Crypto Services instance and root key available in the region that you want to deploy your VSI instance to. diff --git a/examples/fscloud/main.tf b/examples/fscloud/main.tf new file mode 100644 index 00000000..751b87bd --- /dev/null +++ b/examples/fscloud/main.tf @@ -0,0 +1,79 @@ +############################################################################## +# Locals +############################################################################## + +locals { + resource_group_id = var.resource_group != null ? data.ibm_resource_group.existing_resource_group[0].id : ibm_resource_group.resource_group[0].id + ssh_key_id = var.ssh_key != null ? data.ibm_is_ssh_key.existing_ssh_key[0].id : ibm_is_ssh_key.ssh_key[0].id +} + +############################################################################## +# Resource Group +# (if var.resource_group is null, create a new RG using var.prefix) +############################################################################## + +resource "ibm_resource_group" "resource_group" { + count = var.resource_group != null ? 0 : 1 + name = "${var.prefix}-rg" + quota_id = null +} + +data "ibm_resource_group" "existing_resource_group" { + count = var.resource_group != null ? 1 : 0 + name = var.resource_group +} + +############################################################################## +# SSH key +############################################################################## +resource "tls_private_key" "tls_key" { + count = var.ssh_key != null ? 0 : 1 + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "ibm_is_ssh_key" "ssh_key" { + count = var.ssh_key != null ? 0 : 1 + name = "${var.prefix}-ssh-key" + public_key = tls_private_key.tls_key[0].public_key_openssh +} + +data "ibm_is_ssh_key" "existing_ssh_key" { + count = var.ssh_key != null ? 1 : 0 + name = var.ssh_key +} + +############################################################################# +# Provision VPC +############################################################################# + +module "slz_vpc" { + source = "git::https://github.com/terraform-ibm-modules/terraform-ibm-landing-zone-vpc.git?ref=v7.2.0" + resource_group_id = local.resource_group_id + region = var.region + prefix = var.prefix + tags = var.resource_tags + name = var.vpc_name +} + +############################################################################# +# Provision VSI +############################################################################# + +module "slz_vsi" { + source = "../../profiles/fscloud" + resource_group_id = local.resource_group_id + image_id = var.image_id + create_security_group = var.create_security_group + security_group = var.security_group + tags = var.resource_tags + subnets = module.slz_vpc.subnet_zone_list + vpc_id = module.slz_vpc.vpc_id + prefix = var.prefix + machine_type = var.machine_type + user_data = var.user_data + boot_volume_encryption_key = var.boot_volume_encryption_key + existing_kms_instance_guid = var.existing_kms_instance_guid + vsi_per_subnet = var.vsi_per_subnet + ssh_key_ids = [local.ssh_key_id] +} diff --git a/examples/fscloud/outputs.tf b/examples/fscloud/outputs.tf new file mode 100644 index 00000000..b18b60a3 --- /dev/null +++ b/examples/fscloud/outputs.tf @@ -0,0 +1,9 @@ +output "slz_vpc" { + value = module.slz_vpc + description = "VPC module values" +} + +output "slz_vsi" { + value = module.slz_vsi + description = "VSI module values" +} diff --git a/examples/fscloud/provider.tf b/examples/fscloud/provider.tf new file mode 100644 index 00000000..df45ef50 --- /dev/null +++ b/examples/fscloud/provider.tf @@ -0,0 +1,4 @@ +provider "ibm" { + ibmcloud_api_key = var.ibmcloud_api_key + region = var.region +} diff --git a/examples/fscloud/variables.tf b/examples/fscloud/variables.tf new file mode 100644 index 00000000..f175ec1f --- /dev/null +++ b/examples/fscloud/variables.tf @@ -0,0 +1,114 @@ +variable "ibmcloud_api_key" { + description = "APIkey that's associated with the account to provision resources to" + type = string + sensitive = true +} + +variable "resource_group" { + type = string + description = "An existing resource group name to use for this example, if unset a new resource group will be created" + default = null +} + +variable "region" { + description = "The region to which to deploy the VPC" + type = string + default = "us-south" +} + +variable "prefix" { + description = "The prefix that you would like to append to your resources" + type = string + default = "rajat-fs-vsi" +} + +variable "resource_tags" { + description = "List of tags to apply to resources created by this module." + type = list(string) + default = [] +} + +variable "image_id" { + description = "Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region" + type = string + default = "r006-7ca7884c-c797-468e-a565-5789102aedc6" +} + +variable "machine_type" { + description = "VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles" + type = string + default = "cx2-2x4" +} + +variable "create_security_group" { + description = "Create security group for VSI" + type = string + default = false +} + +variable "security_group" { + description = "Security group created for VSI" + type = object({ + name = string + rules = list( + object({ + name = string + direction = string + source = string + tcp = optional( + object({ + port_max = number + port_min = number + }) + ) + udp = optional( + object({ + port_max = number + port_min = number + }) + ) + icmp = optional( + object({ + type = number + code = number + }) + ) + }) + ) + }) + default = null +} + +variable "user_data" { + description = "User data to initialize VSI deployment" + type = string + default = null +} + +variable "vsi_per_subnet" { + description = "Number of VSI instances for each subnet" + type = number + default = 1 +} + +variable "ssh_key" { + type = string + description = "An existing ssh key name to use for this example, if unset a new ssh key will be created" + default = null +} + +variable "vpc_name" { + type = string + description = "Name for VPC" + default = "vpc" +} + +variable "boot_volume_encryption_key" { + description = "CRN of boot volume encryption key" + type = string +} + +variable "existing_kms_instance_guid" { + description = "The GUID of the Hyper Protect Crypto Services or Key Protect instance in which the key specified in var.kms_key_crn and var.backup_encryption_key_crn is coming from. Required only if var.kms_encryption_enabled is set to true, var.skip_iam_authorization_policy is set to false, and you pass a value for var.kms_key_crn, var.backup_encryption_key_crn, or both." + type = string +} diff --git a/examples/fscloud/version.tf b/examples/fscloud/version.tf new file mode 100644 index 00000000..c2653efa --- /dev/null +++ b/examples/fscloud/version.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.3.0" + required_providers { + # Pin to the lowest provider version of the range defined in the main module's version.tf to ensure lowest version still works + ibm = { + source = "IBM-Cloud/ibm" + version = "1.52.0" + } + # The tls provider is not actually required by the module itself, just this example, so OK to use ">=" here instead of locking into a version + tls = { + source = "hashicorp/tls" + version = ">= 4.0.4" + } + } +} diff --git a/main.tf b/main.tf index 56210460..9eed7ec9 100644 --- a/main.tf +++ b/main.tf @@ -1,9 +1,18 @@ +locals { + # Validation (approach based on https://github.com/hashicorp/terraform/issues/25609#issuecomment-1057614400) + # tflint-ignore: terraform_unused_declarations + validate_kms_values = !var.kms_encryption_enabled && var.boot_volume_encryption_key != null ? tobool("When passing values for var.boot_volume_encryption_key, you must set var.kms_encryption_enabled to true. Otherwise unset them to use default encryption") : true + # tflint-ignore: terraform_unused_declarations + validate_kms_vars = var.kms_encryption_enabled && var.boot_volume_encryption_key == null ? tobool("When setting var.kms_encryption_enabled to true, a value must be passed for var.boot_volume_encryption_key") : true + # tflint-ignore: terraform_unused_declarations + validate_auth_policy = var.kms_encryption_enabled && var.skip_iam_authorization_policy == false && var.existing_kms_instance_guid == null ? tobool("When var.skip_iam_authorization_policy is set to false, and var.kms_encryption_enabled to true, a value must be passed for var.existing_kms_instance_guid in order to create the auth policy.") : true +} + ############################################################################## # Virtual Server Data ############################################################################## locals { - # Create list of VSI using subnets and VSI per subnet # Create list of VSI using subnets and VSI per subnet vsi_list = flatten([ # For each number in a range from 0 to VSI per subnet @@ -64,6 +73,17 @@ locals { # Create Virtual Servers ############################################################################## +resource "ibm_iam_authorization_policy" "block_storage_policy" { + count = var.skip_iam_authorization_policy ? 0 : 1 + source_service_name = "server-protect" + # commented the following as policy is not working as expected with this option. Related support case - https://cloud.ibm.com/unifiedsupport/cases?number=CS3419700 + # source_resource_group_id = var.resource_group_id + target_service_name = "hs-crypto" + target_resource_instance_id = var.existing_kms_instance_guid + roles = ["Reader"] + description = "Allow block storage volumes to be encrypted by Key Management instance." +} + resource "ibm_is_instance" "vsi" { for_each = local.vsi_map name = each.key @@ -107,7 +127,7 @@ resource "ibm_is_instance" "vsi" { } boot_volume { - encryption = var.boot_volume_encryption_key == "" ? null : var.boot_volume_encryption_key + encryption = var.boot_volume_encryption_key } # Only add volumes if volumes are being created by the module diff --git a/module-metadata.json b/module-metadata.json index 1c04c614..8ae9328e 100644 --- a/module-metadata.json +++ b/module-metadata.json @@ -30,7 +30,7 @@ "default": false, "pos": { "filename": "variables.tf", - "line": 106 + "line": 113 } }, "block_storage_volumes": { @@ -43,14 +43,13 @@ ], "pos": { "filename": "variables.tf", - "line": 187 + "line": 206 } }, "boot_volume_encryption_key": { "name": "boot_volume_encryption_key", "type": "string", "description": "CRN of boot volume encryption key", - "required": true, "pos": { "filename": "variables.tf", "line": 95 @@ -63,7 +62,7 @@ "required": true, "pos": { "filename": "variables.tf", - "line": 112 + "line": 119 } }, "enable_floating_ip": { @@ -76,9 +75,23 @@ ], "pos": { "filename": "variables.tf", - "line": 100 + "line": 107 } }, + "existing_kms_instance_guid": { + "name": "existing_kms_instance_guid", + "type": "string", + "description": "The GUID of the Hyper Protect Crypto Services instance in which the key specified in var.boot_volume_encryption_key is coming from.", + "source": [ + "ibm_iam_authorization_policy.block_storage_policy.target_resource_instance_id" + ], + "pos": { + "filename": "variables.tf", + "line": 101 + }, + "immutable": true, + "computed": true + }, "image_id": { "name": "image_id", "type": "string", @@ -94,6 +107,16 @@ "immutable": true, "computed": true }, + "kms_encryption_enabled": { + "name": "kms_encryption_enabled", + "type": "bool", + "description": "Set this to true to control the encryption keys used to encrypt the data that for the block storage volumes for VPC. If set to false, the data is encrypted by using randomly generated keys. For more info on encrypting block storage volumes, see https://cloud.ibm.com/docs/vpc?topic=vpc-creating-instances-byok", + "default": false, + "pos": { + "filename": "variables.tf", + "line": 194 + } + }, "load_balancers": { "name": "load_balancers", "type": "list(\n object({\n name = string\n type = string\n listener_port = number\n listener_protocol = string\n connection_limit = number\n algorithm = string\n protocol = string\n health_delay = number\n health_retries = number\n health_timeout = number\n health_type = string\n pool_member_port = string\n security_group = optional(\n object({\n name = string\n rules = list(\n object({\n name = string\n direction = string\n source = string\n tcp = optional(\n object({\n port_max = number\n port_min = number\n })\n )\n udp = optional(\n object({\n port_max = number\n port_min = number\n })\n )\n icmp = optional(\n object({\n type = number\n code = number\n })\n )\n })\n )\n })\n )\n })\n )", @@ -101,7 +124,7 @@ "default": [], "pos": { "filename": "variables.tf", - "line": 206 + "line": 225 } }, "machine_type": { @@ -160,7 +183,7 @@ "default": false, "pos": { "filename": "variables.tf", - "line": 372 + "line": 391 } }, "secondary_floating_ips": { @@ -173,7 +196,7 @@ ], "pos": { "filename": "variables.tf", - "line": 361 + "line": 380 } }, "secondary_security_groups": { @@ -183,7 +206,7 @@ "default": [], "pos": { "filename": "variables.tf", - "line": 340 + "line": 359 } }, "secondary_subnets": { @@ -193,7 +216,7 @@ "default": [], "pos": { "filename": "variables.tf", - "line": 321 + "line": 340 } }, "secondary_use_vsi_security_group": { @@ -203,7 +226,7 @@ "default": false, "pos": { "filename": "variables.tf", - "line": 334 + "line": 353 } }, "security_group": { @@ -213,7 +236,7 @@ "required": true, "pos": { "filename": "variables.tf", - "line": 117 + "line": 124 } }, "security_group_ids": { @@ -223,7 +246,20 @@ "default": [], "pos": { "filename": "variables.tf", - "line": 171 + "line": 178 + } + }, + "skip_iam_authorization_policy": { + "name": "skip_iam_authorization_policy", + "type": "bool", + "description": "Set to true to skip the creation of an IAM authorization policy that permits all Storage Blocks to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing_kms_instance_guid variable. In addition, no policy is created if var.kms_encryption_enabled is set to false.", + "default": false, + "source": [ + "ibm_iam_authorization_policy.block_storage_policy.count" + ], + "pos": { + "filename": "variables.tf", + "line": 200 } }, "ssh_key_ids": { @@ -384,6 +420,22 @@ } }, "managed_resources": { + "ibm_iam_authorization_policy.block_storage_policy": { + "mode": "managed", + "type": "ibm_iam_authorization_policy", + "name": "block_storage_policy", + "attributes": { + "count": "skip_iam_authorization_policy", + "target_resource_instance_id": "existing_kms_instance_guid" + }, + "provider": { + "name": "ibm" + }, + "pos": { + "filename": "main.tf", + "line": 76 + } + }, "ibm_is_floating_ip.secondary_fip": { "mode": "managed", "type": "ibm_is_floating_ip", @@ -398,7 +450,7 @@ }, "pos": { "filename": "main.tf", - "line": 134 + "line": 154 } }, "ibm_is_floating_ip.vsi_fip": { @@ -415,7 +467,7 @@ }, "pos": { "filename": "main.tf", - "line": 126 + "line": 146 } }, "ibm_is_instance.vsi": { @@ -438,7 +490,7 @@ }, "pos": { "filename": "main.tf", - "line": 67 + "line": 87 } }, "ibm_is_lb.lb": { @@ -556,7 +608,7 @@ }, "pos": { "filename": "main.tf", - "line": 54 + "line": 63 } }, "data.ibm_is_vpcs.vpcs": { @@ -571,7 +623,7 @@ }, "pos": { "filename": "main.tf", - "line": 48 + "line": 57 } } }, diff --git a/profiles/fscloud/README.md b/profiles/fscloud/README.md new file mode 100644 index 00000000..9a736d2b --- /dev/null +++ b/profiles/fscloud/README.md @@ -0,0 +1,54 @@ +# Profile for IBM Cloud Framework for Financial Services + +This code is a version of the [parent root module](../../) that includes a default configuration that complies with the relevant controls from the [IBM Cloud Framework for Financial Services](https://cloud.ibm.com/docs/framework-financial-services?topic=framework-financial-services-about). See the [Example for IBM Cloud Framework for Financial Services](/examples/fscloud/) for logic that uses this module. + +The default values in this profile were scanned by [IBM Code Risk Analyzer (CRA)](https://cloud.ibm.com/docs/code-risk-analyzer-cli-plugin?topic=code-risk-analyzer-cli-plugin-cra-cli-plugin#terraform-command) for compliance with the IBM Cloud Framework for Financial Services profile that is specified by the IBM Security and Compliance Center. The scan passed for all applicable goals. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [fscloud\_vsi](#module\_fscloud\_vsi) | ../../ | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allow\_ip\_spoofing](#input\_allow\_ip\_spoofing) | Allow IP spoofing on the primary network interface | `bool` | `false` | no | +| [block\_storage\_volumes](#input\_block\_storage\_volumes) | List describing the block storage volumes that will be attached to each vsi |
list(
object({
name = string
profile = string
capacity = optional(number)
iops = optional(number)
encryption_key = optional(string)
})
)
| `[]` | no | +| [boot\_volume\_encryption\_key](#input\_boot\_volume\_encryption\_key) | CRN of boot volume encryption key | `string` | n/a | yes | +| [create\_security\_group](#input\_create\_security\_group) | Create security group for VSI. If this is passed as false, the default will be used | `bool` | n/a | yes | +| [enable\_floating\_ip](#input\_enable\_floating\_ip) | Create a floating IP for each virtual server created | `bool` | `false` | no | +| [existing\_kms\_instance\_guid](#input\_existing\_kms\_instance\_guid) | The GUID of the Hyper Protect Crypto Services or Key Protect instance in which the key specified in var.kms\_key\_crn and var.backup\_encryption\_key\_crn is coming from. Required only if var.kms\_encryption\_enabled is set to true, var.skip\_iam\_authorization\_policy is set to false, and you pass a value for var.kms\_key\_crn, var.backup\_encryption\_key\_crn, or both. | `string` | n/a | yes | +| [image\_id](#input\_image\_id) | Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region | `string` | n/a | yes | +| [load\_balancers](#input\_load\_balancers) | Load balancers to add to VSI |
list(
object({
name = string
type = string
listener_port = number
listener_protocol = string
connection_limit = number
algorithm = string
protocol = string
health_delay = number
health_retries = number
health_timeout = number
health_type = string
pool_member_port = string
security_group = optional(
object({
name = string
rules = list(
object({
name = string
direction = string
source = string
tcp = optional(
object({
port_max = number
port_min = number
})
)
udp = optional(
object({
port_max = number
port_min = number
})
)
icmp = optional(
object({
type = number
code = number
})
)
})
)
})
)
})
)
| `[]` | no | +| [machine\_type](#input\_machine\_type) | VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles | `string` | n/a | yes | +| [prefix](#input\_prefix) | The prefix that you would like to append to your resources | `string` | n/a | yes | +| [resource\_group\_id](#input\_resource\_group\_id) | id of resource group to create VPC | `string` | n/a | yes | +| [security\_group](#input\_security\_group) | Security group created for VSI |
object({
name = string
rules = list(
object({
name = string
direction = string
source = string
tcp = optional(
object({
port_max = number
port_min = number
})
)
udp = optional(
object({
port_max = number
port_min = number
})
)
icmp = optional(
object({
type = number
code = number
})
)
})
)
})
| n/a | yes | +| [security\_group\_ids](#input\_security\_group\_ids) | IDs of additional security groups to be added to VSI deployment primary interface. A VSI interface can have a maximum of 5 security groups. | `list(string)` | `[]` | no | +| [skip\_iam\_authorization\_policy](#input\_skip\_iam\_authorization\_policy) | Set to true to skip the creation of an IAM authorization policy that permits all Storage Blocks to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing\_kms\_instance\_guid variable. In addition, no policy is created if var.kms\_encryption\_enabled is set to false. | `bool` | `false` | no | +| [ssh\_key\_ids](#input\_ssh\_key\_ids) | ssh key ids to use in creating vsi | `list(string)` | n/a | yes | +| [subnets](#input\_subnets) | A list of subnet IDs where VSI will be deployed |
list(
object({
name = string
id = string
zone = string
cidr = string
})
)
| n/a | yes | +| [tags](#input\_tags) | List of tags to apply to resources created by this module. | `list(string)` | `[]` | no | +| [user\_data](#input\_user\_data) | User data to initialize VSI deployment | `string` | n/a | yes | +| [vpc\_id](#input\_vpc\_id) | ID of VPC | `string` | n/a | yes | +| [vsi\_per\_subnet](#input\_vsi\_per\_subnet) | Number of VSI instances for each subnet | `number` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [slz\_vsi](#output\_slz\_vsi) | VSI module values | + diff --git a/profiles/fscloud/main.tf b/profiles/fscloud/main.tf new file mode 100644 index 00000000..c4218fa5 --- /dev/null +++ b/profiles/fscloud/main.tf @@ -0,0 +1,24 @@ +module "fscloud_vsi" { + source = "../../" + resource_group_id = var.resource_group_id + prefix = var.prefix + tags = var.tags + vpc_id = var.vpc_id + subnets = var.subnets + image_id = var.image_id + ssh_key_ids = var.ssh_key_ids + machine_type = var.machine_type + vsi_per_subnet = var.vsi_per_subnet + user_data = var.user_data + existing_kms_instance_guid = var.existing_kms_instance_guid + skip_iam_authorization_policy = var.skip_iam_authorization_policy + boot_volume_encryption_key = var.boot_volume_encryption_key + kms_encryption_enabled = true + enable_floating_ip = var.enable_floating_ip + allow_ip_spoofing = var.allow_ip_spoofing + create_security_group = var.create_security_group + security_group = var.security_group + security_group_ids = var.security_group_ids + block_storage_volumes = var.block_storage_volumes + load_balancers = var.load_balancers +} diff --git a/profiles/fscloud/outputs.tf b/profiles/fscloud/outputs.tf new file mode 100644 index 00000000..b16a8c6e --- /dev/null +++ b/profiles/fscloud/outputs.tf @@ -0,0 +1,4 @@ +output "slz_vsi" { + value = module.fscloud_vsi + description = "VSI module values" +} diff --git a/profiles/fscloud/variables.tf b/profiles/fscloud/variables.tf new file mode 100644 index 00000000..72e41157 --- /dev/null +++ b/profiles/fscloud/variables.tf @@ -0,0 +1,312 @@ +############################################################################## +# Account Variables +############################################################################## + +variable "resource_group_id" { + description = "id of resource group to create VPC" + type = string +} + +variable "prefix" { + description = "The prefix that you would like to append to your resources" + type = string + + validation { + error_message = "Prefix must begin and end with a letter and contain only letters, numbers, and - characters." + condition = can(regex("^([A-z]|[a-z][-a-z0-9]*[a-z0-9])$", var.prefix)) + } +} + +variable "tags" { + description = "List of tags to apply to resources created by this module." + type = list(string) + default = [] +} + +############################################################################## + + +############################################################################## +# VPC Variables +############################################################################## + +variable "vpc_id" { + description = "ID of VPC" + type = string +} + +variable "subnets" { + description = "A list of subnet IDs where VSI will be deployed" + type = list( + object({ + name = string + id = string + zone = string + cidr = string + }) + ) +} + +############################################################################## + + +############################################################################## +# VSI Variables +############################################################################## + +variable "image_id" { + description = "Image ID used for VSI. Run 'ibmcloud is images' to find available images in a region" + type = string +} + +variable "ssh_key_ids" { + description = "ssh key ids to use in creating vsi" + type = list(string) +} + +variable "machine_type" { + description = "VSI machine type. Run 'ibmcloud is instance-profiles' to get a list of regional profiles" + type = string +} + +variable "vsi_per_subnet" { + description = "Number of VSI instances for each subnet" + type = number +} + +variable "user_data" { + description = "User data to initialize VSI deployment" + type = string +} + +variable "boot_volume_encryption_key" { + description = "CRN of boot volume encryption key" + type = string +} + +variable "enable_floating_ip" { + description = "Create a floating IP for each virtual server created" + type = bool + default = false +} + +variable "allow_ip_spoofing" { + description = "Allow IP spoofing on the primary network interface" + type = bool + default = false +} + +variable "create_security_group" { + description = "Create security group for VSI. If this is passed as false, the default will be used" + type = bool +} + +variable "security_group" { + description = "Security group created for VSI" + type = object({ + name = string + rules = list( + object({ + name = string + direction = string + source = string + tcp = optional( + object({ + port_max = number + port_min = number + }) + ) + udp = optional( + object({ + port_max = number + port_min = number + }) + ) + icmp = optional( + object({ + type = number + code = number + }) + ) + }) + ) + }) + + validation { + error_message = "Each security group rule must have a unique name." + condition = ( + var.security_group == null + ? true + : length(distinct(var.security_group.rules[*].name)) == length(var.security_group.rules[*].name) + ) + } + + validation { + error_message = "Security group rule direction can only be `inbound` or `outbound`." + condition = var.security_group == null ? true : length( + distinct( + flatten([ + for rule in var.security_group.rules : + false if !contains(["inbound", "outbound"], rule.direction) + ]) + ) + ) == 0 + } + +} + +variable "security_group_ids" { + description = "IDs of additional security groups to be added to VSI deployment primary interface. A VSI interface can have a maximum of 5 security groups." + type = list(string) + default = [] + + validation { + error_message = "Security group IDs must be unique." + condition = length(var.security_group_ids) == length(distinct(var.security_group_ids)) + } + + validation { + error_message = "No more than 5 security groups can be added to a VSI deployment." + condition = length(var.security_group_ids) <= 5 + } +} + +variable "block_storage_volumes" { + description = "List describing the block storage volumes that will be attached to each vsi" + type = list( + object({ + name = string + profile = string + capacity = optional(number) + iops = optional(number) + encryption_key = optional(string) + }) + ) + default = [] + + validation { + error_message = "Each block storage volume must have a unique name." + condition = length(distinct(var.block_storage_volumes[*].name)) == length(var.block_storage_volumes) + } +} + +variable "load_balancers" { + description = "Load balancers to add to VSI" + type = list( + object({ + name = string + type = string + listener_port = number + listener_protocol = string + connection_limit = number + algorithm = string + protocol = string + health_delay = number + health_retries = number + health_timeout = number + health_type = string + pool_member_port = string + security_group = optional( + object({ + name = string + rules = list( + object({ + name = string + direction = string + source = string + tcp = optional( + object({ + port_max = number + port_min = number + }) + ) + udp = optional( + object({ + port_max = number + port_min = number + }) + ) + icmp = optional( + object({ + type = number + code = number + }) + ) + }) + ) + }) + ) + }) + ) + default = [] + + validation { + error_message = "Load balancer names must match the regex pattern ^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$." + condition = length(distinct( + flatten([ + # Check through rules + for load_balancer in var.load_balancers : + # Return false if direction is not valid + false if !can(regex("^([a-z]|[a-z][-a-z0-9]*[a-z0-9])$", load_balancer.name)) + ]) + )) == 0 + } + + validation { + error_message = "Load Balancer Pool algorithm can only be `round_robin`, `weighted_round_robin`, or `least_connections`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["round_robin", "weighted_round_robin", "least_connections"], load_balancer.algorithm) + ]) + ) == 0 + } + + validation { + error_message = "Load Balancer Pool Protocol can only be `http`, `https`, or `tcp`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["http", "https", "tcp"], load_balancer.protocol) + ]) + ) == 0 + } + + validation { + error_message = "Pool health delay must be greater than the timeout." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if load_balancer.health_delay < load_balancer.health_timeout + ]) + ) == 0 + } + + validation { + error_message = "Load Balancer Pool Health Check Type can only be `http`, `https`, or `tcp`." + condition = length( + flatten([ + for load_balancer in var.load_balancers : + true if !contains(["http", "https", "tcp"], load_balancer.health_type) + ]) + ) == 0 + } + + validation { + error_message = "Each load balancer must have a unique name." + condition = length(distinct(var.load_balancers[*].name)) == length(var.load_balancers[*].name) + } +} + +variable "existing_kms_instance_guid" { + description = "The GUID of the Hyper Protect Crypto Services or Key Protect instance in which the key specified in var.kms_key_crn and var.backup_encryption_key_crn is coming from. Required only if var.kms_encryption_enabled is set to true, var.skip_iam_authorization_policy is set to false, and you pass a value for var.kms_key_crn, var.backup_encryption_key_crn, or both." + type = string +} + +variable "skip_iam_authorization_policy" { + type = bool + description = "Set to true to skip the creation of an IAM authorization policy that permits all Storage Blocks to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing_kms_instance_guid variable. In addition, no policy is created if var.kms_encryption_enabled is set to false." + default = false +} + +############################################################################## diff --git a/profiles/fscloud/version.tf b/profiles/fscloud/version.tf new file mode 100644 index 00000000..12ad22ab --- /dev/null +++ b/profiles/fscloud/version.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 1.3.0" +} diff --git a/storage.tf b/storage.tf index 4486769e..49de08b3 100644 --- a/storage.tf +++ b/storage.tf @@ -19,7 +19,7 @@ locals { capacity = volume.capacity vsi_name = "${var.prefix}-${(subnet) * (var.vsi_per_subnet) + count + 1}" iops = volume.iops - encryption_key = volume.encryption_key + encryption_key = var.kms_encryption_enabled ? var.boot_volume_encryption_key : volume.encryption_key } ] ] diff --git a/tests/other_test.go b/tests/other_test.go new file mode 100644 index 00000000..dad41ace --- /dev/null +++ b/tests/other_test.go @@ -0,0 +1,17 @@ +// Tests in this file are NOT run in the PR pipeline. They are run in the continuous testing pipeline along with the ones in pr_test.go +package test + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRunBasicExample(t *testing.T) { + t.Parallel() + + options := setupOptions(t, "slz-vsi") + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") +} diff --git a/tests/pr_test.go b/tests/pr_test.go index e968c41f..5db78e19 100644 --- a/tests/pr_test.go +++ b/tests/pr_test.go @@ -11,6 +11,8 @@ import ( ) const defaultExampleTerraformDir = "examples/default" +const fsCloudExampleTerraformDir = "examples/fscloud" + const resourceGroup = "geretain-test-resources" const region = "us-south" @@ -19,6 +21,7 @@ const yamlLocation = "../common-dev-assets/common-go-assets/common-permanent-res var permanentResources map[string]interface{} +// TestMain will be run before any parallel tests, used to read data from yaml for use with tests func TestMain(m *testing.M) { // Read the YAML file contents var err error @@ -45,16 +48,6 @@ func setupOptions(t *testing.T, prefix string) *testhelper.TestOptions { return options } -func TestRunBasicExample(t *testing.T) { - t.Parallel() - - options := setupOptions(t, "slz-vsi") - - output, err := options.RunTestConsistency() - assert.Nil(t, err, "This should not have errored") - assert.NotNil(t, output, "Expected some output") -} - func TestRunUpgradeBasicExample(t *testing.T) { t.Parallel() @@ -66,3 +59,23 @@ func TestRunUpgradeBasicExample(t *testing.T) { assert.NotNil(t, output, "Expected some output") } } + +func TestRunFSCloudExample(t *testing.T) { + t.Parallel() + + options := testhelper.TestOptionsDefaultWithVars(&testhelper.TestOptions{ + Testing: t, + TerraformDir: fsCloudExampleTerraformDir, + Prefix: "slz-fs-vsi", + ResourceGroup: resourceGroup, + Region: region, + TerraformVars: map[string]interface{}{ + "existing_kms_instance_guid": permanentResources["hpcs_south"], + "boot_volume_encryption_key": permanentResources["hpcs_south_root_key_crn"], + }, + }) + + output, err := options.RunTestConsistency() + assert.Nil(t, err, "This should not have errored") + assert.NotNil(t, output, "Expected some output") +} diff --git a/variables.tf b/variables.tf index 0528aaee..04edb97a 100644 --- a/variables.tf +++ b/variables.tf @@ -94,9 +94,16 @@ variable "user_data" { variable "boot_volume_encryption_key" { description = "CRN of boot volume encryption key" + default = null type = string } +variable "existing_kms_instance_guid" { + description = "The GUID of the Hyper Protect Crypto Services instance in which the key specified in var.boot_volume_encryption_key is coming from." + type = string + default = null +} + variable "enable_floating_ip" { description = "Create a floating IP for each virtual server created" type = bool @@ -184,6 +191,18 @@ variable "security_group_ids" { } } +variable "kms_encryption_enabled" { + type = bool + description = "Set this to true to control the encryption keys used to encrypt the data that for the block storage volumes for VPC. If set to false, the data is encrypted by using randomly generated keys. For more info on encrypting block storage volumes, see https://cloud.ibm.com/docs/vpc?topic=vpc-creating-instances-byok" + default = false +} + +variable "skip_iam_authorization_policy" { + type = bool + description = "Set to true to skip the creation of an IAM authorization policy that permits all Storage Blocks to read the encryption key from the KMS instance. If set to false, pass in a value for the KMS instance in the existing_kms_instance_guid variable. In addition, no policy is created if var.kms_encryption_enabled is set to false." + default = false +} + variable "block_storage_volumes" { description = "List describing the block storage volumes that will be attached to each vsi" type = list(