Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions quickstart/101-aks-entra-k8s-rbac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Use Microsoft Entra ID Groups with Kubernetes RBAC in AKS

This template uses Microsoft Entra ID groups with Kubernetes role-based access control (Kubernetes RBAC) in an existing Azure Kubernetes Service (AKS) cluster.

The example creates two Microsoft Entra groups and scopes each group to a namespace:

- The `appdev` group can manage resources in the `dev` namespace.
- The `opssre` group can manage resources in the `sre` namespace.

The AKS cluster must already have Microsoft Entra integration and Kubernetes RBAC enabled. Azure RBAC for Kubernetes Authorization must be disabled for this example.

## Prerequisites

- An Azure subscription
- An existing AKS cluster with Microsoft Entra integration and Kubernetes RBAC enabled
- Azure RBAC for Kubernetes Authorization disabled on the cluster
- Terraform `>= 1.6.0` installed
- Azure CLI and `kubectl` installed
- Permission to create Microsoft Entra users and groups
- Permission to assign Azure roles at the AKS cluster scope
- Permission to manage Kubernetes resources on the AKS cluster

Sign in to Azure and select the subscription to use:

```console
az login
az account set --subscription <subscription-id>
```

## Terraform providers and variables

This sample uses the AzureRM provider to reference the existing AKS cluster and assign Azure permissions, the AzureAD provider to create Microsoft Entra users and groups, and the Kubernetes provider to create namespaces, Roles, and RoleBindings.

The Terraform variables require values for the existing cluster and the example users:

```hcl
resource_group_name = "<resource-group-name>"
aks_cluster_name = "<aks-cluster-name>"
appdev_user_principal_name = "<appdev-user-principal-name>"
opssre_user_principal_name = "<opssre-user-principal-name>"
temporary_password = "<temporary-password>"
```

`temporary_password` is a sensitive Terraform variable. Replace `<temporary-password>` with a strong temporary password supplied through a secure `terraform.tfvars` file or another protected input method. Do not commit that value.

## Example

Create a `terraform.tfvars` file with values for the existing cluster and test users, then initialize, format, and validate the configuration:

```console
terraform init
terraform fmt
terraform validate
```

Review and apply the configuration:

```console
terraform plan
terraform apply
```

The configuration creates the following Microsoft Entra and Kubernetes resources:

- `appdev` and `opssre` Microsoft Entra security groups
- One example Microsoft Entra user in each group
- Cluster User Role assignments for both groups
- `dev` and `sre` Kubernetes namespaces
- Namespace-scoped Kubernetes Roles and RoleBindings

## Verify namespace access

Get credentials for the AKS cluster:

```console
az aks get-credentials \
--resource-group <resource-group-name> \
--name <aks-cluster-name>
```

Verify that both namespaces exist:

```console
kubectl get namespaces
```

The output should include `dev` and `sre`.

## Test appdev access

Authenticate as the `appdev` test user and create a pod in the `dev` namespace:

```console
kubectl run nginx-dev \
--image=nginx \
--restart=Never \
--namespace dev
kubectl get pods --namespace dev
```

Listing pods across all namespaces or creating a pod in the `sre` namespace should return a `Forbidden` error because the `appdev` group is scoped to `dev`.

## Test opssre access

Authenticate as the `opssre` test user and create a pod in the `sre` namespace:

```console
kubectl run nginx-sre \
--image=nginx \
--restart=Never \
--namespace sre
kubectl get pods --namespace sre
```

Creating a pod in the `dev` namespace should return a `Forbidden` error because the `opssre` group is scoped to `sre`.

## Clean up

Remove the namespaces, RoleBindings, Roles, groups, users, and role assignments created by this configuration:

```console
terraform destroy
```
204 changes: 204 additions & 0 deletions quickstart/101-aks-entra-k8s-rbac/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
terraform {
required_version = ">= 1.6.0"

required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 3.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.30"
}
}
}

provider "azurerm" {
features {}
}

provider "azuread" {}

variable "resource_group_name" {
type = string
description = "Name of the resource group that contains the existing AKS cluster."
}

