diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0e61d593..b598fc38 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @yzha645 @nanux @bianchi2 +* @yzha645 @nanux @bianchi2 @kush982 diff --git a/config.tfvars b/config.tfvars index 10cac785..b16a269c 100644 --- a/config.tfvars +++ b/config.tfvars @@ -24,7 +24,11 @@ products = [""] # List of IP ranges that are allowed to access the running applications over the World Wide Web. # By default the deployed applications are publicly accessible (0.0.0.0/0). You can restrict this access by changing the -# default value to your desired CIDR blocks. e.g. ["10.20.0.0/16" , "99.68.64.0/10"] +# default value to your desired CIDR blocks. +# Examples: +# IPv4: ["10.20.0.0/16", "99.68.64.0/10"] +# IPv6: ["2001:db8::/32", "2001:db8:1234::/48"] +# Mixed: ["10.20.0.0/16", "2001:db8::/32"] whitelist_cidr = ["0.0.0.0/0"] # By default, Ingress controller listens on 443 and 80. You can enable only http port 80 by @@ -126,8 +130,8 @@ max_cluster_capacity = 5 # If undefined, current AWS region will be used (the one set in `region` in this file). Defaults to undefined. # osquery_fleet_enrollment_secret_region_aws = "" -# The value of OSQUERY_ENV that will be used to send logs to Splunk. It should not be something like “production” -# or “prod-west2” but should instead relate to the product, platform, or team. Defaults to osquery_dc_e2e_tests +# The value of OSQUERY_ENV that will be used to send logs to Splunk. It should not be something like "production" +# or "prod-west2" but should instead relate to the product, platform, or team. Defaults to osquery_dc_e2e_tests # osquery_env = "osquery_dc_e2e_tests" # Osquery version. Defaults to 5.7.0. Osquery is installed as yum package, make sure you test the version before an update diff --git a/modules/AWS/eks/locals.tf b/modules/AWS/eks/locals.tf index 3e580b60..c22638e5 100644 --- a/modules/AWS/eks/locals.tf +++ b/modules/AWS/eks/locals.tf @@ -1,4 +1,3 @@ - locals { oicd_provider = replace(module.eks.cluster_oidc_issuer_url, "https://", "") @@ -9,7 +8,10 @@ locals { ami_type = "AL2_x86_64" + # IPv4 service CIDR (used for backward compatibility) cluster_service_ipv4_cidr = "172.20.0.0/16" + + # IPv6 service CIDR will be automatically assigned by AWS osquery_secret_region = var.osquery_secret_region != "" ? var.osquery_secret_region : var.region diff --git a/modules/AWS/eks/main.tf b/modules/AWS/eks/main.tf index d2ea57e3..a0aa2f4e 100644 --- a/modules/AWS/eks/main.tf +++ b/modules/AWS/eks/main.tf @@ -37,6 +37,14 @@ module "eks" { kube-proxy = {} vpc-cni = { resolve_conflicts_on_create = "OVERWRITE" + configuration_values = jsonencode({ + env = { + # Enable IPv6 for pods + ENABLE_IPV6 = "true" + # Enable prefix delegation + ENABLE_PREFIX_DELEGATION = "true" + } + }) } aws-ebs-csi-driver = { resolve_conflicts_on_create = "OVERWRITE" @@ -66,11 +74,14 @@ module "eks" { create_kms_key = false cluster_encryption_config = {} - # Networking vpc_id = var.vpc_id subnet_ids = var.subnets cluster_service_ipv4_cidr = local.cluster_service_ipv4_cidr + + # Enable IPv6 for cluster + cluster_ip_family = "ipv6" + create_cni_ipv6_iam_policy = true # Managed node group defaults eks_managed_node_group_defaults = { diff --git a/modules/AWS/ingress/main.tf b/modules/AWS/ingress/main.tf index 0fdf40db..c2afe3da 100644 --- a/modules/AWS/ingress/main.tf +++ b/modules/AWS/ingress/main.tf @@ -62,13 +62,15 @@ resource "helm_release" "ingress" { controller = { config = { # If true, NGINX passes the incoming "X-Forwarded-*" headers to upstreams. - # https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#use-forwarded-headers - # https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html - "use-forwarded-headers" : "true" + "use-forwarded-headers" : "true", + # Enable IPv6 in nginx + "enable-ipv6" : "true", + # Configure IPv6 resolver + "enable-real-ip" : "true", + "proxy-real-ip-cidr" : "::/0" } service = { # The value "Local" preserves the client source IP. - # https://kubernetes.io/docs/tutorials/services/source-ip/#source-ip-for-services-with-typeloadbalancer externalTrafficPolicy = "Local" enableHttps = local.enable_https_ingress @@ -80,19 +82,20 @@ resource "helm_release" "ingress" { } annotations = { # Whether the LB will be internet-facing or internal. - # https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/guide/service/annotations/#lb-internal "service.beta.kubernetes.io/aws-load-balancer-internal" : "false" - # Specifies the IP address type, in this case "dualstack" will allow clients - # can access the load balancer using either IPv4 or IPv6. - # https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/service/annotations/#ip-address-type + # Enable dualstack mode for the load balancer "service.beta.kubernetes.io/aws-load-balancer-ip-address-type" : "dualstack" - # The protocol to use for backend traffic between the load balancer and the k8s pods. - # https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.3/guide/service/annotations/#backend-protocol + # Configure IPv6 health checks + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol" : "HTTP" + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-port" : "80" + "service.beta.kubernetes.io/aws-load-balancer-healthcheck-path" : "/healthz" + + # The protocol to use for backend traffic "service.beta.kubernetes.io/aws-load-balancer-backend-protocol" : "http" - # LoadBalancer is created by AWS not Terraform, so we need to add resource tags to it + # LoadBalancer tags "service.beta.kubernetes.io/aws-load-balancer-additional-resource-tags" : local.resource_tags } } diff --git a/modules/AWS/ingress/variables.tf b/modules/AWS/ingress/variables.tf index 7ab95f5b..edc99778 100644 --- a/modules/AWS/ingress/variables.tf +++ b/modules/AWS/ingress/variables.tf @@ -16,12 +16,14 @@ variable "enable_ssh_tcp" { } variable "load_balancer_access_ranges" { - description = "List of allowed CIDRs that can access the load balancer." + description = "List of allowed CIDRs (IPv4 and IPv6) that can access the load balancer." type = list(string) validation { condition = alltrue([ - for cidr in var.load_balancer_access_ranges : can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", cidr))]) - error_message = "Invalid CIDR. Valid format is a list of '/[0-32]' e.g: [\"10.0.0.0/18\"]." + for cidr in var.load_balancer_access_ranges : can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", cidr)) || + can(regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", cidr)) + ]) + error_message = "Invalid CIDR. Valid format is a list of IPv4 CIDR '/[0-32]' (e.g: [\"10.0.0.0/18\"]) or IPv6 CIDR '/[0-128]' (e.g: [\"2001:db8::/32\"])." } } diff --git a/modules/AWS/rds/main.tf b/modules/AWS/rds/main.tf index a4c00a91..a1222857 100644 --- a/modules/AWS/rds/main.tf +++ b/modules/AWS/rds/main.tf @@ -15,9 +15,20 @@ module "security_group" { from_port = 5432 to_port = 5432 protocol = "tcp" - description = "PostgreSQL access from within EKS cluster" + description = "PostgreSQL access from within EKS cluster (IPv4)" cidr_blocks = var.vpc.vpc_cidr_block - }, + } + ] + + # IPv6 ingress + ingress_with_ipv6_cidr_blocks = [ + { + from_port = 5432 + to_port = 5432 + protocol = "tcp" + description = "PostgreSQL access from within EKS cluster (IPv6)" + ipv6_cidr_blocks = var.vpc.vpc_ipv6_cidr_block + } ] } diff --git a/modules/AWS/vpc/locals.tf b/modules/AWS/vpc/locals.tf index 5e38737e..1fb7f293 100644 --- a/modules/AWS/vpc/locals.tf +++ b/modules/AWS/vpc/locals.tf @@ -1,4 +1,9 @@ locals { - subnets = [for cidr_block in cidrsubnets(var.vpc_cidr, 2, 2) : cidrsubnets(cidr_block, 2, 2)] + # For IPv4, we split the VPC CIDR into 2 parts with 2 bits each, then split each part into 2 more with 2 bits each + # For IPv6, we use the AWS VPC module's built-in IPv6 subnet allocation + is_ipv6 = can(regex("^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", var.vpc_cidr)) + + # For IPv4, calculate subnet ranges. For IPv6, we'll let the VPC module handle it + subnets = local.is_ipv6 ? [[], []] : [for cidr_block in cidrsubnets(var.vpc_cidr, 2, 2) : cidrsubnets(cidr_block, 2, 2)] } diff --git a/modules/AWS/vpc/main.tf b/modules/AWS/vpc/main.tf index 17a3df65..7fa002ef 100644 --- a/modules/AWS/vpc/main.tf +++ b/modules/AWS/vpc/main.tf @@ -17,6 +17,28 @@ module "vpc" { single_nat_gateway = true enable_dns_hostnames = true + # Enable IPv6 support + enable_ipv6 = true + assign_ipv6_address_on_creation = true + + # Enable IPv6 DNS support + enable_dns_support = true + + # Create IPv6-enabled subnets with Amazon-provided IPv6 CIDR blocks + create_egress_only_igw = true + enable_ipv6_public_subnet = true + enable_ipv6_private_subnet = true + public_subnet_ipv6_prefixes = [0, 1] + private_subnet_ipv6_prefixes = [2, 3] + public_subnet_suffix = "public-subnet" private_subnet_suffix = "private-subnet" + + # Tags for subnets to support EKS + public_subnet_tags = { + "kubernetes.io/role/elb" = "1" + } + private_subnet_tags = { + "kubernetes.io/role/internal-elb" = "1" + } } diff --git a/modules/AWS/vpc/variables.tf b/modules/AWS/vpc/variables.tf index ab9323eb..d19f7ba3 100644 --- a/modules/AWS/vpc/variables.tf +++ b/modules/AWS/vpc/variables.tf @@ -8,11 +8,14 @@ variable "vpc_name" { } variable "vpc_cidr" { - description = "Cidr block for vpc" + description = "CIDR block for VPC (IPv4 or IPv6)" type = string default = "10.0.0.0/18" validation { - condition = can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([1-9]|1[0-9]|2[0-4])$", var.vpc_cidr)) - error_message = "Invalid CIDR. Valid format is '/[1-24]' e.g: 10.0.0.0/18." + condition = ( + can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([1-9]|1[0-9]|2[0-4])$", var.vpc_cidr)) || + can(regex("^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", var.vpc_cidr)) + ) + error_message = "Invalid VPC CIDR block. Must be a valid IPv4 CIDR (e.g., 10.0.0.0/16) or IPv6 CIDR (e.g., 2001:db8::/32) with appropriate prefix length." } } \ No newline at end of file diff --git a/modules/common/security_group.tf b/modules/common/security_group.tf index d9579d13..f5c7e529 100644 --- a/modules/common/security_group.tf +++ b/modules/common/security_group.tf @@ -1,5 +1,18 @@ resource "aws_security_group" "vpc" { name_prefix = format("%s_sg", local.vpc_name) - description = "VPC security group." + description = "VPC security group with IPv4 and IPv6 support." vpc_id = module.vpc.vpc_id + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + description = "Allow all outbound traffic (IPv4 and IPv6)" + } + + tags = { + Name = format("%s_sg", local.vpc_name) + } } \ No newline at end of file diff --git a/variables.tf b/variables.tf index 95006e8c..b6ddeaa6 100644 --- a/variables.tf +++ b/variables.tf @@ -136,13 +136,15 @@ variable "eks_additional_roles" { } variable "whitelist_cidr" { - description = "List of CIDRs allowed accessing the application(s)." + description = "List of CIDRs (IPv4 and IPv6) allowed accessing the application(s)." default = ["0.0.0.0/0"] type = list(string) validation { condition = alltrue([ - for o in var.whitelist_cidr : can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", o))]) - error_message = "Invalid whitelist CIDR. Valid format is a list of '/[0-32]' e.g: [\"10.0.0.0/18\"]." + for o in var.whitelist_cidr : can(regex("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|1[0-9]|2[0-9]|3[0-2])$", o)) || + can(regex("^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$", o)) + ]) + error_message = "Invalid whitelist CIDR. Valid format is a list of IPv4 CIDR '/[0-32]' (e.g: [\"10.0.0.0/18\"]) or IPv6 CIDR '/[0-128]' (e.g: [\"2001:db8::/32\"])." } }