This section contains specific details for this spec
This script demonstrates Azure Kubernetes Service (AKS) workload identity integration with Key Vault, allowing pods to authenticate to Azure services and access secrets without storing credentials.
- Azure CLI: ≥2.73.0 for Azure resource management
- kubectl: ≥1.28.0 for Kubernetes cluster management
- Resource Group: Container for all demo resources
- Managed Identity: User-assigned identity for workload identity
- AKS Cluster: With OIDC issuer and workload identity enabled
- Key Vault: Contains demo secrets, uses RBAC authorization
- Namespace: Dedicated namespace for demo resources
- Service Account: Annotated with managed identity information
- SecretProviderClass: Configures Key Vault secret mounting
- Demo Job: Validates workload identity by reading mounted secrets and completing successfully
- Managed identity trusts AKS OIDC issuer via federated credentials
- Demo job runs with service account linked to managed identity
- Job automatically gets Azure AD tokens and accesses Key Vault secrets
- Secrets are mounted at
/mnt/secrets/
and read by the job to prove functionality
- Single file: All functionality in one executable Python script
- uv script format: Use uv script header with dependencies
- Simple execution:
uv run aks-workload-identity-demo.py
- No arguments required: Should work with sensible defaults
- Resource Group: Optional (
--resource-group
), auto-generates unique name if not provided - Location: Azure region (
--location
, default:eastus2
) - Size: Azure VM size for AKS nodes (
--size
, default:Standard_D4ds_v5
)
# Basic deployment with auto-generated resource group
uv run aks-workload-identity-demo.py
# All custom parameters
uv run aks-workload-identity-demo.py --resource-group "my-demo" --location "westus2" --size "Standard_D2s_v3"
Mixed content - core patterns are reusable, specific examples are demo-specific
- Command Transparency: All Azure CLI and kubectl commands MUST be displayed in syntax-highlighted panels before execution
- Educational Purpose: Users must see exactly what commands accomplish each step of workload identity setup
- Copy-able Commands: Format commands so users can copy and run them manually if desired
- Show command in Rich Syntax panel with bash highlighting
- Include brief explanation of what the command accomplishes
- Execute command after display
- Group related commands in logical panels
╭─ Creating Resource Group ─╮
│ az group create \ │
│ --name my-rg \ │
│ --location eastus │
╰───────────────────────────╯
Creating resource group...
✅ Resource group created successfully
Reusable pattern - these principles apply to any cloud demo
- Unique identifiers: All resources include auto-generated unique suffix
- Global uniqueness: Prevents conflicts when multiple users run demo
- Consistent patterns: Predictable naming for operational clarity
Demo-specific - replace with target platform resources when adapting
Resource Type | Naming Pattern | Purpose |
---|---|---|
Resource Group | aks-wi-demo-{unique-id} |
Contains all demo resources |
Managed Identity | wi-identity-{unique-id} |
Provides workload identity |
AKS Cluster | aks-{unique-id} |
Kubernetes cluster with workload identity |
Key Vault | kv-{unique-id} |
Stores demo secrets |
Demo-specific - replace with target platform permissions when adapting The managed identity needs these role assignments:
- Key Vault Secrets User (on Key Vault): Read secrets via CSI driver
- Reader (on AKS node resource group): Enable CSI driver operations
Example Azure CLI Commands:
# Assign Reader role to managed identity on AKS node resource group
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
az role assignment create \
--role "Reader" \
--assignee $CLIENT_ID \
--scope "/subscriptions/$SUBSCRIPTION_ID/resourceGroups/$NODE_RESOURCE_GROUP"
# Create federated identity credential with required audiences parameter
az identity federated-credential create \
--name kubernetes-federated-credential \
--identity-name $MANAGED_IDENTITY_NAME \
--resource-group $RESOURCE_GROUP \
--issuer $OIDC_ISSUER_URL \
--subject system:serviceaccount:$NAMESPACE:$SERVICE_ACCOUNT_NAME \
--audiences api://AzureADTokenExchange
Reusable pattern - these principles apply to any cloud demo
- Single resource group: All resources contained for easy cleanup
- Consistent tagging: All resources tagged for identification
- Least privilege: RBAC assignments use minimal required permissions
Critical implementation pattern - prevents common deployment bugs
When deploying Kubernetes resources, several values must be resolved dynamically at runtime:
Value Type | Source | Template Placeholder | Resolution Method |
---|---|---|---|
Tenant ID | Current Azure context | {tenant_id} |
az account show --query tenantId -o tsv |
Client ID | Created managed identity | {client_id} |
From managed identity creation result |
OIDC Issuer URL | AKS cluster configuration | {oidc_issuer_url} |
az aks show --query oidcIssuerProfile.issuerUrl -o tsv |
Critical Implementation Constraints:
- No shell substitution in YAML: Kubernetes YAML files cannot execute shell commands like
$(command)
- Variable resolution required: All dynamic values must be resolved in the script before resource creation
- Template processing: Use proper string formatting/templating, not shell command substitution within YAML
- Validation: Always verify resolved values are non-empty before using in resource definitions
Example Implementation Pattern:
# ✅ CORRECT: Resolve values in script
tenant_id = subprocess.run("az account show --query tenantId -o tsv", ...).stdout.strip()
yaml_content = f"tenantId: {tenant_id}"
# ❌ INCORRECT: Shell substitution in YAML
yaml_content = 'tenantId: "$(az account show --query tenantId -o tsv)"' # This won't work!
The script demonstrates workload identity by:
- Creating Azure infrastructure (resource group, managed identity, AKS cluster, Key Vault)
- Configuring workload identity trust between AKS and Azure AD
- Deploying a demo pod that accesses Key Vault secrets without stored credentials
- Validating the integration works correctly
-
Infrastructure Setup
- Create resource group with appropriate tags
- Create user-assigned managed identity
- Create Key Vault with RBAC authorization and demo secrets
- Assign required RBAC roles to managed identity
-
AKS Cluster Configuration
- Create AKS cluster with workload identity and OIDC issuer enabled
- Install Key Vault CSI driver (via AKS addon)
- Configure federated identity credentials for trust relationship
-
Application Deployment
- Create namespace with workload identity labels
- Deploy service account with workload identity annotation
- Create SecretProviderClass for Key Vault integration
- Deploy demo validation job
az aks create \
--resource-group $RESOURCE_GROUP \
--name $CLUSTER_NAME \
--node-vm-size $SIZE \
--enable-workload-identity \
--enable-oidc-issuer \
--enable-addons azure-keyvault-secrets-provider
- Managed identity must exist before RBAC assignments
- OIDC issuer URL must be available before federated credentials
- Federated credentials must exist before Kubernetes workload identity resources
- Key Vault RBAC must be assigned before SecretProviderClass deployment
- Single execution success: Must complete successfully on first run with defaults
- No manual intervention: Complete automation from single command
- No credential storage: Must not store or log authentication credentials
- Comprehensive error handling: All failures must include actionable guidance
This section contains implementation patterns and standards that remain consistent across different specs.
The script serves as an educational step-by-step guide, displaying each command needed to configure the integration, allowing users to learn the exact process and commands required for the demonstrated functionality.
- Python: ≥3.12 for modern language features and library support
- Single file: All functionality in one executable Python script for easy sharing and execution
- uv script format: Use uv script header with dependencies for modern Python dependency management
# /// script
# dependencies = [
# "typer>=0.16.0", # CLI framework with automatic help generation
# "rich>=14.0.0", # Rich console output and formatting
# "pydantic>=2.11.7", # Data validation and settings management
# "pyyaml>=6.0", # YAML processing for resource definitions
# ]
# ///
- Simple execution:
uv run script-name.py
- No setup required: Dependencies automatically managed by uv
- Cross-platform: Works consistently across development environments
The script uses Rich library with these implementation rules:
console = Console(width=120)
Content Type | Rich Component | Required Parameters |
---|---|---|
Commands | Panel(Syntax()) |
theme="monokai" , line_numbers=False |
Configuration resources | Panel(Syntax()) |
theme="monokai" , line_numbers=True |
Command output - JSON | Panel(Syntax()) |
language="json" , theme="monokai" , line_numbers=False |
Command output - Plain text | Panel() |
Plain text content, no syntax highlighting |
Command output - Logs | Panel(Syntax()) |
language="bash" , theme="monokai" , line_numbers=False |
Resource details | Table() |
box=ROUNDED |
Status messages | Status() |
console=console |
Error output | Panel() |
border_style="error" |
Component | Parameters | Values |
---|---|---|
Panel() |
width |
120 |
Panel() |
expand |
False |
Panel() |
padding |
(1, 2) |
Syntax() |
language |
"bash" for commands, "yaml" for resources |
Table() |
box |
ROUNDED |
All components | title |
Max 60 chars, include emojis |
Content Type | Border Style | Title Format | Theme Color | Purpose |
---|---|---|---|---|
Primary infrastructure commands | Distinct color (e.g. "blue" ) |
🔧 {title} |
Tool-specific theme | Main provisioning operations |
Secondary/orchestration commands | Different color (e.g. "green" ) |
🔧 {title} |
Tool-specific theme | Configuration and deployment |
Configuration files | "cyan" |
📄 {resource_type} |
N/A | YAML, JSON, and other configs |
Command output | "dim" |
💻 {description} |
N/A | Display command execution results |
Data tables | "magenta" |
Resource name | N/A | Resource summaries and lists |
Error messages | "red" |
❌ Error |
"error" |
Failure conditions |
Success messages | "green" |
Status message | "success" |
Completion confirmations |
Design Principles:
- Different command categories should use distinct, consistent colors for logical grouping
- Related commands should share visual styling to indicate their relationship
- Color choices should enhance educational clarity and command comprehension
- Tool-specific themes should be defined based on the actual tools used in implementation
Text Type | Format | Example |
---|---|---|
Success | console.print("✓ {message}") |
console.print("✓ Created successfully") |
Error | [error]✗ {message}[/error] |
[error]✗ Failed to create[/error] |
Warning | [warning]⚠️ {message}[/warning] |
[warning]⚠️ Operation may take time[/warning] |
Info | [info]{message}[/info] |
[info]Using resource group: my-rg[/info] |
Long commands | Readable formatting | Commands should be formatted for visual clarity and easy comprehension |
Command structure | Educational clarity | Users should quickly understand command structure and be able to copy portions if needed |
Shell safety | Secure execution | Command arguments must be properly escaped for shell execution |
When command output is displayed, it must follow these visual presentation standards:
JSON Output Example:
# Format JSON output with syntax highlighting
json_output = json.dumps(result, indent=2)
console.print(Panel(
Syntax(json_output, "json", theme="monokai", line_numbers=False),
title="💻 Resource Creation Result",
border_style="dim",
width=120,
expand=False,
padding=(1, 2)
))
Plain Text Output Example:
# Format plain text output
console.print(Panel(
output_text,
title="💻 Command Output",
border_style="dim",
width=120,
expand=False,
padding=(1, 2)
))
Log Output Example:
# Format log output with bash syntax highlighting
console.print(Panel(
Syntax(log_output, "bash", theme="monokai", line_numbers=False),
title="💻 Application Logs",
border_style="dim",
width=120,
expand=False,
padding=(1, 2)
))
Theme({
"success": "green", # Dimmed success (not bold)
"error": "bold red",
"warning": "bold yellow",
"info": "cyan", # Information messages
"command1": "bold blue", # Primary commands
"command2": "bold green", # Secondary commands
"dim": "dim white", # Subtle status messages
...
})
- Rich
Syntax
class does NOT supportwrap
parameter - Commands must be displayed in a visually clear, readable format
- Long commands should be formatted to avoid visual clutter while maintaining comprehension
- All panels MUST use
expand=False
to prevent full-width expansion - Command arguments must be properly escaped for secure shell execution
- Use
tempfile.NamedTemporaryFile()
for kubectl apply operations
The standard execution flow should follow this visual hierarchy:
[Command Panel] → Execution Message → [Output Panel] → Completion Message
- Format:
console.print("Executing: {description}...")
- Style: Plain text, no Rich markup
- Purpose: Indicate command is running
- Example:
console.print("Executing: Creating Resource Group...")
- Format:
console.print("✓ {description} completed successfully")
- Style: Plain text with emoji, no Rich styling
- Purpose: Confirm operation success
- Example:
console.print("✓ Creating Resource Group completed successfully")
- Format:
console.print(f"[info]{message}[/info]")
- Style: Cyan text for configuration info
- Purpose: Display configuration values
- Example:
console.print(f"[info]Using resource group: {config.resource_group}[/info]")
- Format:
console.print("✓ {tool} is {status}")
- Style: Plain text, consistent with completion messages
- Purpose: Confirm tool availability
- Example:
console.print("✓ Azure CLI is installed")
- One blank line before command panels
- No blank line between command panel and "Executing:" message
- No blank line between output panel and completion message
- One blank line after completion message before next operation
<previous operation completion>
╭─ 🔧 Creating Resource Group ─╮
│ az group create ... │
╰───────────────────────────────╯
Executing: Creating Resource Group...
╭─ 💻 Command Output ─╮
│ { "name": "rg" } │
╰─────────────────────╯
✓ Creating Resource Group completed successfully
╭─ 🔧 Next Operation ─╮
- Short commands (≤60 chars): Single line, no wrapping
- Medium commands (61-100 chars): Single line with careful formatting
- Long commands (>100 chars): Multi-line with logical breaks
# For commands with multiple parameters
command = f"""az group create \\
--name {resource_group} \\
--location {location} \\
--tags purpose=demo component=workload-identity"""
- Break after backslash for command continuation
- Align parameters under the command for readability
- Group related parameters on same line when possible
- Indent continuation lines by 2 spaces
- Maximum useful width: 80 characters for command content
- Panel padding: Account for 4 characters (2 chars padding each side)
- Total panel width: 120 characters (content + borders + padding)
Panel Type | Width | Expand | Padding | Purpose |
---|---|---|---|---|
Command panels | 120 |
False |
(1, 2) |
Command display |
Output panels | 120 |
False |
(1, 2) |
Command results |
Config panels | 120 |
False |
(1, 2) |
YAML/JSON resources |
Info panels | 120 |
False |
(1, 2) |
Welcome, summary |
Error panels | 120 |
False |
(1, 2) |
Error messages |
- Long URLs: Break at natural points (after protocol, domain)
- Long IDs: Allow natural wrapping within panel constraints
- JSON output: Use
indent=2
for readability within width limits - Command parameters: Use multi-line formatting with proper indentation
- All panels use fixed
width=120
for consistency - Content should be formatted to fit within this constraint
- Never use
expand=True
to prevent terminal width variations
Context | Spacing | Implementation |
---|---|---|
Between major operations | 1 blank line | console.print() |
Panel to execution message | 0 blank lines | Immediate sequence |
Output panel to completion | 0 blank lines | Immediate sequence |
Completion to next operation | 1 blank line | console.print() |
Section headers | 1 blank line before | For visual grouping |
- Panel titles: Left-aligned with emoji prefix
- Panel content: Consistent padding
(1, 2)
- Command parameters: Aligned under command name
- Table content: Column alignment per Rich table defaults
- Primary: Panel-displayed commands and outputs
- Secondary: Execution status messages (plain text)
- Tertiary: Completion confirmations (plain text with emoji)
- Supporting: Information and configuration messages
This section contains specific details for this spec
The script demonstrates workload identity by deploying a Kubernetes Job that accesses Key Vault secrets and completes successfully. This validation is integrated into the normal script execution.
-
Deploy Demo Job
- Create Kubernetes resources (namespace, service account, SecretProviderClass)
- Deploy validation job using
mcr.microsoft.com/azure-cli
image - Job mounts Key Vault secrets at
/mnt/secrets/
and reads them
-
Demonstrate Success
- Job executes commands to read and display all mounted secrets
- Job outputs secret values to prove workload identity is working
- Job completes successfully without any stored credentials
The script proves workload identity works by showing:
- ✅ Validation job completes successfully without stored credentials
- ✅ Secrets are mounted from Key Vault at
/mnt/secrets/
- ✅ Job reads and displays all secret values using workload identity
- ✅ No connection strings or service principal credentials needed
- ✅ Azure workload identity environment variables are present
Critical implementation pattern - prevents infinite waiting loops
To properly check Kubernetes job completion status:
Status Check | Command | Success Value | Purpose |
---|---|---|---|
Job Succeeded | kubectl get job <name> -o jsonpath='{.status.succeeded}' |
"1" |
Confirms job completed successfully |
Job Failed | kubectl get job <name> -o jsonpath='{.status.failed}' |
"1" |
Detects job failure |
Pod Status | kubectl get pods -l job-name=<name> -o jsonpath='{.items[0].status.phase}' |
"Succeeded" |
Alternative status check |
Critical Implementation Constraints:
- Use status fields: Check
.status.succeeded
and.status.failed
, not.status.conditions
- Handle empty responses: Job status fields may be empty before job starts or completes
- Implement timeouts: Always include maximum wait time to prevent infinite loops
- Proper error handling: Use try/catch blocks as job may not exist initially
Example Implementation Pattern:
# ✅ CORRECT: Check job status fields
for i in range(max_attempts):
try:
result = subprocess.run(
f"kubectl get job {job_name} -o jsonpath='{{.status.succeeded}}'",
capture_output=True, text=True, check=True
)
if result.stdout.strip() == "1":
break
except subprocess.CalledProcessError:
pass # Job may not exist yet
time.sleep(wait_interval)
# ❌ INCORRECT: Using conditions array
result = subprocess.run(
f"kubectl get job {job_name} -o jsonpath='{{.status.conditions[0].type}}'",
...
)
- Script executes successfully with
uv run aks-workload-identity-demo.py
- Azure infrastructure created and configured correctly
- AKS cluster deployed with workload identity enabled
- Demo job completes successfully and displays Key Vault secrets
- Validation job proves workload identity functioning without stored credentials
Demo-specific - replace with target platform documentation when adapting