diff --git a/src/_nebari/stages/infrastructure/__init__.py b/src/_nebari/stages/infrastructure/__init__.py index 553e520e3a..9d5abbbd55 100644 --- a/src/_nebari/stages/infrastructure/__init__.py +++ b/src/_nebari/stages/infrastructure/__init__.py @@ -91,6 +91,22 @@ class AzureNodeGroupInputVars(schema.Base): max_nodes: int +class AADAccessControl(schema.Base): + """ + Represents the configuration for Azure Active Directory Role-Based Access Control + (RBAC) integration in a Kubernetes cluster. + + Attributes: + enabled (bool): Indicates whether Azure AD-based Role-Based Access Control is + enabled. + admin_group_object_ids (List[str]): A list of Object IDs of Azure AD groups + assigned administrative roles on the cluster. + """ + + azure_rbac_enabled: bool + admin_group_object_ids: List[str] + + class AzureInputVars(schema.Base): name: str environment: str @@ -108,6 +124,7 @@ class AzureInputVars(schema.Base): network_profile: Optional[Dict[str, str]] = None azure_policy_enabled: Optional[bool] = None workload_identity_enabled: bool = False + aad_access_control: Optional[AADAccessControl] = None class AWSAmiTypes(str, enum.Enum): @@ -375,6 +392,7 @@ class AzureProvider(schema.Base): max_pods: Optional[int] = None workload_identity_enabled: bool = False azure_policy_enabled: Optional[bool] = None + aad_access_control: Optional[AADAccessControl] = None @model_validator(mode="before") @classmethod @@ -828,6 +846,7 @@ def input_vars(self, stage_outputs: Dict[str, Dict[str, Any]]): network_profile=self.config.azure.network_profile, max_pods=self.config.azure.max_pods, workload_identity_enabled=self.config.azure.workload_identity_enabled, + aad_access_control=self.config.azure.aad_access_control, azure_policy_enabled=self.config.azure.azure_policy_enabled, ).model_dump() elif self.config.provider == schema.ProviderEnum.aws: diff --git a/src/_nebari/stages/infrastructure/template/azure/main.tf b/src/_nebari/stages/infrastructure/template/azure/main.tf index 960b755f8c..2131bd306d 100644 --- a/src/_nebari/stages/infrastructure/template/azure/main.tf +++ b/src/_nebari/stages/infrastructure/template/azure/main.tf @@ -44,5 +44,6 @@ module "kubernetes" { vnet_subnet_id = var.vnet_subnet_id private_cluster_enabled = var.private_cluster_enabled workload_identity_enabled = var.workload_identity_enabled + aad_access_control = var.aad_access_control azure_policy_enabled = var.azure_policy_enabled } diff --git a/src/_nebari/stages/infrastructure/template/azure/modules/credentials/outputs.tf b/src/_nebari/stages/infrastructure/template/azure/modules/credentials/outputs.tf new file mode 100644 index 0000000000..0ceac66d76 --- /dev/null +++ b/src/_nebari/stages/infrastructure/template/azure/modules/credentials/outputs.tf @@ -0,0 +1,12 @@ +output "credentials" { + description = "Credentials required for connecting to Kubernetes cluster" + sensitive = true + value = { + endpoint = var.azure_rbac_enabled ? var.kube_admin_config.host : var.kube_config.host + username = var.azure_rbac_enabled ? var.kube_admin_config.username : var.kube_config.username + password = var.azure_rbac_enabled ? var.kube_admin_config.password : var.kube_config.password + client_certificate = var.azure_rbac_enabled ? base64decode(var.kube_admin_config.client_certificate) : base64decode(var.kube_config.client_certificate) + client_key = var.azure_rbac_enabled ? base64decode(var.kube_admin_config.client_key) : base64decode(var.kube_config.client_key) + cluster_ca_certificate = var.azure_rbac_enabled ? base64decode(var.kube_admin_config.cluster_ca_certificate) : base64decode(var.kube_config.cluster_ca_certificate) + } +} diff --git a/src/_nebari/stages/infrastructure/template/azure/modules/credentials/variables.tf b/src/_nebari/stages/infrastructure/template/azure/modules/credentials/variables.tf new file mode 100644 index 0000000000..cf1916cb7e --- /dev/null +++ b/src/_nebari/stages/infrastructure/template/azure/modules/credentials/variables.tf @@ -0,0 +1,16 @@ +variable "azure_rbac_enabled" { + description = "Flag to enable Azure RBAC" + type = bool +} + +variable "kube_admin_config" { + description = "Kube admin config for RBAC" + type = any + sensitive = true +} + +variable "kube_config" { + description = "Kube config for standard access" + type = any + sensitive = true +} diff --git a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/main.tf b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/main.tf index f97f1f6383..7650d12ea1 100644 --- a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/main.tf +++ b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/main.tf @@ -1,3 +1,7 @@ +data "azurerm_client_config" "current" { + count = var.aad_access_control.azure_rbac_enabled ? 1 : 0 +} + # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster resource "azurerm_kubernetes_cluster" "main" { name = var.name @@ -67,6 +71,15 @@ resource "azurerm_kubernetes_cluster" "main" { ] } + dynamic "azure_active_directory_role_based_access_control" { + for_each = var.aad_access_control.azure_rbac_enabled ? [var.aad_access_control] : [] + content { + azure_rbac_enabled = var.aad_access_control.azure_rbac_enabled + admin_group_object_ids = var.aad_access_control.admin_group_object_ids + tenant_id = data.azurerm_client_config.current[0].tenant_id + managed = true + } + } } # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster_node_pool diff --git a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/outputs.tf b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/outputs.tf index e96187bcd6..890b764886 100644 --- a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/outputs.tf +++ b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/outputs.tf @@ -1,21 +1,20 @@ +module "k8s_credentials" { + source = "../credentials" + azure_rbac_enabled = var.aad_access_control.azure_rbac_enabled + kube_admin_config = azurerm_kubernetes_cluster.main.kube_admin_config[0] + kube_config = azurerm_kubernetes_cluster.main.kube_config[0] +} + output "credentials" { - description = "Credentials required for connecting to kubernetes cluster" + description = "Credentials required for connecting to Kubernetes cluster" sensitive = true - value = { - # see bottom of https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster - endpoint = azurerm_kubernetes_cluster.main.kube_config.0.host - username = azurerm_kubernetes_cluster.main.kube_config.0.username - password = azurerm_kubernetes_cluster.main.kube_config.0.password - client_certificate = base64decode(azurerm_kubernetes_cluster.main.kube_config.0.client_certificate) - client_key = base64decode(azurerm_kubernetes_cluster.main.kube_config.0.client_key) - cluster_ca_certificate = base64decode(azurerm_kubernetes_cluster.main.kube_config.0.cluster_ca_certificate) - } + value = module.k8s_credentials.credentials } output "kubeconfig" { description = "Kubernetes connection kubeconfig" sensitive = true - value = azurerm_kubernetes_cluster.main.kube_config_raw + value = var.aad_access_control.azure_rbac_enabled ? azurerm_kubernetes_cluster.main.kube_admin_config_raw : azurerm_kubernetes_cluster.main.kube_config_raw } output "cluster_oidc_issuer_url" { diff --git a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/variables.tf b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/variables.tf index 95d2045420..090864e680 100644 --- a/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/variables.tf +++ b/src/_nebari/stages/infrastructure/template/azure/modules/kubernetes/variables.tf @@ -77,6 +77,19 @@ variable "workload_identity_enabled" { default = false } +variable "aad_access_control" { + description = "Azure Active Directory Role-Based Access Control (RBAC) integration in a Kubernetes cluster" + type = object({ + azure_rbac_enabled : bool + admin_group_object_ids : list(string) + }) + default = { + azure_rbac_enabled : false + admin_group_object_ids : [] + } + nullable = false +} + variable "authorized_ip_ranges" { description = "The ip range allowed to access the Kubernetes API server, defaults to 0.0.0.0/0" type = list(string) diff --git a/src/_nebari/stages/infrastructure/template/azure/variables.tf b/src/_nebari/stages/infrastructure/template/azure/variables.tf index 44ef90463f..31c8e7d8ae 100644 --- a/src/_nebari/stages/infrastructure/template/azure/variables.tf +++ b/src/_nebari/stages/infrastructure/template/azure/variables.tf @@ -83,6 +83,18 @@ variable "workload_identity_enabled" { default = false } +variable "aad_access_control" { + description = "Azure Active Directory Role-Based Access Control (RBAC) integration in a Kubernetes cluster" + type = object({ + azure_rbac_enabled : bool + admin_group_object_ids : list(string) + }) + default = { + azure_rbac_enabled : false + admin_group_object_ids : [] + } +} + variable "authorized_ip_ranges" { description = "The ip range allowed to access the Kubernetes API server, defaults to 0.0.0.0/0" type = list(string) diff --git a/src/_nebari/stages/tf_objects.py b/src/_nebari/stages/tf_objects.py index 28884d4789..7f1b61925c 100644 --- a/src/_nebari/stages/tf_objects.py +++ b/src/_nebari/stages/tf_objects.py @@ -22,6 +22,31 @@ def NebariKubernetesProvider(nebari_config: schema.Main): token="${data.aws_eks_cluster_auth.default.token}", ), ) + + # if ( + # nebari_config.provider == "azure" + # ): # we need to also check for disabled local account + # return deep_merge( + # Data("azurerm_kubernetes_cluster", "default", name=cluster_name), + # Provider( + # "kubernetes", + # host="${data.azurerm_kubernetes_cluster.default.kube_config.0.host}", + # cluster_ca_certificate="${base64decode(data.azurerm_kubernetes_cluster.default.kube_config.0.cluster_ca_certificate)}", + # exec={ + # "api_version": "client.authentication.k8s.io/v1beta1", + # "command": "az", + # "args": [ + # "aks", + # "get-credentials", + # "--resource-group", + # nebari_config.azure.resource_group_name, + # "--name", + # nebari_config.azure.cluster_name, + # "--overwrite-existing", + # ], + # }, + # ), + # ) return Provider( "kubernetes", )