From 56748a48da44104da0304dd252f0ec5feb8c61e3 Mon Sep 17 00:00:00 2001 From: Guilherme Branco Date: Fri, 13 Dec 2024 11:33:00 -0300 Subject: [PATCH] OCM-13095 | feat: include zero egress vpc support --- README.md | 2 +- .../rosa-hcp-private-zero-egress/README.md | 155 ++++++++++++++++++ examples/rosa-hcp-private-zero-egress/main.tf | 87 ++++++++++ .../rosa-hcp-private-zero-egress/outputs.tf | 59 +++++++ .../rosa-hcp-private-zero-egress/variables.tf | 12 ++ .../rosa-hcp-private-zero-egress/versions.tf | 18 ++ examples/rosa-hcp-private/README.md | 1 - .../rosa-hcp-public-unmanaged-oidc/README.md | 1 - examples/rosa-hcp-public/README.md | 1 - main.tf | 17 +- modules/account-iam-resources/README.md | 4 +- modules/account-iam-resources/main.tf | 66 ++++---- modules/account-iam-resources/variables.tf | 8 +- modules/idp/README.md | 1 - modules/machine-pool/README.md | 1 - modules/oidc-config-and-provider/README.md | 1 - modules/operator-roles/README.md | 1 - modules/rosa-cluster-hcp/README.md | 2 +- modules/rosa-cluster-hcp/main.tf | 15 +- modules/rosa-cluster-hcp/variables.tf | 5 + modules/vpc/README.md | 5 +- modules/vpc/main.tf | 31 +++- modules/vpc/variables.tf | 6 + modules/vpc/zero-egress/README.md | 18 ++ modules/vpc/zero-egress/main.tf | 58 +++++++ modules/vpc/zero-egress/variables.tf | 15 ++ modules/vpc/zero-egress/versions.tf | 10 ++ variables.tf | 10 +- 28 files changed, 539 insertions(+), 71 deletions(-) create mode 100644 examples/rosa-hcp-private-zero-egress/README.md create mode 100644 examples/rosa-hcp-private-zero-egress/main.tf create mode 100644 examples/rosa-hcp-private-zero-egress/outputs.tf create mode 100644 examples/rosa-hcp-private-zero-egress/variables.tf create mode 100644 examples/rosa-hcp-private-zero-egress/versions.tf create mode 100644 modules/vpc/zero-egress/README.md create mode 100644 modules/vpc/zero-egress/main.tf create mode 100644 modules/vpc/zero-egress/variables.tf create mode 100644 modules/vpc/zero-egress/versions.tf diff --git a/README.md b/README.md index bdd00aa..401977e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ This module serves as a comprehensive solution for deploying, configuring and ma ``` module "hcp" { source = "terraform-redhat/rosa-hcp/rhcs" - version = "1.6.2" cluster_name = "my-cluster" openshift_version = "4.14.24" @@ -125,6 +124,7 @@ We recommend you install the following CLI tools: | [https\_proxy](#input\_https\_proxy) | A proxy URL to use for creating HTTPS connections outside the cluster. | `string` | `null` | no | | [identity\_providers](#input\_identity\_providers) | Provides a generic approach to add multiple identity providers after the creation of the cluster. This variable allows users to specify configurations for multiple identity providers in a flexible and customizable manner, facilitating the management of resources post-cluster deployment. For additional details regarding the variables utilized, refer to the [idp sub-module](./modules/idp). For non-primitive variables (such as maps, lists, and objects), supply the JSON-encoded string. | `map(any)` | `{}` | no | | [ignore\_machine\_pools\_deletion\_error](#input\_ignore\_machine\_pools\_deletion\_error) | Ignore machine pool deletion error. Assists when cluster resource is managed within the same file for the destroy use case | `bool` | `false` | no | +| [is\_zero\_ingress](#input\_is\_zero\_ingress) | Indicates use of zero ingress resources | `bool` | `false` | no | | [kms\_key\_arn](#input\_kms\_key\_arn) | The key ARN is the Amazon Resource Name (ARN) of a CMK. It is a unique, fully qualified identifier for the CMK. A key ARN includes the AWS account, Region, and the key ID. | `string` | `null` | no | | [kubelet\_configs](#input\_kubelet\_configs) | Provides a generic approach to add multiple kubelet configs after the creation of the cluster. This variable allows users to specify configurations for multiple kubelet configs in a flexible and customizable manner, facilitating the management of resources post-cluster deployment. For additional details regarding the variables utilized, refer to the [idp sub-module](./modules/kubelet-configs). For non-primitive variables (such as maps, lists, and objects), supply the JSON-encoded string. | `map(any)` | `{}` | no | | [machine\_cidr](#input\_machine\_cidr) | Block of IP addresses used by OpenShift while installing the cluster, for example "10.0.0.0/16". | `string` | `null` | no | diff --git a/examples/rosa-hcp-private-zero-egress/README.md b/examples/rosa-hcp-private-zero-egress/README.md new file mode 100644 index 0000000..7462c6d --- /dev/null +++ b/examples/rosa-hcp-private-zero-egress/README.md @@ -0,0 +1,155 @@ +# Private Zero Egress ROSA HCP + +## Introduction + +This is a Terraform manifest example for creating a Red Hat OpenShift Service on AWS (ROSA) Hosted Control Plane (HCP) cluster. This example provides a structured configuration template that demonstrates how to deploy a ROSA cluster within your AWS environment by using Terraform. + +This example includes: +- A Zero Egress ROSA cluster with private access. +- All AWS resources (IAM and networking) that are created as part of the ROSA cluster module execution. +- A bastion host EC2 instance that allows to reach the private cluster. + +## Example Usage + +``` +############################ +# Cluster +############################ +module "hcp" { + source = "terraform-redhat/rosa-hcp/rhcs" + + cluster_name = "my-cluster" + openshift_version = "4.14.24" + machine_cidr = module.vpc.cidr_block + aws_subnet_ids = module.vpc.private_subnets + aws_availability_zones = module.vpc.availability_zones + replicas = 2 + private = true + create_admin_user = true + admin_credentials_username = "admin" + admin_credentials_password = random_password.password.result + + // STS configuration + create_account_roles = true + account_role_prefix = "my-cluster-account" + create_oidc = true + create_operator_roles = true + operator_role_prefix = "my-cluster-operator" + is_zero_ingress = true +} + +resource "random_password" "password" { + length = 14 + special = true + min_lower = 1 + min_numeric = 1 + min_special = 1 + min_upper = 1 +} + +############################ +# VPC +############################ +module "vpc" { + source = "terraform-redhat/rosa-hcp/rhcs//modules/vpc" + + name_prefix = "my-vpc" + availability_zones_count = 1 + is_zero_ingress = true +} + +############################ +# Bastion instance for connection to the cluster +############################ +data "aws_ami" "rhel9" { + most_recent = true + + filter { + name = "platform-details" + values = ["Red Hat Enterprise Linux"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "manifest-location" + values = ["amazon/RHEL-9.*_HVM-*-x86_64-*-Hourly2-GP2"] + } + + owners = ["309956199498"] # Amazon's "Official Red Hat" account +} +module "bastion_host" { + source = "../../modules/bastion-host" + prefix = "my-host" + vpc_id = module.vpc.vpc_id + subnet_ids = [module.vpc.public_subnets[0]] + ami_id = aws_ami.rhel9.id + user_data_file = file("bastion-host-user-data.yaml") +} +``` + + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.35.0 | +| [random](#requirement\_random) | >= 2.0 | +| [rhcs](#requirement\_rhcs) | >= 1.6.2 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.35.0 | +| [random](#provider\_random) | >= 2.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [bastion\_host](#module\_bastion\_host) | ../../modules/bastion-host | n/a | +| [hcp](#module\_hcp) | ../../ | n/a | +| [vpc](#module\_vpc) | ../../modules/vpc | n/a | + +## Resources + +| Name | Type | +|------|------| +| [random_password.password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_ami.rhel9](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cluster\_name](#input\_cluster\_name) | n/a | `string` | n/a | yes | +| [openshift\_version](#input\_openshift\_version) | n/a | `string` | `"4.16.3"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [account\_role\_prefix](#output\_account\_role\_prefix) | The prefix used for all generated AWS resources. | +| [account\_roles\_arn](#output\_account\_roles\_arn) | A map of Amazon Resource Names (ARNs) associated with the AWS IAM roles created. The key in the map represents the name of an AWS IAM role, while the corresponding value represents the associated Amazon Resource Name (ARN) of that role. | +| [bastion\_host\_public\_ip](#output\_bastion\_host\_public\_ip) | Bastion Host Public IP | +| [cluster\_api\_url](#output\_cluster\_api\_url) | The URL of the API server. | +| [cluster\_console\_url](#output\_cluster\_console\_url) | The URL of the console. | +| [cluster\_id](#output\_cluster\_id) | Unique identifier of the cluster. | +| [oidc\_config\_id](#output\_oidc\_config\_id) | The unique identifier associated with users authenticated through OpenID Connect (OIDC) generated by this OIDC config. | +| [oidc\_endpoint\_url](#output\_oidc\_endpoint\_url) | Registered OIDC configuration issuer URL, generated by this OIDC config. | +| [operator\_role\_prefix](#output\_operator\_role\_prefix) | Prefix used for generated AWS operator policies. | +| [operator\_roles\_arn](#output\_operator\_roles\_arn) | List of Amazon Resource Names (ARNs) for all operator roles created. | +| [password](#output\_password) | n/a | +| [path](#output\_path) | The arn path for the account/operator roles as well as their policies. | + \ No newline at end of file diff --git a/examples/rosa-hcp-private-zero-egress/main.tf b/examples/rosa-hcp-private-zero-egress/main.tf new file mode 100644 index 0000000..cd89308 --- /dev/null +++ b/examples/rosa-hcp-private-zero-egress/main.tf @@ -0,0 +1,87 @@ +locals { + account_role_prefix = "${var.cluster_name}-account" + operator_role_prefix = "${var.cluster_name}-operator" +} + +############################ +# Cluster +############################ +module "hcp" { + source = "../../" + + cluster_name = var.cluster_name + openshift_version = var.openshift_version + machine_cidr = module.vpc.cidr_block + aws_subnet_ids = module.vpc.private_subnets + replicas = 2 + private = true + create_admin_user = true + admin_credentials_username = "admin" + admin_credentials_password = random_password.password.result + ec2_metadata_http_tokens = "required" + + // STS configuration + create_account_roles = true + account_role_prefix = local.account_role_prefix + create_oidc = true + create_operator_roles = true + operator_role_prefix = local.operator_role_prefix + is_zero_ingress = true +} + +resource "random_password" "password" { + length = 14 + special = true + min_lower = 1 + min_numeric = 1 + min_special = 1 + min_upper = 1 +} + +############################ +# VPC +############################ +module "vpc" { + source = "../../modules/vpc" + + name_prefix = var.cluster_name + availability_zones_count = 1 + is_zero_egress = true +} + +############################ +# Bastion instance for connection to the cluster +############################ +data "aws_ami" "rhel9" { + most_recent = true + + filter { + name = "platform-details" + values = ["Red Hat Enterprise Linux"] + } + + filter { + name = "architecture" + values = ["x86_64"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "manifest-location" + values = ["amazon/RHEL-9.*_HVM-*-x86_64-*-Hourly2-GP2"] + } + + owners = ["309956199498"] # Amazon's "Official Red Hat" account +} +module "bastion_host" { + source = "../../modules/bastion-host" + prefix = var.cluster_name + vpc_id = module.vpc.vpc_id + subnet_ids = [module.vpc.public_subnets[0]] + ami_id = data.aws_ami.rhel9.id + user_data_file = file("../../assets/bastion-host-user-data.yaml") +} diff --git a/examples/rosa-hcp-private-zero-egress/outputs.tf b/examples/rosa-hcp-private-zero-egress/outputs.tf new file mode 100644 index 0000000..056e1ea --- /dev/null +++ b/examples/rosa-hcp-private-zero-egress/outputs.tf @@ -0,0 +1,59 @@ +output "bastion_host_public_ip" { + value = module.bastion_host.bastion_host_public_ip + description = "Bastion Host Public IP" +} + +output "cluster_id" { + value = module.hcp.cluster_id + description = "Unique identifier of the cluster." +} + +output "cluster_api_url" { + value = module.hcp.cluster_api_url + description = "The URL of the API server." +} + +output "cluster_console_url" { + value = module.hcp.cluster_console_url + description = "The URL of the console." +} + +output "account_role_prefix" { + value = module.hcp.account_role_prefix + description = "The prefix used for all generated AWS resources." +} + +output "account_roles_arn" { + value = module.hcp.account_roles_arn + description = "A map of Amazon Resource Names (ARNs) associated with the AWS IAM roles created. The key in the map represents the name of an AWS IAM role, while the corresponding value represents the associated Amazon Resource Name (ARN) of that role." +} + +output "path" { + value = module.hcp.path + description = "The arn path for the account/operator roles as well as their policies." +} + +output "oidc_config_id" { + value = module.hcp.oidc_config_id + description = "The unique identifier associated with users authenticated through OpenID Connect (OIDC) generated by this OIDC config." +} + +output "oidc_endpoint_url" { + value = module.hcp.oidc_endpoint_url + description = "Registered OIDC configuration issuer URL, generated by this OIDC config." +} + +output "operator_role_prefix" { + value = module.hcp.operator_role_prefix + description = "Prefix used for generated AWS operator policies." +} + +output "operator_roles_arn" { + value = module.hcp.operator_roles_arn + description = "List of Amazon Resource Names (ARNs) for all operator roles created." +} + +output "password" { + value = resource.random_password.password + sensitive = true +} diff --git a/examples/rosa-hcp-private-zero-egress/variables.tf b/examples/rosa-hcp-private-zero-egress/variables.tf new file mode 100644 index 0000000..dff934c --- /dev/null +++ b/examples/rosa-hcp-private-zero-egress/variables.tf @@ -0,0 +1,12 @@ +variable "openshift_version" { + type = string + default = "4.16.3" + validation { + condition = can(regex("^[0-9]*[0-9]+.[0-9]*[0-9]+.[0-9]*[0-9]+$", var.openshift_version)) + error_message = "openshift_version must be with structure .. (for example 4.13.6)." + } +} + +variable "cluster_name" { + type = string +} \ No newline at end of file diff --git a/examples/rosa-hcp-private-zero-egress/versions.tf b/examples/rosa-hcp-private-zero-egress/versions.tf new file mode 100644 index 0000000..3888b74 --- /dev/null +++ b/examples/rosa-hcp-private-zero-egress/versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.35.0" + } + rhcs = { + version = ">= 1.6.2" + source = "terraform-redhat/rhcs" + } + random = { + source = "hashicorp/random" + version = ">= 2.0" + } + } +} diff --git a/examples/rosa-hcp-private/README.md b/examples/rosa-hcp-private/README.md index 6e345da..13232e7 100644 --- a/examples/rosa-hcp-private/README.md +++ b/examples/rosa-hcp-private/README.md @@ -17,7 +17,6 @@ This example includes: ############################ module "hcp" { source = "terraform-redhat/rosa-hcp/rhcs" - version = "1.6.2" cluster_name = "my-cluster" openshift_version = "4.14.24" diff --git a/examples/rosa-hcp-public-unmanaged-oidc/README.md b/examples/rosa-hcp-public-unmanaged-oidc/README.md index 0ec9ece..ff6674d 100644 --- a/examples/rosa-hcp-public-unmanaged-oidc/README.md +++ b/examples/rosa-hcp-public-unmanaged-oidc/README.md @@ -16,7 +16,6 @@ This example includes: ############################ module "hcp" { source = "terraform-redhat/rosa-hcp/rhcs" - version = "1.6.2" cluster_name = "my-cluster" openshift_version = "4.14.24" diff --git a/examples/rosa-hcp-public/README.md b/examples/rosa-hcp-public/README.md index 988d901..053f8d6 100644 --- a/examples/rosa-hcp-public/README.md +++ b/examples/rosa-hcp-public/README.md @@ -16,7 +16,6 @@ This example includes: ############################ module "hcp" { source = "terraform-redhat/rosa-hcp/rhcs" - version = "1.6.2" cluster_name = "my-cluster" openshift_version = "4.14.24" diff --git a/main.tf b/main.tf index 2482f29..522c3f1 100644 --- a/main.tf +++ b/main.tf @@ -17,10 +17,11 @@ module "account_iam_resources" { source = "./modules/account-iam-resources" count = var.create_account_roles ? 1 : 0 - account_role_prefix = local.account_role_prefix - path = local.path - permissions_boundary = var.permissions_boundary - tags = var.tags + account_role_prefix = local.account_role_prefix + path = local.path + permissions_boundary = var.permissions_boundary + tags = var.tags + attach_worker_role_zero_egress_policy = var.is_zero_ingress } ############################ @@ -83,6 +84,7 @@ module "rosa_cluster_hcp" { kms_key_arn = var.kms_key_arn aws_billing_account_id = var.aws_billing_account_id ec2_metadata_http_tokens = var.ec2_metadata_http_tokens + is_zero_ingress = var.is_zero_ingress ######## # Cluster Admin User @@ -103,10 +105,9 @@ module "rosa_cluster_hcp" { ####################### # Default Machine Pool ####################### - - replicas = var.replicas - compute_machine_type = var.compute_machine_type - aws_availability_zones = var.aws_availability_zones + replicas = var.replicas + compute_machine_type = var.compute_machine_type + aws_availability_zones = var.aws_availability_zones aws_additional_compute_security_group_ids = var.aws_additional_compute_security_group_ids ######## diff --git a/modules/account-iam-resources/README.md b/modules/account-iam-resources/README.md index c1d749c..9d9433c 100644 --- a/modules/account-iam-resources/README.md +++ b/modules/account-iam-resources/README.md @@ -13,7 +13,6 @@ For more information, see [About IAM resources for ROSA clusters that use STS](h ``` module "account_iam_resources" { source = "terraform-redhat/rosa-hcp/rhcs//modules/account-iam-resources" - version = "1.6.2" account_role_prefix = "my-cluster-account" } @@ -61,9 +60,10 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [account\_role\_prefix](#input\_account\_role\_prefix) | Prefix to be used when creating the account roles | `string` | `"tf-acc"` | no | +| [attach\_worker\_role\_zero\_egress\_policy](#input\_attach\_worker\_role\_zero\_egress\_policy) | Signals to attach zero egress policy to worker role | `bool` | `false` | no | | [path](#input\_path) | (Optional) The arn path for the account/operator roles as well as their policies. Must begin and end with '/'. | `string` | `"/"` | no | | [permissions\_boundary](#input\_permissions\_boundary) | The ARN of the policy that is used to set the permissions boundary for the IAM roles in STS clusters. | `string` | `""` | no | -| [tags](#input\_tags) | List of AWS resource tags to apply. | `map(string)` | `null` | no | +| [tags](#input\_tags) | Mapping of AWS resource tags to apply. | `map(string)` | `null` | no | ## Outputs diff --git a/modules/account-iam-resources/main.tf b/modules/account-iam-resources/main.tf index 17716a7..a7e3d06 100644 --- a/modules/account-iam-resources/main.tf +++ b/modules/account-iam-resources/main.tf @@ -1,29 +1,32 @@ +data "rhcs_hcp_policies" "all_policies" {} +data "aws_partition" "current" {} +data "rhcs_info" "current" {} locals { path = coalesce(var.path, "/") - account_roles_properties = [ - { + account_roles_properties = { + "installer" = { role_name = "HCP-ROSA-Installer" role_type = "installer" - policy_details = "arn:aws:iam::aws:policy/service-role/ROSAInstallerPolicy" + policy_details = ["arn:aws:iam::aws:policy/service-role/ROSAInstallerPolicy"] principal_type = "AWS" principal_identifier = "arn:${data.aws_partition.current.partition}:iam::${data.rhcs_info.current.ocm_aws_account_id}:role/RH-Managed-OpenShift-Installer" }, - { - role_name = "HCP-ROSA-Support" - role_type = "support" - policy_details = "arn:aws:iam::aws:policy/service-role/ROSASRESupportPolicy" - principal_type = "AWS" + "support" = { + role_name = "HCP-ROSA-Support" + role_type = "support" + policy_details = ["arn:aws:iam::aws:policy/service-role/ROSASRESupportPolicy"] + principal_type = "AWS" // This is a SRE RH Support role which is used to assume this support role principal_identifier = data.rhcs_hcp_policies.all_policies.account_role_policies["sts_support_rh_sre_role"] }, - { + "instance_worker" = { role_name = "HCP-ROSA-Worker" role_type = "instance_worker" - policy_details = "arn:aws:iam::aws:policy/service-role/ROSAWorkerInstancePolicy" + policy_details = concat(["arn:aws:iam::aws:policy/service-role/ROSAWorkerInstancePolicy"], var.attach_worker_role_zero_egress_policy ? ["arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"] : []) principal_type = "Service" principal_identifier = "ec2.amazonaws.com" }, - ] + } account_roles_count = length(local.account_roles_properties) account_role_prefix_valid = (var.account_role_prefix != null && var.account_role_prefix != "") ? ( var.account_role_prefix @@ -33,38 +36,49 @@ locals { } data "aws_iam_policy_document" "custom_trust_policy" { - count = local.account_roles_count + for_each = local.account_roles_properties statement { effect = "Allow" actions = ["sts:AssumeRole"] principals { - type = local.account_roles_properties[count.index].principal_type - identifiers = [local.account_roles_properties[count.index].principal_identifier] + type = each.value.principal_type + identifiers = [each.value.principal_identifier] } } } resource "aws_iam_role" "account_role" { - count = local.account_roles_count - name = substr("${local.account_role_prefix_valid}-${local.account_roles_properties[count.index].role_name}-Role", 0, 64) + for_each = local.account_roles_properties + name = substr("${local.account_role_prefix_valid}-${each.value.role_name}-Role", 0, 64) permissions_boundary = var.permissions_boundary path = local.path - assume_role_policy = data.aws_iam_policy_document.custom_trust_policy[count.index].json + assume_role_policy = data.aws_iam_policy_document.custom_trust_policy[each.key].json tags = merge(var.tags, { red-hat-managed = true rosa_hcp_policies = true rosa_managed_policies = true rosa_role_prefix = local.account_role_prefix_valid - rosa_role_type = local.account_roles_properties[count.index].role_type + rosa_role_type = each.value.role_type }) } +locals { + role_policies = flatten([ + for account_role_property in local.account_roles_properties : [ + for policy in account_role_property.policy_details : { + name = aws_iam_role.account_role[account_role_property.role_type].name + arn = policy + } + ] + ]) +} + resource "aws_iam_role_policy_attachment" "account_role_policy_attachment" { - count = local.account_roles_count - role = aws_iam_role.account_role[count.index].name - policy_arn = local.account_roles_properties[count.index].policy_details + for_each = { for role_policy in local.role_policies : "${role_policy.name}-${role_policy.arn}" => role_policy } + role = each.value.name + policy_arn = each.value.arn } resource "random_string" "default_random" { @@ -75,19 +89,13 @@ resource "random_string" "default_random" { upper = false } -data "rhcs_hcp_policies" "all_policies" {} - -data "aws_partition" "current" {} - -data "rhcs_info" "current" {} - resource "time_sleep" "account_iam_resources_wait" { destroy_duration = "10s" create_duration = "10s" triggers = { - account_iam_role_name = jsonencode([ for value in aws_iam_role.account_role : value.name]) + account_iam_role_name = jsonencode([for value in aws_iam_role.account_role : value.name]) account_roles_arn = jsonencode({ for idx, value in aws_iam_role.account_role : local.account_roles_properties[idx].role_name => value.arn }) - account_policy_arns = jsonencode([ for value in aws_iam_role_policy_attachment.account_role_policy_attachment : value.policy_arn]) + account_policy_arns = jsonencode([for value in aws_iam_role_policy_attachment.account_role_policy_attachment : value.policy_arn]) account_role_prefix = local.account_role_prefix_valid path = local.path } diff --git a/modules/account-iam-resources/variables.tf b/modules/account-iam-resources/variables.tf index 9397f5c..7de1411 100644 --- a/modules/account-iam-resources/variables.tf +++ b/modules/account-iam-resources/variables.tf @@ -17,7 +17,13 @@ variable "permissions_boundary" { } variable "tags" { - description = "List of AWS resource tags to apply." + description = "Mapping of AWS resource tags to apply." type = map(string) default = null } + +variable "attach_worker_role_zero_egress_policy" { + description = "Signals to attach zero egress policy to worker role" + type = bool + default = false +} diff --git a/modules/idp/README.md b/modules/idp/README.md index 096c233..52ab994 100644 --- a/modules/idp/README.md +++ b/modules/idp/README.md @@ -9,7 +9,6 @@ This Terraform sub-module assists with the configuration of identity providers ( ``` module "htpasswd_idp" { source = "terraform-redhat/rosa-hcp/rhcs//modules/idp" - version = "1.6.2" cluster_id = "cluster-id-123" name = "htpasswd-idp" diff --git a/modules/machine-pool/README.md b/modules/machine-pool/README.md index e9206f9..ce98d12 100644 --- a/modules/machine-pool/README.md +++ b/modules/machine-pool/README.md @@ -9,7 +9,6 @@ This Terraform sub-module manages the machine pool for ROSA HCP clusters. It ena ``` module "mp" { source = "terraform-redhat/rosa-hcp/rhcs//modules/machine-pool" - version = "1.6.2" cluster_id = "cluster-id-123" name = "my-pool" diff --git a/modules/oidc-config-and-provider/README.md b/modules/oidc-config-and-provider/README.md index 4f528a9..2b9057e 100644 --- a/modules/oidc-config-and-provider/README.md +++ b/modules/oidc-config-and-provider/README.md @@ -11,7 +11,6 @@ For more information, see [OpenID Connect Overview](https://docs.openshift.com/r ``` module "oidc_config_and_provider" { source = "terraform-redhat/rosa-hcp/rhcs//modules/oidc-config-and-provider" - version = "1.6.2" managed = true } diff --git a/modules/operator-roles/README.md b/modules/operator-roles/README.md index 8794167..4973737 100644 --- a/modules/operator-roles/README.md +++ b/modules/operator-roles/README.md @@ -21,7 +21,6 @@ For more information, see the [About IAM resources for ROSA clusters that use ST ``` module "operator_roles" { source = "terraform-redhat/rosa-hcp/rhcs//modules/operator-roles" - version = "1.6.2" operator_role_prefix = "role-prefix" oidc_endpoint_url = "my-url" diff --git a/modules/rosa-cluster-hcp/README.md b/modules/rosa-cluster-hcp/README.md index b9665d2..a8bff44 100644 --- a/modules/rosa-cluster-hcp/README.md +++ b/modules/rosa-cluster-hcp/README.md @@ -12,7 +12,6 @@ This sub-module also includes the following resources: ``` module "rosa_cluster_hcp" { source = "terraform-redhat/rosa-hcp/rhcs//modules/rosa-cluster-hcp" - version = "1.6.2" cluster_name = "my-cluster" operator_role_prefix = "my-operators" @@ -97,6 +96,7 @@ No modules. | [http\_proxy](#input\_http\_proxy) | A proxy URL to use for creating HTTP connections outside the cluster. The URL scheme must be http. | `string` | `null` | no | | [https\_proxy](#input\_https\_proxy) | A proxy URL to use for creating HTTPS connections outside the cluster. | `string` | `null` | no | | [installer\_role\_arn](#input\_installer\_role\_arn) | The Amazon Resource Name (ARN) associated with the AWS IAM role used by the ROSA installer. | `string` | `null` | no | +| [is\_zero\_ingress](#input\_is\_zero\_ingress) | Indicates use of zero ingress resources | `bool` | `false` | no | | [kms\_key\_arn](#input\_kms\_key\_arn) | The key ARN is the Amazon Resource Name (ARN) of a CMK. It is a unique, fully qualified identifier for the CMK. A key ARN includes the AWS account, Region, and the key ID. | `string` | `null` | no | | [machine\_cidr](#input\_machine\_cidr) | Block of IP addresses used by OpenShift while installing the cluster, for example "10.0.0.0/16". | `string` | `null` | no | | [no\_proxy](#input\_no\_proxy) | A comma-separated list of destination domain names, domains, IP addresses or other network CIDRs to exclude proxying. | `string` | `null` | no | diff --git a/modules/rosa-cluster-hcp/main.tf b/modules/rosa-cluster-hcp/main.tf index 1416d4d..1e834ea 100644 --- a/modules/rosa-cluster-hcp/main.tf +++ b/modules/rosa-cluster-hcp/main.tf @@ -22,7 +22,7 @@ locals { operator_role_prefix = var.operator_role_prefix, oidc_config_id = var.oidc_config_id } - aws_account_arn = var.aws_account_arn == null ? data.aws_caller_identity.current[0].arn : var.aws_account_arn + aws_account_arn = var.aws_account_arn == null ? data.aws_caller_identity.current[0].arn : var.aws_account_arn create_admin_user = var.create_admin_user admin_credentials = var.admin_credentials_username == null && var.admin_credentials_password == null ? ( null @@ -36,14 +36,9 @@ resource "rhcs_cluster_rosa_hcp" "rosa_hcp_cluster" { version = var.openshift_version upgrade_acknowledgements_for = var.upgrade_acknowledgements_for private = var.private - properties = merge( - { - rosa_creator_arn = local.aws_account_arn - }, - var.properties - ) - cloud_region = var.aws_region == null ? data.aws_region.current[0].name : var.aws_region - aws_account_id = local.aws_account_id + properties = merge(merge(var.properties, { rosa_creator_arn = local.aws_account_arn }), var.is_zero_ingress ? { "zero_egress" : "true" } : {}) + cloud_region = var.aws_region == null ? data.aws_region.current[0].name : var.aws_region + aws_account_id = local.aws_account_id aws_billing_account_id = var.aws_billing_account_id == null || var.aws_billing_account_id == "" ? ( local.aws_account_id ) : (var.aws_billing_account_id) @@ -134,7 +129,7 @@ resource "rhcs_hcp_cluster_autoscaler" "cluster_autoscaler" { } resource "rhcs_hcp_default_ingress" "default_ingress" { - cluster = rhcs_cluster_rosa_hcp.rosa_hcp_cluster.id + cluster = rhcs_cluster_rosa_hcp.rosa_hcp_cluster.id listening_method = var.default_ingress_listening_method != "" ? ( var.default_ingress_listening_method) : ( var.private ? "internal" : "external" diff --git a/modules/rosa-cluster-hcp/variables.tf b/modules/rosa-cluster-hcp/variables.tf index fe31928..d72d6ad 100644 --- a/modules/rosa-cluster-hcp/variables.tf +++ b/modules/rosa-cluster-hcp/variables.tf @@ -229,6 +229,11 @@ variable "upgrade_acknowledgements_for" { description = "Indicates acknowledgement of agreements required to upgrade the cluster version between minor versions (e.g. a value of \"4.12\" indicates acknowledgement of any agreements required to upgrade to OpenShift 4.12.z from 4.11 or before)." } +variable "is_zero_ingress" { + type = bool + default = false + description = "Indicates use of zero ingress resources" +} ############################################################## # Default Machine Pool Variables diff --git a/modules/vpc/README.md b/modules/vpc/README.md index 3e41cd4..037789d 100644 --- a/modules/vpc/README.md +++ b/modules/vpc/README.md @@ -33,7 +33,9 @@ module "vpc" { ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [zero\_egress](#module\_zero\_egress) | ./zero-egress | n/a | ## Resources @@ -64,6 +66,7 @@ No modules. |------|-------------|------|---------|:--------:| | [availability\_zones](#input\_availability\_zones) | A list of availability zones names in the region. This value should not be updated, please create a new resource instead | `list(string)` | `null` | no | | [availability\_zones\_count](#input\_availability\_zones\_count) | The count of availability zones to utilize within the specified AWS Region, where pairs of public and private subnets will be generated. Valid only when availability\_zones variable is not provided. This value should not be updated, please create a new resource instead | `number` | `null` | no | +| [is\_zero\_egress](#input\_is\_zero\_egress) | Indicates intention for zero egress vpc. | `bool` | `false` | no | | [name\_prefix](#input\_name\_prefix) | User-defined prefix for all generated AWS resources of this VPC. This value should not be updated, please create a new resource instead | `string` | n/a | yes | | [tags](#input\_tags) | AWS tags to be applied to generated AWS resources of this VPC. | `map(string)` | `null` | no | | [vpc\_cidr](#input\_vpc\_cidr) | Cidr block of the desired VPC. This value should not be updated, please create a new resource instead | `string` | `"10.0.0.0/16"` | no | diff --git a/modules/vpc/main.tf b/modules/vpc/main.tf index fecd6b2..a8c11ff 100644 --- a/modules/vpc/main.tf +++ b/modules/vpc/main.tf @@ -1,5 +1,5 @@ locals { - tags = var.tags == null ? {} : var.tags + tags = var.tags == null ? {} : var.tags availability_zones = var.availability_zones != null ? var.availability_zones : slice(data.aws_availability_zones.available.names, 0, var.availability_zones_count) } @@ -18,9 +18,22 @@ resource "aws_vpc" "vpc" { } } +######################### +# ZERO EGRESS SUPPORT +######################### +module "zero_egress" { + count = var.is_zero_egress ? 1 : 0 + source = "./zero-egress" + vpc_id = aws_vpc.vpc.id + subnet_ids = [for subnet in aws_subnet.private_subnet[*] : subnet.id] + cidr_blocks = [for subnet in aws_subnet.private_subnet[*] : subnet.cidr_block] +} + resource "aws_vpc_endpoint" "s3" { - vpc_id = aws_vpc.vpc.id - service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + vpc_id = aws_vpc.vpc.id + service_name = "com.amazonaws.${data.aws_region.current.name}.s3" + vpc_endpoint_type = "Gateway" + route_table_ids = var.is_zero_egress ? [for rt in aws_route_table.private_route_table[*] : rt.id] : null } resource "aws_subnet" "public_subnet" { @@ -31,7 +44,7 @@ resource "aws_subnet" "public_subnet" { availability_zone = local.availability_zones[count.index] tags = merge( { - "Name" = join("-", [var.name_prefix, "subnet", "public${count.index + 1}", local.availability_zones[count.index]]) + "Name" = join("-", [var.name_prefix, "subnet", "public${count.index + 1}", local.availability_zones[count.index]]) "kubernetes.io/role/elb" = "" }, local.tags, @@ -49,7 +62,7 @@ resource "aws_subnet" "private_subnet" { availability_zone = local.availability_zones[count.index] tags = merge( { - "Name" = join("-", [var.name_prefix, "subnet", "private${count.index + 1}", local.availability_zones[count.index]]) + "Name" = join("-", [var.name_prefix, "subnet", "private${count.index + 1}", local.availability_zones[count.index]]) "kubernetes.io/role/internal-elb" = "" }, local.tags, @@ -165,7 +178,7 @@ resource "aws_route" "ipv6_egress_route" { # Send private traffic to NAT resource "aws_route" "private_nat" { - count = length(local.availability_zones) + count = (var.is_zero_egress) ? 0 : length(local.availability_zones) route_table_id = aws_route_table.private_route_table[count.index].id destination_cidr_block = "0.0.0.0/0" @@ -202,14 +215,14 @@ resource "aws_route_table_association" "private_route_table_association" { # This resource is used in order to add dependencies on all resources # Any resource uses this VPC ID, must wait to all resources creation completion resource "time_sleep" "vpc_resources_wait" { - create_duration = "20s" + create_duration = "20s" destroy_duration = "20s" triggers = { vpc_id = aws_vpc.vpc.id cidr_block = aws_vpc.vpc.cidr_block ipv4_egress_route_id = aws_route.ipv4_egress_route.id ipv6_egress_route_id = aws_route.ipv6_egress_route.id - private_nat_ids = jsonencode([for value in aws_route.private_nat : value.id]) + private_nat_ids = (var.is_zero_egress) ? jsonencode([]) : jsonencode([for value in aws_route.private_nat : value.id]) private_vpc_endpoint_route_table_association_ids = jsonencode([for value in aws_vpc_endpoint_route_table_association.private_vpc_endpoint_route_table_association : value.id]) public_route_table_association_ids = jsonencode([for value in aws_route_table_association.public_route_table_association : value.id]) private_route_table_association_ids = jsonencode([for value in aws_route_table_association.private_route_table_association : value.id]) @@ -226,4 +239,4 @@ data "aws_availability_zones" "available" { name = "opt-in-status" values = ["opt-in-not-required"] } -} \ No newline at end of file +} diff --git a/modules/vpc/variables.tf b/modules/vpc/variables.tf index 2111bb5..8b6ccf3 100644 --- a/modules/vpc/variables.tf +++ b/modules/vpc/variables.tf @@ -26,3 +26,9 @@ variable "tags" { default = null description = "AWS tags to be applied to generated AWS resources of this VPC." } + +variable "is_zero_egress" { + type = bool + default = false + description = "Indicates intention for zero egress vpc." +} \ No newline at end of file diff --git a/modules/vpc/zero-egress/README.md b/modules/vpc/zero-egress/README.md new file mode 100644 index 0000000..942eecd --- /dev/null +++ b/modules/vpc/zero-egress/README.md @@ -0,0 +1,18 @@ +# Zero-Egress + +## Introduction + +This repository contains Terraform configurations to set up resources to allow a Disconnected AWS Virtual Private Cloud (VPC) for use with Red Hat OpenShift Service on AWS (ROSA) Hosted Control Planes (HCP) cluster. This setup ensures that all cluster traffic remains within the AWS network, eliminating the need for internet access (egress) for the cluster. + + +## Example Usage + +``` +module "zero_egress" { + count = var.is_zero_egress ? 1 : 0 + source = "./zero-egress" + vpc_id = aws_vpc.vpc.id + subnet_ids = [for subnet in aws_subnet.private_subnet[*] : subnet.id] + cidr_blocks = [for subnet in aws_subnet.private_subnet[*] : subnet.cidr_block] +} +``` \ No newline at end of file diff --git a/modules/vpc/zero-egress/main.tf b/modules/vpc/zero-egress/main.tf new file mode 100644 index 0000000..7470063 --- /dev/null +++ b/modules/vpc/zero-egress/main.tf @@ -0,0 +1,58 @@ +data "aws_region" "current" {} + +resource "aws_security_group" "authorize_inbound_vpc_traffic" { + ingress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = var.cidr_blocks + } + vpc_id = var.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } +} + +resource "aws_vpc_endpoint" "sts" { + service_name = "com.amazonaws.${data.aws_region.current.name}.sts" + vpc_id = var.vpc_id + vpc_endpoint_type = "Interface" + + private_dns_enabled = true + subnet_ids = var.subnet_ids + security_group_ids = [aws_security_group.authorize_inbound_vpc_traffic.id] + lifecycle { + ignore_changes = [tags] + } +} + +resource "aws_vpc_endpoint" "ecr_api" { + service_name = "com.amazonaws.${data.aws_region.current.name}.ecr.api" + vpc_id = var.vpc_id + vpc_endpoint_type = "Interface" + + private_dns_enabled = true + subnet_ids = var.subnet_ids + security_group_ids = [aws_security_group.authorize_inbound_vpc_traffic.id] + lifecycle { + ignore_changes = [tags] + } +} + +resource "aws_vpc_endpoint" "ecr_dkr" { + service_name = "com.amazonaws.${data.aws_region.current.name}.ecr.dkr" + vpc_id = var.vpc_id + vpc_endpoint_type = "Interface" + + private_dns_enabled = true + subnet_ids = var.subnet_ids + security_group_ids = [aws_security_group.authorize_inbound_vpc_traffic.id] + lifecycle { + ignore_changes = [tags] + } +} \ No newline at end of file diff --git a/modules/vpc/zero-egress/variables.tf b/modules/vpc/zero-egress/variables.tf new file mode 100644 index 0000000..0ccf44f --- /dev/null +++ b/modules/vpc/zero-egress/variables.tf @@ -0,0 +1,15 @@ +variable "cidr_blocks" { + type = list(string) + default = null + description = "CIDR ranges to include as ingress allowed ranges" +} + +variable "vpc_id" { + type = string + description = "ID of the AWS VPC resource" +} + +variable "subnet_ids" { + type = list(string) + description = "ID of the subnets resource" +} \ No newline at end of file diff --git a/modules/vpc/zero-egress/versions.tf b/modules/vpc/zero-egress/versions.tf new file mode 100644 index 0000000..8a0ad64 --- /dev/null +++ b/modules/vpc/zero-egress/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.35.0" + } + } +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index a164692..df38c7c 100644 --- a/variables.tf +++ b/variables.tf @@ -322,7 +322,7 @@ variable "oidc_endpoint_url" { } variable "machine_pools" { - type = map(any) + type = map(any) default = {} description = "Provides a generic approach to add multiple machine pools after the creation of the cluster. This variable allows users to specify configurations for multiple machine pools in a flexible and customizable manner, facilitating the management of resources post-cluster deployment. For additional details regarding the variables utilized, refer to the [machine-pool sub-module](./modules/machine-pool). For non-primitive variables (such as maps, lists, and objects), supply the JSON-encoded string." } @@ -336,7 +336,7 @@ variable "identity_providers" { variable "kubelet_configs" { type = map(any) default = {} - description = "Provides a generic approach to add multiple kubelet configs after the creation of the cluster. This variable allows users to specify configurations for multiple kubelet configs in a flexible and customizable manner, facilitating the management of resources post-cluster deployment. For additional details regarding the variables utilized, refer to the [idp sub-module](./modules/kubelet-configs). For non-primitive variables (such as maps, lists, and objects), supply the JSON-encoded string." + description = "Provides a generic approach to add multiple kubelet configs after the creation of the cluster. This variable allows users to specify configurations for multiple kubelet configs in a flexible and customizable manner, facilitating the management of resources post-cluster deployment. For additional details regarding the variables utilized, refer to the [idp sub-module](./modules/kubelet-configs). For non-primitive variables (such as maps, lists, and objects), supply the JSON-encoded string." } variable "ignore_machine_pools_deletion_error" { @@ -344,3 +344,9 @@ variable "ignore_machine_pools_deletion_error" { default = false description = "Ignore machine pool deletion error. Assists when cluster resource is managed within the same file for the destroy use case" } + +variable "is_zero_ingress" { + type = bool + default = false + description = "Indicates use of zero ingress resources" +}