variable "aks_cluster_name" {
type = string
description = "Name of the existing AKS cluster with Microsoft Entra integration and Kubernetes RBAC enabled."
}

variable "appdev_user_principal_name" {
type = string
description = "User principal name for the application developer test user."
}

variable "opssre_user_principal_name" {
type = string
description = "User principal name for the SRE test user."
}

variable "temporary_password" {
type = string
description = "Temporary password used for the example Microsoft Entra users."
sensitive = true
}

data "azurerm_kubernetes_cluster" "aks" {
name = var.aks_cluster_name
resource_group_name = var.resource_group_name
}

provider "kubernetes" {
host = data.azurerm_kubernetes_cluster.aks.kube_admin_config[0].host
client_certificate = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config[0].client_certificate)
client_key = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config[0].client_key)
cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.aks.kube_admin_config[0].cluster_ca_certificate)
}

resource "azuread_group" "appdev" {
display_name = "appdev"
security_enabled = true
}

resource "azuread_group" "opssre" {
display_name = "opssre"
security_enabled = true
}

resource "azuread_user" "aksdev" {
user_principal_name = var.appdev_user_principal_name
display_name = "AKS Dev"
mail_nickname = "aksdev"
password = var.temporary_password
}

resource "azuread_user" "akssre" {
user_principal_name = var.opssre_user_principal_name
display_name = "AKS SRE"
mail_nickname = "akssre"
password = var.temporary_password
}

resource "azuread_group_member" "appdev_member" {
group_object_id = azuread_group.appdev.object_id
member_object_id = azuread_user.aksdev.object_id
}

resource "azuread_group_member" "opssre_member" {
group_object_id = azuread_group.opssre.object_id
member_object_id = azuread_user.akssre.object_id
}

resource "azurerm_role_assignment" "appdev_cluster_user" {
scope = data.azurerm_kubernetes_cluster.aks.id
role_definition_name = "Azure Kubernetes Service Cluster User Role"
principal_id = azuread_group.appdev.object_id
}

resource "azurerm_role_assignment" "opssre_cluster_user" {
scope = data.azurerm_kubernetes_cluster.aks.id
role_definition_name = "Azure Kubernetes Service Cluster User Role"
principal_id = azuread_group.opssre.object_id
}

resource "kubernetes_namespace" "dev" {
metadata {
name = "dev"
}
}

resource "kubernetes_namespace" "sre" {
metadata {
name = "sre"
}
}

resource "kubernetes_role" "dev_full_access" {
metadata {
name = "dev-user-full-access"
namespace = kubernetes_namespace.dev.metadata[0].name
}

rule {
api_groups = ["", "extensions", "apps"]
resources = ["*"]
verbs = ["*"]
}

rule {
api_groups = ["batch"]
resources = ["jobs", "cronjobs"]
verbs = ["*"]
}
}

resource "kubernetes_role" "sre_full_access" {
metadata {
name = "sre-user-full-access"
namespace = kubernetes_namespace.sre.metadata[0].name
}

rule {
api_groups = ["", "extensions", "apps"]
resources = ["*"]
verbs = ["*"]
}

rule {
api_groups = ["batch"]
resources = ["jobs", "cronjobs"]
verbs = ["*"]
}
}

resource "kubernetes_role_binding" "dev_user_access" {
metadata {
name = "dev-user-access"
namespace = kubernetes_namespace.dev.metadata[0].name
}

role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = kubernetes_role.dev_full_access.metadata[0].name
}

subject {
kind = "Group"
name = azuread_group.appdev.object_id
api_group = "rbac.authorization.k8s.io"
}
}

resource "kubernetes_role_binding" "sre_user_access" {
metadata {
name = "sre-user-access"
namespace = kubernetes_namespace.sre.metadata[0].name
}

role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "Role"
name = kubernetes_role.sre_full_access.metadata[0].name
}

subject {
kind = "Group"
name = azuread_group.opssre.object_id
api_group = "rbac.authorization.k8s.io"
}
}

output "appdev_group_object_id" {
value = azuread_group.appdev.object_id
}

output "opssre_group_object_id" {
value = azuread_group.opssre.object_id
}
Loading