diff --git a/aqua.yaml b/aqua.yaml index d0908e4ef..742caead3 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -32,3 +32,4 @@ packages: - name: evilmartians/lefthook@v2.0.9 - name: bridgecrewio/checkov@3.2.495 - name: kubernetes-sigs/krew@v0.4.5 + - name: Azure/kubelogin@v0.2.13 diff --git a/terraform/cluster/azure-aks/main.tf b/terraform/cluster/azure-aks/main.tf index ae3f9525b..f388c36c4 100644 --- a/terraform/cluster/azure-aks/main.tf +++ b/terraform/cluster/azure-aks/main.tf @@ -34,6 +34,8 @@ provider "azurerm" { data "azurerm_client_config" "current" {} +data "azurerm_subscription" "current" {} + data "azurerm_virtual_network" "vnet" { name = "${var.vnet_module_name}-${var.context_id}" resource_group_name = "${var.vnet_module_name}-${var.context_id}" @@ -233,8 +235,12 @@ resource "azurerm_kubernetes_cluster" "main" { role_based_access_control_enabled = var.role_based_access_control_enabled automatic_upgrade_channel = var.automatic_upgrade_channel sku_tier = var.sku_tier - # checkov:skip=CKV_AZURE_6: This feature is in preview, we are using a public cluster for testing - # api_server_authorized_ip_ranges = [0.0.0.0/0] + + # checkov:skip=CKV_AZURE_6: We allow user to restrict IPs or default to open (null) + api_server_access_profile { + authorized_ip_ranges = var.authorized_ip_ranges + } + # checkov:skip=CKV_AZURE_115: We are using a public cluster for testing # private clusters are encouraged for production private_cluster_enabled = var.private_cluster_enabled @@ -244,6 +250,11 @@ resource "azurerm_kubernetes_cluster" "main" { # checkov:skip=CKV_AZURE_141: We are setting this to false to avoid the creation of an AD local_account_disabled = var.local_account_disabled + azure_active_directory_role_based_access_control { + azure_rbac_enabled = true + admin_group_object_ids = var.admin_object_ids + } + key_vault_secrets_provider { secret_rotation_enabled = true } @@ -380,3 +391,17 @@ resource "local_file" "kube_config" { content = azurerm_kubernetes_cluster.main.kube_config_raw filename = local.kubeconfig_path } + +# Automatically assign "Azure Kubernetes Service RBAC Cluster Admin" to the +# identity running Terraform (the deployer) and any additional admins provided. +# This ensures immediate access when local_account_disabled is set to true. +resource "azurerm_role_assignment" "aks_rbac_admin" { + for_each = toset(concat( + [data.azurerm_client_config.current.object_id], + var.admin_object_ids + )) + + scope = azurerm_kubernetes_cluster.main.id + role_definition_name = "Azure Kubernetes Service RBAC Cluster Admin" + principal_id = each.value +} diff --git a/terraform/cluster/azure-aks/test.tftest.hcl b/terraform/cluster/azure-aks/test.tftest.hcl index 5c02fa529..3fa52e911 100644 --- a/terraform/cluster/azure-aks/test.tftest.hcl +++ b/terraform/cluster/azure-aks/test.tftest.hcl @@ -5,6 +5,11 @@ mock_provider "azurerm" { object_id = "22222222-2222-2222-2222-222222222222" } } + mock_data "azurerm_subscription" { + defaults = { + subscription_id = "12345678-1234-9876-4563-123456789012" + } + } mock_data "azurerm_virtual_network" { defaults = { subnets = ["private-1-test", "private-2-test", "private-3-test", "public-1-test", "public-2-test", "isolated-1-test", "isolated-2-test"] @@ -76,8 +81,8 @@ run "minimal_configuration" { } assert { - condition = azurerm_kubernetes_cluster.main.local_account_disabled == false - error_message = "Local accounts should be enabled by default" + condition = azurerm_kubernetes_cluster.main.local_account_disabled == true + error_message = "Local accounts should be disabled by default" } assert { @@ -85,6 +90,31 @@ run "minimal_configuration" { error_message = "Cluster should use system-assigned identity by default" } + assert { + condition = azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].azure_rbac_enabled == true + error_message = "Azure RBAC should be enabled by default" + } + + assert { + condition = length(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids) == 0 + error_message = "Admin group object IDs should be empty by default" + } + + assert { + condition = azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges == null + error_message = "Authorized IP ranges should be null by default (allowing all)" + } + + assert { + condition = length(azurerm_role_assignment.aks_rbac_admin) == 1 + error_message = "Role assignment should be created for the deployer identity by default" + } + + assert { + condition = azurerm_role_assignment.aks_rbac_admin["22222222-2222-2222-2222-222222222222"].role_definition_name == "Azure Kubernetes Service RBAC Cluster Admin" + error_message = "Role assignment should use 'Azure Kubernetes Service RBAC Cluster Admin' role" + } + assert { condition = azurerm_kubernetes_cluster.main.oidc_issuer_enabled == true error_message = "OIDC issuer should be enabled by default" @@ -145,6 +175,8 @@ run "full_configuration" { private_cluster_enabled = false azure_policy_enabled = true local_account_disabled = false + authorized_ip_ranges = ["10.0.0.0/8"] + admin_object_ids = ["55555555-5555-5555-5555-555555555555"] enable_volume_snapshots = true } @@ -247,6 +279,36 @@ run "full_configuration" { condition = contains(azurerm_role_definition.aks_kubelet_vmss_disk_manager.permissions[0].actions, "Microsoft.Compute/snapshots/write") error_message = "Snapshot write permissions should be included when enable_volume_snapshots is true" } + + assert { + condition = length(azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges) == 1 + error_message = "Authorized IP ranges should contain 1 entry" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges, "10.0.0.0/8") + error_message = "Authorized IP ranges should include 10.0.0.0/8" + } + + assert { + condition = azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].azure_rbac_enabled == true + error_message = "Azure RBAC should be enabled" + } + + assert { + condition = length(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids) == 1 + error_message = "Admin group object IDs should contain 1 entry" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids, "55555555-5555-5555-5555-555555555555") + error_message = "Admin group object IDs should include the specified object ID" + } + + assert { + condition = length(azurerm_role_assignment.aks_rbac_admin) == 2 + error_message = "Role assignments should be created for deployer plus 1 admin object ID (2 total)" + } } # Tests the private cluster configuration, ensuring that enabling the private_cluster_enabled @@ -306,6 +368,91 @@ run "network_configuration" { } } +# Tests the authorized IP ranges configuration, ensuring that setting authorized_ip_ranges +# results in the API server access profile being configured with the specified IP ranges. +run "authorized_ip_ranges" { + command = plan + + variables { + context_id = "test" + name = "windsor-aks" + cluster_name = "test-cluster" + kubernetes_version = "1.32" + authorized_ip_ranges = ["10.0.0.0/8", "192.168.0.0/16"] + } + + assert { + condition = length(azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges) == 2 + error_message = "Authorized IP ranges should contain 2 entries" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges, "10.0.0.0/8") + error_message = "Authorized IP ranges should include 10.0.0.0/8" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.api_server_access_profile[0].authorized_ip_ranges, "192.168.0.0/16") + error_message = "Authorized IP ranges should include 192.168.0.0/16" + } +} + +# Tests the Azure RBAC configuration with admin object IDs, ensuring that the +# azure_active_directory_role_based_access_control block is configured correctly and +# role assignments are created for all specified admin object IDs plus the deployer. +run "azure_rbac_with_admin_object_ids" { + command = plan + + variables { + context_id = "test" + name = "windsor-aks" + cluster_name = "test-cluster" + kubernetes_version = "1.32" + local_account_disabled = true + admin_object_ids = ["33333333-3333-3333-3333-333333333333", "44444444-4444-4444-4444-444444444444"] + } + + assert { + condition = azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].azure_rbac_enabled == true + error_message = "Azure RBAC should be enabled" + } + + assert { + condition = length(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids) == 2 + error_message = "Admin group object IDs should contain 2 entries" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids, "33333333-3333-3333-3333-333333333333") + error_message = "Admin group object IDs should include the first specified object ID" + } + + assert { + condition = contains(azurerm_kubernetes_cluster.main.azure_active_directory_role_based_access_control[0].admin_group_object_ids, "44444444-4444-4444-4444-444444444444") + error_message = "Admin group object IDs should include the second specified object ID" + } + + assert { + condition = length(azurerm_role_assignment.aks_rbac_admin) == 3 + error_message = "Role assignments should be created for deployer plus 2 admin object IDs (3 total)" + } + + assert { + condition = azurerm_role_assignment.aks_rbac_admin["22222222-2222-2222-2222-222222222222"].role_definition_name == "Azure Kubernetes Service RBAC Cluster Admin" + error_message = "Role assignment for deployer should use 'Azure Kubernetes Service RBAC Cluster Admin' role" + } + + assert { + condition = azurerm_role_assignment.aks_rbac_admin["33333333-3333-3333-3333-333333333333"].role_definition_name == "Azure Kubernetes Service RBAC Cluster Admin" + error_message = "Role assignment for first admin should use 'Azure Kubernetes Service RBAC Cluster Admin' role" + } + + assert { + condition = azurerm_role_assignment.aks_rbac_admin["44444444-4444-4444-4444-444444444444"].role_definition_name == "Azure Kubernetes Service RBAC Cluster Admin" + error_message = "Role assignment for second admin should use 'Azure Kubernetes Service RBAC Cluster Admin' role" + } +} + run "multiple_invalid_inputs" { command = plan expect_failures = [ diff --git a/terraform/cluster/azure-aks/variables.tf b/terraform/cluster/azure-aks/variables.tf index ae6203aaa..3c8d35ebe 100644 --- a/terraform/cluster/azure-aks/variables.tf +++ b/terraform/cluster/azure-aks/variables.tf @@ -2,6 +2,12 @@ # Variables #----------------------------------------------------------------------------------------------------------------------- +variable "admin_object_ids" { + type = list(string) + description = "List of Azure AD Object IDs (User or Group) to assign 'Azure Kubernetes Service RBAC Cluster Admin' role. Required when local_account_disabled is true to ensure access." + default = [] +} + variable "context_path" { type = string description = "The path to the context folder, where kubeconfig is stored" @@ -184,7 +190,13 @@ variable "azure_policy_enabled" { variable "local_account_disabled" { type = bool description = "Whether to disable local accounts for the AKS cluster" - default = false + default = true +} + +variable "authorized_ip_ranges" { + type = set(string) + description = "Set of authorized IP ranges to allow access to the API server. If null, allows all (0.0.0.0/0)." + default = null } variable "public_network_access_enabled" {