diff --git a/contexts/azure-example/blueprint.yaml b/contexts/azure-example/blueprint.yaml new file mode 100644 index 00000000..52e515f0 --- /dev/null +++ b/contexts/azure-example/blueprint.yaml @@ -0,0 +1,93 @@ +kind: Blueprint +apiVersion: blueprints.windsorcli.dev/v1alpha1 +metadata: + name: cloud + description: This blueprint outlines resources in the cloud context +repository: + url: "" + ref: + branch: main + secretName: flux-system +sources: +- name: core + url: github.com/windsorcli/core + ref: + branch: main +terraform: +- path: network/azure-vnet +- path: cluster/azure-aks +- path: gitops/flux + destroy: false +kustomize: +- name: telemetry-base + path: telemetry/base + source: core + components: + - prometheus + - prometheus/flux +- name: telemetry-resources + path: telemetry/resources + source: core + dependsOn: + - telemetry-base + components: + - prometheus + - prometheus/flux +- name: policy-base + path: policy/base + source: core + components: + - kyverno +- name: policy-resources + path: policy/resources + source: core + dependsOn: + - policy-base +- name: pki-base + path: pki/base + source: core + dependsOn: + - policy-resources + force: true + components: + - cert-manager + - trust-manager +- name: pki-resources + path: pki/resources + source: core + dependsOn: + - pki-base + force: true + components: + - private-issuer/ca + - public-issuer/selfsigned +- name: ingress-base + path: ingress/base + source: core + dependsOn: + - pki-resources + force: true + components: + - nginx + - nginx/flux-webhook + - nginx/web +- name: gitops + path: gitops/flux + source: core + dependsOn: + - ingress-base + force: true + components: + - webhook +- name: observability + path: observability + source: core + dependsOn: + - ingress-base + components: + - grafana + - grafana/ingress + - grafana/prometheus + - grafana/node + - grafana/kubernetes + - grafana/flux diff --git a/contexts/azure-example/terraform/backend/azurerm.tfvars b/contexts/azure-example/terraform/backend/azurerm.tfvars new file mode 100644 index 00000000..ef8b9ae2 --- /dev/null +++ b/contexts/azure-example/terraform/backend/azurerm.tfvars @@ -0,0 +1,29 @@ +// Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. + +// Azure region where resources will be created +// location = "eastus2" + +// Name of the resource group where the storage account will be created +// resource_group_name = "" + +// Name of the storage account. If not provided, a default name will be generated +// storage_account_name = "" + +// Name of the blob container for Terraform state +// container_name = "" + +// Additional tags to apply to resources +// tags = { +// } + +// Enable customer managed key encryption +// enable_cmk = false + +// The ID of the Key Vault Key to use for CMK encryption +// key_vault_key_id = "" + +// Allow public access to the storage account +// allow_public_access = true + +// List of IP ranges to allow access to the storage account +// allowed_ip_ranges = [] diff --git a/contexts/azure-example/terraform/cluster/azure-aks.tfvars b/contexts/azure-example/terraform/cluster/azure-aks.tfvars new file mode 100644 index 00000000..c4dd44b8 --- /dev/null +++ b/contexts/azure-example/terraform/cluster/azure-aks.tfvars @@ -0,0 +1,103 @@ +# Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. + +# Name of the resource +# name = "cluster" + +# Name of the resource group +# resource_group_name = null + +# Name of the AKS cluster +# cluster_name = null + +# Name on the VNET module +# vnet_module_name = "network" + +# ID of the subnet +# vnet_subnet_id = null + +# Region for the resources +# region = "eastus" + +# Version of Kubernetes to use +# kubernetes_version = "1.32" + +# Configuration for the default node pool +# default_node_pool = { +# host_encryption_enabled = true +# max_count = null +# max_pods = null +# min_count = null +# name = "system" +# node_count = null +# only_critical_addons_enabled = true +# os_disk_type = "Managed" +# vm_size = "Standard_D2s_v3" +# } + +# Configuration for the autoscaled node pool +# autoscaled_node_pool = { +# enabled = true +# host_encryption_enabled = true +# max_count = null +# max_pods = null +# min_count = null +# mode = "User" +# name = "autoscaled" +# os_disk_type = "Managed" +# vm_size = "Standard_D2s_v3" +# } + +# Whether to enable role-based access control for the AKS cluster +# role_based_access_control_enabled = true + +# Configuration for the AKS cluster's auto-scaler +# auto_scaler_profile = { +# balance_similar_node_groups = true +# max_graceful_termination_sec = null +# scale_down_delay_after_add = "10m" +# scale_down_delay_after_delete = "10s" +# scale_down_delay_after_failure = "3m" +# scale_down_unneeded = "10m" +# scale_down_unready = "20m" +# scale_down_utilization_threshold = "0.5" +# scan_interval = "10s" +# } + +# Configuration for the AKS cluster's workload autoscaler +# workload_autoscaler_profile = { +# keda_enabled = false +# vertical_pod_autoscaler_enabled = false +# } + +# The automatic upgrade channel for the AKS cluster +# automatic_upgrade_channel = "stable" + +# The SKU tier for the AKS cluster +# sku_tier = "Standard" + +# Whether to enable private cluster for the AKS cluster +# private_cluster_enabled = false + +# Whether to enable Azure Policy for the AKS cluster +# azure_policy_enabled = true + +# Whether to disable local accounts for the AKS cluster +# local_account_disabled = false + +# Whether to enable public network access for the AKS cluster +# public_network_access_enabled = true + +# The default action for the AKS cluster's network ACLs +# network_acls_default_action = "Allow" + +# The expiration date for the AKS cluster's key vault +# expiration_date = null + +# Additional user assigned identity IDs for the AKS cluster +# additional_cluster_identity_ids = [] + +# The number of days to retain the AKS cluster's key vault +# soft_delete_retention_days = null + +# Tags to apply to the resources +# tags = {} diff --git a/contexts/azure-example/terraform/gitops/flux.tfvars b/contexts/azure-example/terraform/gitops/flux.tfvars new file mode 100644 index 00000000..5d257471 --- /dev/null +++ b/contexts/azure-example/terraform/gitops/flux.tfvars @@ -0,0 +1,32 @@ +# Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. +# Module source: github.com/windsorcli/core//terraform/gitops/flux?ref=aws-eks + +# The namespace in which Flux will be installed +# flux_namespace = "system-gitops" + +# The version of Flux Helm chart to install +# flux_helm_version = "2.15.0" + +# The version of Flux to install +# flux_version = "2.5.1" + +# The private key to use for SSH authentication +# ssh_private_key = "(sensitive)" + +# The public key to use for SSH authentication +# ssh_public_key = "(sensitive)" + +# The known hosts to use for SSH authentication +# ssh_known_hosts = "(sensitive)" + +# The name of the secret to store the git authentication details +# git_auth_secret = "flux-system" + +# The git user to use to authenticte with the git provider +# git_username = "git" + +# The git password or PAT used to authenticte with the git provider +# git_password = "(sensitive)" + +# The token to use for the webhook +# webhook_token = "(sensitive)" diff --git a/contexts/azure-example/terraform/network/azure-vnet.tfvars b/contexts/azure-example/terraform/network/azure-vnet.tfvars new file mode 100644 index 00000000..dd618088 --- /dev/null +++ b/contexts/azure-example/terraform/network/azure-vnet.tfvars @@ -0,0 +1,24 @@ +// Managed by Windsor CLI: This file is partially managed by the windsor CLI. Your changes will not be overwritten. +// Module source: github.com/windsorcli/core//terraform/network/azure-vnet?ref=main + +// Region for the resources +// region = "eastus" + +// Name of the resource group +// resource_group_name = null + +// Name of the VNET +// vnet_name = null + +// Number of availability zones to create +// vnet_zones = null + +// CIDR block for VNET +// vnet_cidr = "10.20.0.0/16" + +// Subnets to create in the VNET +// vnet_subnets = { +// data = [] +// private = [] +// public = [] +// } diff --git a/contexts/local/blueprint.yaml b/contexts/local/blueprint.yaml index 6a587856..55c8f82c 100644 --- a/contexts/local/blueprint.yaml +++ b/contexts/local/blueprint.yaml @@ -43,7 +43,6 @@ kustomize: path: csi dependsOn: - policy-resources - force: true components: - openebs - openebs/dynamic-localpv @@ -51,7 +50,6 @@ kustomize: path: ingress/base dependsOn: - pki-resources - force: true components: - nginx - nginx/nodeport @@ -62,7 +60,6 @@ kustomize: path: pki/base dependsOn: - policy-resources - force: true components: - cert-manager - trust-manager @@ -70,7 +67,6 @@ kustomize: path: pki/resources dependsOn: - pki-base - force: true components: - private-issuer/ca - public-issuer/selfsigned @@ -78,7 +74,6 @@ kustomize: path: dns dependsOn: - pki-base - force: true components: - coredns - coredns/etcd @@ -90,6 +85,5 @@ kustomize: path: gitops/flux dependsOn: - ingress-base - force: true components: - webhook diff --git a/contexts/local/terraform/cluster/talos.tfvars b/contexts/local/terraform/cluster/talos.tfvars index ab379b0a..79937124 100644 --- a/contexts/local/terraform/cluster/talos.tfvars +++ b/contexts/local/terraform/cluster/talos.tfvars @@ -5,7 +5,7 @@ # kubernetes_version = "1.33.1" # The talos version to deploy. -# talos_version = "1.10.1" +# talos_version = "1.10.2" # The name of the cluster. cluster_name = "talos" @@ -32,12 +32,16 @@ common_config_patches = < [azurerm](#requirement\_azurerm) | 4.28.0 | +| [azurerm](#requirement\_azurerm) | 4.29.0 | ## Providers | Name | Version | |------|---------| -| [azurerm](#provider\_azurerm) | 4.28.0 | +| [azurerm](#provider\_azurerm) | 4.29.0 | | [local](#provider\_local) | 2.5.3 | ## Modules @@ -19,10 +19,10 @@ No modules. | Name | Type | |------|------| -| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.28.0/docs/resources/resource_group) | resource | -| [azurerm_storage_account.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.28.0/docs/resources/storage_account) | resource | -| [azurerm_storage_container.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.28.0/docs/resources/storage_container) | resource | -| [azurerm_user_assigned_identity.storage](https://registry.terraform.io/providers/hashicorp/azurerm/4.28.0/docs/resources/user_assigned_identity) | resource | +| [azurerm_resource_group.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.29.0/docs/resources/resource_group) | resource | +| [azurerm_storage_account.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.29.0/docs/resources/storage_account) | resource | +| [azurerm_storage_container.this](https://registry.terraform.io/providers/hashicorp/azurerm/4.29.0/docs/resources/storage_container) | resource | +| [azurerm_user_assigned_identity.storage](https://registry.terraform.io/providers/hashicorp/azurerm/4.29.0/docs/resources/user_assigned_identity) | resource | | [local_file.backend_config](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | ## Inputs diff --git a/docs/terraform/cluster/azure-aks.md b/docs/terraform/cluster/azure-aks.md index e93853b7..c7f949ab 100644 --- a/docs/terraform/cluster/azure-aks.md +++ b/docs/terraform/cluster/azure-aks.md @@ -1,15 +1,36 @@ +# Azure AKS Module + +This module creates an Azure Kubernetes Service (AKS) cluster with configurable node pools, networking, and security settings. + +## Prerequisites + +The following features must be enabled in your Azure subscription before using this module: + +- EncryptionAtHost feature for Microsoft.Compute provider + ```bash + az feature register --namespace Microsoft.Compute --name EncryptionAtHost + az provider register --namespace Microsoft.Compute + ``` + +### Subscription Requirements + +This module requires a paid Azure subscription. Free tier subscriptions are not supported due to: +- Insufficient vCPU quotas +- Restricted VM sizes +- Limited node pool operations + ## Requirements | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >=1.8 | -| [azurerm](#requirement\_azurerm) | ~> 4.28.0 | +| [azurerm](#requirement\_azurerm) | ~> 4.29.0 | ## Providers | Name | Version | |------|---------| -| [azurerm](#provider\_azurerm) | 4.28.0 | +| [azurerm](#provider\_azurerm) | 4.29.0 | | [local](#provider\_local) | 2.5.3 | | [random](#provider\_random) | 3.7.2 | | [time](#provider\_time) | 0.13.1 | @@ -50,12 +71,14 @@ No modules. | [additional\_cluster\_identity\_ids](#input\_additional\_cluster\_identity\_ids) | Additional user assigned identity IDs for the AKS cluster | `list(string)` | `[]` | no | | [auto\_scaler\_profile](#input\_auto\_scaler\_profile) | Configuration for the AKS cluster's auto-scaler |
object({
balance_similar_node_groups = bool
max_graceful_termination_sec = number
scale_down_delay_after_add = string
scale_down_delay_after_delete = string
scale_down_delay_after_failure = string
scan_interval = string
scale_down_unneeded = string
scale_down_unready = string
scale_down_utilization_threshold = string
})
|
{
"balance_similar_node_groups": true,
"max_graceful_termination_sec": 600,
"scale_down_delay_after_add": "10m",
"scale_down_delay_after_delete": "10s",
"scale_down_delay_after_failure": "3m",
"scale_down_unneeded": "10m",
"scale_down_unready": "20m",
"scale_down_utilization_threshold": "0.5",
"scan_interval": "10s"
}
| no | | [automatic\_upgrade\_channel](#input\_automatic\_upgrade\_channel) | The automatic upgrade channel for the AKS cluster | `string` | `"stable"` | no | -| [autoscaled\_node\_pool](#input\_autoscaled\_node\_pool) | Configuration for the autoscaled node pool |
object({
enabled = bool
name = string
vm_size = string
mode = string
os_disk_type = string
max_pods = number
host_encryption_enabled = bool
min_count = number
max_count = number
})
|
{
"enabled": true,
"host_encryption_enabled": true,
"max_count": 3,
"max_pods": 30,
"min_count": 1,
"mode": "User",
"name": "autoscaled",
"os_disk_type": "Managed",
"vm_size": "Standard_D2s_v3"
}
| no | +| [autoscaled\_node\_pool](#input\_autoscaled\_node\_pool) | Configuration for the autoscaled node pool |
object({
enabled = bool
name = string
vm_size = string
mode = string
os_disk_type = string
max_pods = number
host_encryption_enabled = bool
min_count = number
max_count = number
})
|
{
"enabled": true,
"host_encryption_enabled": true,
"max_count": 3,
"max_pods": 110,
"min_count": 1,
"mode": "User",
"name": "autoscaled",
"os_disk_type": "Managed",
"vm_size": "Standard_D2s_v3"
}
| no | | [azure\_policy\_enabled](#input\_azure\_policy\_enabled) | Whether to enable Azure Policy for the AKS cluster | `bool` | `true` | no | | [cluster\_name](#input\_cluster\_name) | Name of the AKS cluster | `string` | `null` | no | | [context\_id](#input\_context\_id) | Context ID for the resources | `string` | `null` | no | | [context\_path](#input\_context\_path) | The path to the context folder, where kubeconfig is stored | `string` | `""` | no | -| [default\_node\_pool](#input\_default\_node\_pool) | Configuration for the default node pool |
object({
name = string
vm_size = string
os_disk_type = string
max_pods = number
host_encryption_enabled = bool
min_count = number
max_count = number
node_count = number
only_critical_addons_enabled = bool
})
|
{
"host_encryption_enabled": true,
"max_count": 3,
"max_pods": 30,
"min_count": 1,
"name": "system",
"node_count": 1,
"only_critical_addons_enabled": true,
"os_disk_type": "Managed",
"vm_size": "Standard_D2s_v3"
}
| no | +| [default\_node\_pool](#input\_default\_node\_pool) | Configuration for the default node pool |
object({
name = string
vm_size = string
os_disk_type = string
max_pods = number
host_encryption_enabled = bool
min_count = number
max_count = number
node_count = number
only_critical_addons_enabled = bool
})
|
{
"host_encryption_enabled": true,
"max_count": 3,
"max_pods": 110,
"min_count": 1,
"name": "system",
"node_count": 1,
"only_critical_addons_enabled": true,
"os_disk_type": "Managed",
"vm_size": "Standard_D2s_v3"
}
| no | +| [dns\_service\_ip](#input\_dns\_service\_ip) | IP address for Kubernetes DNS service | `string` | `"10.96.0.10"` | no | +| [endpoint\_private\_access](#input\_endpoint\_private\_access) | Whether to enable private access to the Kubernetes API server | `bool` | `false` | no | | [expiration\_date](#input\_expiration\_date) | The expiration date for the AKS cluster's key vault | `string` | `null` | no | | [kubernetes\_version](#input\_kubernetes\_version) | Version of Kubernetes to use | `string` | `"1.32"` | no | | [local\_account\_disabled](#input\_local\_account\_disabled) | Whether to disable local accounts for the AKS cluster | `bool` | `false` | no | @@ -66,6 +89,7 @@ No modules. | [region](#input\_region) | Region for the resources | `string` | `"eastus"` | no | | [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group | `string` | `null` | no | | [role\_based\_access\_control\_enabled](#input\_role\_based\_access\_control\_enabled) | Whether to enable role-based access control for the AKS cluster | `bool` | `true` | no | +| [service\_cidr](#input\_service\_cidr) | CIDR block for Kubernetes services | `string` | `"10.96.0.0/16"` | no | | [sku\_tier](#input\_sku\_tier) | The SKU tier for the AKS cluster | `string` | `"Standard"` | no | | [soft\_delete\_retention\_days](#input\_soft\_delete\_retention\_days) | The number of days to retain the AKS cluster's key vault | `number` | `7` | no | | [tags](#input\_tags) | Tags to apply to the resources | `map(string)` | `{}` | no | diff --git a/docs/terraform/cluster/talos.md b/docs/terraform/cluster/talos.md index 9f4677f5..0a4e1810 100644 --- a/docs/terraform/cluster/talos.md +++ b/docs/terraform/cluster/talos.md @@ -44,7 +44,7 @@ | [controlplanes](#input\_controlplanes) | A list of machine configuration details for control planes. |
list(object({
hostname = optional(string)
endpoint = string
node = string
disk_selector = optional(object({
busPath = optional(string)
modalias = optional(string)
model = optional(string)
name = optional(string)
serial = optional(string)
size = optional(string)
type = optional(string)
uuid = optional(string)
wwid = optional(string)
}))
wipe_disk = optional(bool, true)
extra_kernel_args = optional(list(string), [])
config_patches = optional(string, "")
}))
| `[]` | no | | [kubernetes\_version](#input\_kubernetes\_version) | The kubernetes version to deploy. | `string` | `"1.33.1"` | no | | [os\_type](#input\_os\_type) | The operating system type, must be either 'unix' or 'windows' | `string` | `"unix"` | no | -| [talos\_version](#input\_talos\_version) | The talos version to deploy. | `string` | `"1.10.1"` | no | +| [talos\_version](#input\_talos\_version) | The talos version to deploy. | `string` | `"1.10.2"` | no | | [worker\_config\_patches](#input\_worker\_config\_patches) | A YAML string of worker config patches to apply. Can be an empty string or valid YAML. | `string` | `""` | no | | [workers](#input\_workers) | A list of machine configuration details |
list(object({
hostname = optional(string)
endpoint = string
node = string
disk_selector = optional(object({
busPath = optional(string)
modalias = optional(string)
model = optional(string)
name = optional(string)
serial = optional(string)
size = optional(string)
type = optional(string)
uuid = optional(string)
wwid = optional(string)
}))
wipe_disk = optional(bool, true)
extra_kernel_args = optional(list(string), [])
config_patches = optional(string, "")
}))
| `[]` | no | diff --git a/docs/terraform/network/azure-vnet.md b/docs/terraform/network/azure-vnet.md index e1025697..00a81db1 100644 --- a/docs/terraform/network/azure-vnet.md +++ b/docs/terraform/network/azure-vnet.md @@ -3,13 +3,13 @@ | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >=1.8 | -| [azurerm](#requirement\_azurerm) | ~> 4.28.0 | +| [azurerm](#requirement\_azurerm) | ~> 4.29.0 | ## Providers | Name | Version | |------|---------| -| [azurerm](#provider\_azurerm) | 4.28.0 | +| [azurerm](#provider\_azurerm) | 4.29.0 | ## Modules @@ -34,14 +34,15 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [context\_id](#input\_context\_id) | Context ID for the resources | `string` | `null` | no | +| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Enable NAT Gateway for private subnets | `bool` | `true` | no | | [name](#input\_name) | Name of the resource | `string` | `"network"` | no | | [region](#input\_region) | Region for the resources | `string` | `"eastus"` | no | | [resource\_group\_name](#input\_resource\_group\_name) | Name of the resource group | `string` | `null` | no | | [tags](#input\_tags) | Tags to apply to the resources | `map(string)` | `{}` | no | -| [vnet\_cidr](#input\_vnet\_cidr) | CIDR block for VNET | `string` | `"10.20.0.0/16"` | no | +| [vnet\_cidr](#input\_vnet\_cidr) | CIDR block for the VNET | `string` | `"10.0.0.0/16"` | no | | [vnet\_name](#input\_vnet\_name) | Name of the VNET | `string` | `null` | no | | [vnet\_subnets](#input\_vnet\_subnets) | Subnets to create in the VNET | `map(list(string))` |
{
"isolated": [],
"private": [],
"public": []
}
| no | -| [vnet\_zones](#input\_vnet\_zones) | Number of availability zones to create | `number` | `1` | no | +| [vnet\_zones](#input\_vnet\_zones) | Number of availability zones to create. Only used if vnet\_subnets is not defined | `number` | `1` | no | ## Outputs diff --git a/kustomize/ingress/base/nginx/helm-release.yaml b/kustomize/ingress/base/nginx/helm-release.yaml index 0f792cc6..ae5b3a88 100644 --- a/kustomize/ingress/base/nginx/helm-release.yaml +++ b/kustomize/ingress/base/nginx/helm-release.yaml @@ -12,7 +12,7 @@ spec: chart: ingress-nginx # renovate: datasource=helm depName=ingress-nginx package=ingress-nginx helmRepo=https://kubernetes.github.io/ingress-nginx version: 4.12.2 - sourceRef: + sourceRef: kind: HelmRepository name: ingress-nginx namespace: system-ingress diff --git a/terraform/backend/azurerm/test.tftest.hcl b/terraform/backend/azurerm/test.tftest.hcl index 030dbfee..736890b8 100644 --- a/terraform/backend/azurerm/test.tftest.hcl +++ b/terraform/backend/azurerm/test.tftest.hcl @@ -9,8 +9,9 @@ run "minimal_configuration" { command = plan variables { - context_id = "test" - location = "eastus2" + context_id = "test" + location = "eastus2" + container_name = "tfstate-test" } assert { @@ -112,9 +113,10 @@ run "backend_config_generation" { command = plan variables { - context_id = "test" - location = "eastus2" - context_path = "test" + context_id = "test" + location = "eastus2" + context_path = "test" + container_name = "tfstate-test" } assert { @@ -139,9 +141,10 @@ run "backend_config_without_context_path" { command = plan variables { - context_id = "test-nopath" - location = "eastus2" - context_path = "" + context_id = "test-nopath" + location = "eastus2" + context_path = "" + container_name = "tfstate-test-nopath" } assert { diff --git a/terraform/backend/azurerm/variables.tf b/terraform/backend/azurerm/variables.tf index 68c3b560..767d64fd 100644 --- a/terraform/backend/azurerm/variables.tf +++ b/terraform/backend/azurerm/variables.tf @@ -11,6 +11,10 @@ variable "context_path" { variable "context_id" { description = "Context ID for the resources" type = string + validation { + condition = var.context_id != null && var.context_id != "" + error_message = "context_id must be provided and cannot be empty." + } } #--------------------------------------------------------------------------------------------------- diff --git a/terraform/cluster/aws-eks/.terraform.lock.hcl b/terraform/cluster/aws-eks/.terraform.lock.hcl index 0f3b090e..6bec0754 100644 --- a/terraform/cluster/aws-eks/.terraform.lock.hcl +++ b/terraform/cluster/aws-eks/.terraform.lock.hcl @@ -5,6 +5,7 @@ provider "registry.terraform.io/hashicorp/aws" { version = "5.97.0" constraints = "5.97.0" hashes = [ + "h1:+3RBrMkjHMTzLr62gPGI04yTihsPYDWOVr3DyUMAPr8=", "h1:rUDE0OgA+6IiEA+w0cPp3/QQNH4SpjFjYcQ6p7byKS4=", "zh:02790ad98b767d8f24d28e8be623f348bcb45590205708334d52de2fb14f5a95", "zh:088b4398a161e45762dc28784fcc41c4fa95bd6549cb708b82de577f2d39ffc7", diff --git a/terraform/cluster/azure-aks/README.md b/terraform/cluster/azure-aks/README.md new file mode 100644 index 00000000..59681b6b --- /dev/null +++ b/terraform/cluster/azure-aks/README.md @@ -0,0 +1,20 @@ +# Azure AKS Module + +This module creates an Azure Kubernetes Service (AKS) cluster with configurable node pools, networking, and security settings. + +## Prerequisites + +The following features must be enabled in your Azure subscription before using this module: + +- EncryptionAtHost feature for Microsoft.Compute provider + ```bash + az feature register --namespace Microsoft.Compute --name EncryptionAtHost + az provider register --namespace Microsoft.Compute + ``` + +### Subscription Requirements + +This module requires a paid Azure subscription. Free tier subscriptions are not supported due to: +- Insufficient vCPU quotas +- Restricted VM sizes +- Limited node pool operations diff --git a/terraform/cluster/azure-aks/main.tf b/terraform/cluster/azure-aks/main.tf index 688539d1..1866192b 100644 --- a/terraform/cluster/azure-aks/main.tf +++ b/terraform/cluster/azure-aks/main.tf @@ -247,10 +247,13 @@ resource "azurerm_kubernetes_cluster" "main" { orchestrator_version = var.kubernetes_version only_critical_addons_enabled = var.default_node_pool.only_critical_addons_enabled # checkov:skip=CKV_AZURE_226: we are using the managed disk type to reduce costs + os_disk_type = var.default_node_pool.os_disk_type host_encryption_enabled = var.default_node_pool.host_encryption_enabled # checkov:skip=CKV_AZURE_168: This is set in the variable by default to 50 - max_pods = var.default_node_pool.max_pods + + max_pods = var.default_node_pool.max_pods + temporary_name_for_rotation = "rotate" } auto_scaler_profile { @@ -271,8 +274,10 @@ resource "azurerm_kubernetes_cluster" "main" { } network_profile { - network_policy = "azure" network_plugin = "azure" + network_policy = "azure" + service_cidr = var.service_cidr + dns_service_ip = var.dns_service_ip } oms_agent { @@ -293,12 +298,6 @@ resource "azurerm_kubernetes_cluster" "main" { user_assigned_identity_id = azurerm_user_assigned_identity.cluster.id } - lifecycle { - ignore_changes = [ - default_node_pool[0].upgrade_settings, - workload_autoscaler_profile - ] - } tags = merge({ Name = local.cluster_name }, local.tags) @@ -318,14 +317,10 @@ resource "azurerm_kubernetes_cluster_node_pool" "autoscaled" { # checkov:skip=CKV_AZURE_226: We are using the managed disk type to reduce costs os_disk_type = var.autoscaled_node_pool.os_disk_type # checkov:skip=CKV_AZURE_168: This is set in the variable by default to 50 - max_pods = var.autoscaled_node_pool.max_pods - host_encryption_enabled = var.autoscaled_node_pool.host_encryption_enabled + max_pods = var.autoscaled_node_pool.max_pods + host_encryption_enabled = var.autoscaled_node_pool.host_encryption_enabled + temporary_name_for_rotation = "rotate" - lifecycle { - ignore_changes = [ - upgrade_settings - ] - } tags = merge({ Name = var.autoscaled_node_pool.name }, local.tags) diff --git a/terraform/cluster/azure-aks/test.tftest.hcl b/terraform/cluster/azure-aks/test.tftest.hcl index 47c82b4c..475bb70d 100644 --- a/terraform/cluster/azure-aks/test.tftest.hcl +++ b/terraform/cluster/azure-aks/test.tftest.hcl @@ -218,3 +218,23 @@ run "config_file_created" { error_message = "Kubeconfig file should be generated when context path is provided" } } + +run "network_configuration" { + command = plan + + variables { + context_id = "test" + service_cidr = "10.0.0.0/16" + dns_service_ip = "10.0.0.10" + } + + assert { + condition = azurerm_kubernetes_cluster.main.network_profile[0].service_cidr == "10.0.0.0/16" + error_message = "Service CIDR should match input value" + } + + assert { + condition = azurerm_kubernetes_cluster.main.network_profile[0].dns_service_ip == "10.0.0.10" + error_message = "DNS service IP should match input value" + } +} diff --git a/terraform/cluster/azure-aks/variables.tf b/terraform/cluster/azure-aks/variables.tf index f6895d8e..dde63e97 100644 --- a/terraform/cluster/azure-aks/variables.tf +++ b/terraform/cluster/azure-aks/variables.tf @@ -72,7 +72,7 @@ variable "default_node_pool" { name = "system" vm_size = "Standard_D2s_v3" os_disk_type = "Managed" - max_pods = 30 + max_pods = 110 host_encryption_enabled = true min_count = 1 max_count = 3 @@ -100,7 +100,7 @@ variable "autoscaled_node_pool" { vm_size = "Standard_D2s_v3" mode = "User" os_disk_type = "Managed" - max_pods = 30 + max_pods = 110 host_encryption_enabled = true min_count = 1 max_count = 3 @@ -216,3 +216,21 @@ variable "tags" { type = map(string) default = {} } + +variable "service_cidr" { + description = "CIDR block for Kubernetes services" + type = string + default = "10.96.0.0/16" +} + +variable "dns_service_ip" { + description = "IP address for Kubernetes DNS service" + type = string + default = "10.96.0.10" +} + +variable "endpoint_private_access" { + description = "Whether to enable private access to the Kubernetes API server" + type = bool + default = false +} diff --git a/terraform/network/azure-vnet/main.tf b/terraform/network/azure-vnet/main.tf index 79aae102..df719b31 100644 --- a/terraform/network/azure-vnet/main.tf +++ b/terraform/network/azure-vnet/main.tf @@ -68,7 +68,7 @@ resource "azurerm_subnet" "public" { name = "public-${count.index + 1}-${var.context_id}" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name - address_prefixes = length(var.vnet_subnets["public"]) > 0 ? [var.vnet_subnets["public"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.${count.index + 1}.0/24"] + address_prefixes = length(var.vnet_subnets["public"]) > 0 ? [var.vnet_subnets["public"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.${51 + count.index}.0/24"] } # Private subnets @@ -77,7 +77,7 @@ resource "azurerm_subnet" "private" { name = "private-${count.index + 1}-${var.context_id}" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name - address_prefixes = length(var.vnet_subnets["private"]) > 0 ? [var.vnet_subnets["private"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.1${count.index + 1}.0/24"] + address_prefixes = length(var.vnet_subnets["private"]) > 0 ? [var.vnet_subnets["private"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.${count.index * 16}.0/20"] } # Isolated subnets @@ -86,7 +86,7 @@ resource "azurerm_subnet" "isolated" { name = "isolated-${count.index + 1}-${var.context_id}" resource_group_name = azurerm_resource_group.main.name virtual_network_name = azurerm_virtual_network.main.name - address_prefixes = length(var.vnet_subnets["isolated"]) > 0 ? [var.vnet_subnets["isolated"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.2${count.index + 1}.0/24"] + address_prefixes = length(var.vnet_subnets["isolated"]) > 0 ? [var.vnet_subnets["isolated"][count.index]] : ["${join(".", slice(split(".", var.vnet_cidr), 0, 2))}.${48 + count.index}.0/24"] } #----------------------------------------------------------------------------------------------------------------------- @@ -127,7 +127,7 @@ resource "azurerm_nat_gateway_public_ip_association" "main" { # Associate NAT Gateway with private subnet resource "azurerm_subnet_nat_gateway_association" "private" { - count = var.vnet_zones + count = var.enable_nat_gateway ? var.vnet_zones : 0 subnet_id = azurerm_subnet.private[count.index].id nat_gateway_id = azurerm_nat_gateway.main[count.index].id } diff --git a/terraform/network/azure-vnet/test.tftest.hcl b/terraform/network/azure-vnet/test.tftest.hcl index 7483ba0c..9963e589 100644 --- a/terraform/network/azure-vnet/test.tftest.hcl +++ b/terraform/network/azure-vnet/test.tftest.hcl @@ -21,8 +21,8 @@ run "minimal_configuration" { } assert { - condition = [for space in azurerm_virtual_network.main.address_space : space][0] == "10.20.0.0/16" - error_message = "VNet CIDR should default to '10.20.0.0/16'" + condition = [for space in azurerm_virtual_network.main.address_space : space][0] == "10.0.0.0/16" + error_message = "VNet CIDR should default to '10.0.0.0/16'" } assert { @@ -77,23 +77,23 @@ run "full_configuration" { } assert { - condition = [for space in azurerm_virtual_network.main.address_space : space][0] == "10.30.0.0/16" - error_message = "VNet CIDR should match input" + condition = tolist(azurerm_virtual_network.main.address_space)[0] == "10.30.0.0/16" + error_message = "VNet CIDR should match input value" } assert { condition = length(azurerm_subnet.public) == 2 - error_message = "Two public subnets should be created" + error_message = "Should create 2 public subnets" } assert { condition = length(azurerm_subnet.private) == 2 - error_message = "Two private subnets should be created" + error_message = "Should create 2 private subnets" } assert { condition = length(azurerm_subnet.isolated) == 2 - error_message = "Two isolated subnets should be created" + error_message = "Should create 2 isolated subnets" } assert { @@ -101,3 +101,124 @@ run "full_configuration" { error_message = "Two NAT Gateways should be created" } } + +# Tests NAT Gateway configuration +run "nat_gateway_configuration" { + command = plan + + variables { + context_id = "test" + name = "windsor-vnet" + enable_nat_gateway = false + } + + assert { + condition = length(azurerm_subnet_nat_gateway_association.private) == 0 + error_message = "No NAT Gateway associations should be created when disabled" + } +} + +run "automatic_subnet_creation" { + command = plan + + variables { + context_id = "test" + name = "test-network" + vnet_zones = 3 + vnet_subnets = { + private = [] + isolated = [] + public = [] + } + } + + assert { + condition = length(azurerm_subnet.private) == 3 + error_message = "Should create 3 private subnets" + } + + assert { + condition = azurerm_subnet.private[0].address_prefixes[0] == "10.0.0.0/20" + error_message = "First private subnet should be 10.0.0.0/20" + } + + assert { + condition = azurerm_subnet.private[1].address_prefixes[0] == "10.0.16.0/20" + error_message = "Second private subnet should be 10.0.16.0/20" + } + + assert { + condition = azurerm_subnet.private[2].address_prefixes[0] == "10.0.32.0/20" + error_message = "Third private subnet should be 10.0.32.0/20" + } + + assert { + condition = length(azurerm_subnet.isolated) == 3 + error_message = "Should create 3 isolated subnets" + } + + assert { + condition = azurerm_subnet.isolated[0].address_prefixes[0] == "10.0.48.0/24" + error_message = "First isolated subnet should be 10.0.48.0/24" + } + + assert { + condition = azurerm_subnet.isolated[1].address_prefixes[0] == "10.0.49.0/24" + error_message = "Second isolated subnet should be 10.0.49.0/24" + } + + assert { + condition = azurerm_subnet.isolated[2].address_prefixes[0] == "10.0.50.0/24" + error_message = "Third isolated subnet should be 10.0.50.0/24" + } + + assert { + condition = length(azurerm_subnet.public) == 3 + error_message = "Should create 3 public subnets" + } + + assert { + condition = azurerm_subnet.public[0].address_prefixes[0] == "10.0.51.0/24" + error_message = "First public subnet should be 10.0.51.0/24" + } + + assert { + condition = azurerm_subnet.public[1].address_prefixes[0] == "10.0.52.0/24" + error_message = "Second public subnet should be 10.0.52.0/24" + } + + assert { + condition = azurerm_subnet.public[2].address_prefixes[0] == "10.0.53.0/24" + error_message = "Third public subnet should be 10.0.53.0/24" + } +} + +# Tests validation rules for required variables +run "multiple_invalid_inputs" { + command = plan + + variables { + context_id = "test" + vnet_subnets = { + private = [ + "10.0.0.0/20", + "invalid-cidr", + "10.0.32.0/20" + ] + isolated = [ + "10.0.48.0/24", + "10.0.49.0/24", + "10.0.50.0/24" + ] + public = [ + "10.0.51.0/24", + "10.0.52.0/24", + "10.0.53.0/24" + ] + } + } + + expect_failures = [ + var.vnet_subnets, + ] +} diff --git a/terraform/network/azure-vnet/variables.tf b/terraform/network/azure-vnet/variables.tf index a23ab8ab..f6c5a99a 100644 --- a/terraform/network/azure-vnet/variables.tf +++ b/terraform/network/azure-vnet/variables.tf @@ -1,9 +1,12 @@ - # Variables variable "context_id" { description = "Context ID for the resources" type = string default = null + validation { + condition = var.context_id != null && var.context_id != "" + error_message = "context_id must be provided and cannot be empty." + } } variable "region" { @@ -31,33 +34,47 @@ variable "vnet_name" { } variable "vnet_cidr" { - description = "CIDR block for VNET" + description = "CIDR block for the VNET" type = string - default = "10.20.0.0/16" + default = "10.0.0.0/16" } variable "vnet_subnets" { description = "Subnets to create in the VNET" type = map(list(string)) - # example: { - # public = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] - # private = ["10.20.11.0/24", "10.20.12.0/24", "10.20.13.0/24"] - # isolated = ["10.20.21.0/24", "10.20.22.0/24", "10.20.23.0/24"] - # } default = { - public = [] private = [] isolated = [] + public = [] + } + validation { + condition = alltrue([for subnet in var.vnet_subnets["private"] : can(cidrhost(subnet, 0))]) + error_message = "Each private subnet must be a valid CIDR block" + } + + validation { + condition = alltrue([for subnet in var.vnet_subnets["isolated"] : can(cidrhost(subnet, 0))]) + error_message = "Each isolated subnet must be a valid CIDR block" + } + + validation { + condition = alltrue([for subnet in var.vnet_subnets["public"] : can(cidrhost(subnet, 0))]) + error_message = "Each public subnet must be a valid CIDR block" } } -# Only used if vnet_subnets is not defined variable "vnet_zones" { - description = "Number of availability zones to create" + description = "Number of availability zones to create. Only used if vnet_subnets is not defined" type = number default = 1 } +variable "enable_nat_gateway" { + description = "Enable NAT Gateway for private subnets" + type = bool + default = true +} + variable "tags" { description = "Tags to apply to the resources" type = map(string)