Comprehensive Claude Code guidance system with: - 5 agents: tdd-guardian, code-reviewer, security-scanner, refactor-scan, dependency-audit - 18 skills covering languages (Python, TypeScript, Rust, Go, Java, C#), infrastructure (AWS, Azure, GCP, Terraform, Ansible, Docker/K8s, Database, CI/CD), testing (TDD, UI, Browser), and patterns (Monorepo, API Design, Observability) - 3 hooks: secret detection, auto-formatting, TDD git pre-commit - Strict TDD enforcement with 80%+ coverage requirements - Multi-model strategy: Opus for planning, Sonnet for execution (opusplan) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
443 lines
14 KiB
Markdown
443 lines
14 KiB
Markdown
---
|
|
name: azure-services
|
|
description: Azure service patterns, RBAC best practices, and common architectures. Use when designing or implementing Azure infrastructure.
|
|
---
|
|
|
|
# Azure Services Skill
|
|
|
|
## Common Architecture Patterns
|
|
|
|
### Web Application (App Service + Azure SQL)
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ VNet │
|
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
│ │ Public Subnet ││
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ││
|
|
│ │ │ App Gateway │ │ NAT GW │ ││
|
|
│ │ └──────┬──────┘ └──────┬──────┘ ││
|
|
│ └─────────┼───────────────────────────────────┼───────────┘│
|
|
│ │ │ │
|
|
│ ┌─────────┼───────────────────────────────────┼───────────┐│
|
|
│ │ │ Private Subnet │ ││
|
|
│ │ ┌──────▼──────┐ ┌───────▼─────┐ ││
|
|
│ │ │ App Service │ │ Azure SQL │ ││
|
|
│ │ │ (Web App) │───────────────────▶│ Database │ ││
|
|
│ │ └─────────────┘ └─────────────┘ ││
|
|
│ └─────────────────────────────────────────────────────────┘│
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Serverless (Azure Functions + API Management)
|
|
```
|
|
┌────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
│ Front │────▶│ APIM │────▶│ Functions │
|
|
│ Door │ │ │ └──────┬──────┘
|
|
└────────────┘ └─────────────┘ │
|
|
┌──────────────────────────┼──────────────┐
|
|
│ │ │
|
|
┌──────▼─────┐ ┌─────────┐ ┌────▼────┐
|
|
│ Cosmos DB │ │ Blob │ │ Key │
|
|
└────────────┘ │ Storage │ │ Vault │
|
|
└─────────┘ └─────────┘
|
|
```
|
|
|
|
## RBAC Best Practices
|
|
|
|
### Custom Role Definition
|
|
```json
|
|
{
|
|
"Name": "App Data Reader",
|
|
"Description": "Read access to application data in storage",
|
|
"Actions": [
|
|
"Microsoft.Storage/storageAccounts/blobServices/containers/read",
|
|
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"
|
|
],
|
|
"NotActions": [],
|
|
"DataActions": [
|
|
"Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read"
|
|
],
|
|
"NotDataActions": [],
|
|
"AssignableScopes": [
|
|
"/subscriptions/{subscription-id}/resourceGroups/{resource-group}"
|
|
]
|
|
}
|
|
```
|
|
|
|
### Managed Identity Usage
|
|
```python
|
|
# Python - azure-identity
|
|
from azure.identity import DefaultAzureCredential
|
|
from azure.keyvault.secrets import SecretClient
|
|
from azure.storage.blob import BlobServiceClient
|
|
|
|
# Uses managed identity when deployed to Azure
|
|
credential = DefaultAzureCredential()
|
|
|
|
# Key Vault access
|
|
secret_client = SecretClient(
|
|
vault_url="https://my-vault.vault.azure.net/",
|
|
credential=credential
|
|
)
|
|
secret = secret_client.get_secret("database-password")
|
|
|
|
# Blob Storage access
|
|
blob_service = BlobServiceClient(
|
|
account_url="https://mystorageaccount.blob.core.windows.net/",
|
|
credential=credential
|
|
)
|
|
```
|
|
|
|
```typescript
|
|
// TypeScript - @azure/identity
|
|
import { DefaultAzureCredential } from "@azure/identity";
|
|
import { SecretClient } from "@azure/keyvault-secrets";
|
|
import { BlobServiceClient } from "@azure/storage-blob";
|
|
|
|
const credential = new DefaultAzureCredential();
|
|
|
|
// Key Vault access
|
|
const secretClient = new SecretClient(
|
|
"https://my-vault.vault.azure.net/",
|
|
credential
|
|
);
|
|
const secret = await secretClient.getSecret("database-password");
|
|
|
|
// Blob Storage access
|
|
const blobService = new BlobServiceClient(
|
|
"https://mystorageaccount.blob.core.windows.net/",
|
|
credential
|
|
);
|
|
```
|
|
|
|
## Key Vault Patterns
|
|
|
|
### Secrets Management
|
|
```python
|
|
from azure.identity import DefaultAzureCredential
|
|
from azure.keyvault.secrets import SecretClient
|
|
|
|
def get_secret(vault_url: str, secret_name: str) -> str:
|
|
"""Retrieve secret from Key Vault using managed identity."""
|
|
credential = DefaultAzureCredential()
|
|
client = SecretClient(vault_url=vault_url, credential=credential)
|
|
return client.get_secret(secret_name).value
|
|
|
|
# Usage
|
|
db_password = get_secret(
|
|
"https://my-vault.vault.azure.net/",
|
|
"database-password"
|
|
)
|
|
```
|
|
|
|
### App Service with Key Vault References
|
|
```json
|
|
// App Service configuration
|
|
{
|
|
"name": "DatabasePassword",
|
|
"value": "@Microsoft.KeyVault(SecretUri=https://my-vault.vault.azure.net/secrets/db-password/)",
|
|
"slotSetting": false
|
|
}
|
|
```
|
|
|
|
## Blob Storage Patterns
|
|
|
|
### SAS Token Generation
|
|
```python
|
|
from datetime import datetime, timedelta
|
|
from azure.storage.blob import (
|
|
BlobServiceClient,
|
|
generate_blob_sas,
|
|
BlobSasPermissions,
|
|
)
|
|
|
|
def generate_read_sas(
|
|
account_name: str,
|
|
account_key: str,
|
|
container: str,
|
|
blob_name: str,
|
|
expiry_hours: int = 1
|
|
) -> str:
|
|
"""Generate a read-only SAS URL for a blob."""
|
|
sas_token = generate_blob_sas(
|
|
account_name=account_name,
|
|
container_name=container,
|
|
blob_name=blob_name,
|
|
account_key=account_key,
|
|
permission=BlobSasPermissions(read=True),
|
|
expiry=datetime.utcnow() + timedelta(hours=expiry_hours),
|
|
)
|
|
|
|
return f"https://{account_name}.blob.core.windows.net/{container}/{blob_name}?{sas_token}"
|
|
```
|
|
|
|
### User Delegation SAS (More Secure)
|
|
```python
|
|
from azure.identity import DefaultAzureCredential
|
|
from azure.storage.blob import BlobServiceClient, UserDelegationKey
|
|
|
|
def generate_user_delegation_sas(
|
|
account_url: str,
|
|
container: str,
|
|
blob_name: str,
|
|
) -> str:
|
|
"""Generate SAS using user delegation key (no storage key needed)."""
|
|
credential = DefaultAzureCredential()
|
|
blob_service = BlobServiceClient(account_url, credential=credential)
|
|
|
|
# Get user delegation key
|
|
delegation_key = blob_service.get_user_delegation_key(
|
|
key_start_time=datetime.utcnow(),
|
|
key_expiry_time=datetime.utcnow() + timedelta(hours=1)
|
|
)
|
|
|
|
sas_token = generate_blob_sas(
|
|
account_name=blob_service.account_name,
|
|
container_name=container,
|
|
blob_name=blob_name,
|
|
user_delegation_key=delegation_key,
|
|
permission=BlobSasPermissions(read=True),
|
|
expiry=datetime.utcnow() + timedelta(hours=1),
|
|
)
|
|
|
|
return f"{account_url}/{container}/{blob_name}?{sas_token}"
|
|
```
|
|
|
|
## Cosmos DB Patterns
|
|
|
|
### Async Client Usage
|
|
```python
|
|
from azure.cosmos.aio import CosmosClient
|
|
from azure.identity.aio import DefaultAzureCredential
|
|
|
|
async def get_cosmos_client() -> CosmosClient:
|
|
"""Create async Cosmos client with managed identity."""
|
|
credential = DefaultAzureCredential()
|
|
return CosmosClient(
|
|
url="https://my-cosmos.documents.azure.com:443/",
|
|
credential=credential
|
|
)
|
|
|
|
async def query_items(container_name: str, query: str) -> list:
|
|
"""Query items from Cosmos DB container."""
|
|
async with await get_cosmos_client() as client:
|
|
database = client.get_database_client("my-database")
|
|
container = database.get_container_client(container_name)
|
|
|
|
items = []
|
|
async for item in container.query_items(
|
|
query=query,
|
|
enable_cross_partition_query=True
|
|
):
|
|
items.append(item)
|
|
|
|
return items
|
|
```
|
|
|
|
### Partition Key Design
|
|
```python
|
|
# Good partition key choices:
|
|
# - tenant_id for multi-tenant apps
|
|
# - user_id for user-specific data
|
|
# - category for catalog data
|
|
|
|
# Document structure
|
|
{
|
|
"id": "order-12345",
|
|
"partitionKey": "customer-789", # Use customer ID for orders
|
|
"orderDate": "2024-01-15",
|
|
"items": [...],
|
|
"total": 150.00
|
|
}
|
|
```
|
|
|
|
## Azure Functions Patterns
|
|
|
|
### HTTP Trigger with Input Validation
|
|
```python
|
|
import azure.functions as func
|
|
import logging
|
|
from pydantic import BaseModel, ValidationError
|
|
|
|
class CreateOrderRequest(BaseModel):
|
|
customer_id: str
|
|
items: list[dict]
|
|
|
|
app = func.FunctionApp()
|
|
|
|
@app.route(route="orders", methods=["POST"])
|
|
async def create_order(req: func.HttpRequest) -> func.HttpResponse:
|
|
"""Create a new order with validation."""
|
|
try:
|
|
body = req.get_json()
|
|
request = CreateOrderRequest(**body)
|
|
|
|
# Process order...
|
|
result = await process_order(request)
|
|
|
|
return func.HttpResponse(
|
|
body=result.model_dump_json(),
|
|
status_code=201,
|
|
mimetype="application/json"
|
|
)
|
|
|
|
except ValidationError as e:
|
|
return func.HttpResponse(
|
|
body=e.json(),
|
|
status_code=400,
|
|
mimetype="application/json"
|
|
)
|
|
except Exception as e:
|
|
logging.exception("Error processing order")
|
|
return func.HttpResponse(
|
|
body='{"error": "Internal server error"}',
|
|
status_code=500,
|
|
mimetype="application/json"
|
|
)
|
|
```
|
|
|
|
### Durable Functions Orchestration
|
|
```python
|
|
import azure.functions as func
|
|
import azure.durable_functions as df
|
|
|
|
app = func.FunctionApp()
|
|
|
|
@app.orchestration_trigger(context_name="context")
|
|
def order_orchestrator(context: df.DurableOrchestrationContext):
|
|
"""Orchestrate multi-step order processing."""
|
|
order = context.get_input()
|
|
|
|
# Step 1: Validate inventory
|
|
inventory_result = yield context.call_activity(
|
|
"validate_inventory", order["items"]
|
|
)
|
|
|
|
if not inventory_result["available"]:
|
|
return {"status": "failed", "reason": "insufficient_inventory"}
|
|
|
|
# Step 2: Process payment
|
|
payment_result = yield context.call_activity(
|
|
"process_payment", order["payment"]
|
|
)
|
|
|
|
if not payment_result["success"]:
|
|
return {"status": "failed", "reason": "payment_failed"}
|
|
|
|
# Step 3: Create shipment
|
|
shipment = yield context.call_activity(
|
|
"create_shipment", order
|
|
)
|
|
|
|
return {"status": "completed", "shipment_id": shipment["id"]}
|
|
```
|
|
|
|
## Application Insights
|
|
|
|
### Structured Logging
|
|
```python
|
|
import logging
|
|
from opencensus.ext.azure.log_exporter import AzureLogHandler
|
|
|
|
# Configure logging with Application Insights
|
|
logger = logging.getLogger(__name__)
|
|
logger.addHandler(AzureLogHandler(
|
|
connection_string="InstrumentationKey=xxx;IngestionEndpoint=xxx"
|
|
))
|
|
|
|
# Log with custom dimensions
|
|
logger.info(
|
|
"Order processed",
|
|
extra={
|
|
"custom_dimensions": {
|
|
"order_id": "12345",
|
|
"customer_id": "cust-789",
|
|
"total": 150.00
|
|
}
|
|
}
|
|
)
|
|
```
|
|
|
|
### Custom Metrics
|
|
```python
|
|
from opencensus.ext.azure import metrics_exporter
|
|
from opencensus.stats import aggregation, measure, stats, view
|
|
|
|
# Create measure
|
|
orders_measure = measure.MeasureInt(
|
|
"orders_processed",
|
|
"Number of orders processed",
|
|
"orders"
|
|
)
|
|
|
|
# Create view
|
|
orders_view = view.View(
|
|
"orders_processed_total",
|
|
"Total orders processed",
|
|
[],
|
|
orders_measure,
|
|
aggregation.CountAggregation()
|
|
)
|
|
|
|
# Register and export
|
|
view_manager = stats.stats.view_manager
|
|
view_manager.register_view(orders_view)
|
|
|
|
exporter = metrics_exporter.new_metrics_exporter(
|
|
connection_string="InstrumentationKey=xxx"
|
|
)
|
|
view_manager.register_exporter(exporter)
|
|
|
|
# Record metric
|
|
mmap = stats.stats.stats_recorder.new_measurement_map()
|
|
mmap.measure_int_put(orders_measure, 1)
|
|
mmap.record()
|
|
```
|
|
|
|
## CLI Commands
|
|
|
|
```bash
|
|
# Authentication
|
|
az login
|
|
az account set --subscription "My Subscription"
|
|
az account show
|
|
|
|
# Resource Groups
|
|
az group list --output table
|
|
az group create --name my-rg --location uksouth
|
|
|
|
# Key Vault
|
|
az keyvault secret show --vault-name my-vault --name my-secret
|
|
az keyvault secret set --vault-name my-vault --name my-secret --value "secret-value"
|
|
|
|
# Storage
|
|
az storage blob list --account-name mystorageaccount --container-name mycontainer
|
|
az storage blob upload --account-name mystorageaccount --container-name mycontainer --file local.txt --name remote.txt
|
|
|
|
# App Service
|
|
az webapp list --output table
|
|
az webapp restart --name my-app --resource-group my-rg
|
|
az webapp log tail --name my-app --resource-group my-rg
|
|
|
|
# Functions
|
|
az functionapp list --output table
|
|
az functionapp restart --name my-func --resource-group my-rg
|
|
|
|
# Cosmos DB
|
|
az cosmosdb list --output table
|
|
az cosmosdb sql database list --account-name my-cosmos --resource-group my-rg
|
|
```
|
|
|
|
## Security Checklist
|
|
|
|
- [ ] Use Managed Identities instead of connection strings
|
|
- [ ] Store secrets in Key Vault, not app settings
|
|
- [ ] Enable Azure Defender for all resources
|
|
- [ ] Use Private Endpoints for PaaS services
|
|
- [ ] Enable diagnostic logging to Log Analytics
|
|
- [ ] Configure Network Security Groups
|
|
- [ ] Use User Delegation SAS instead of account keys
|
|
- [ ] Enable soft delete on Key Vault and Storage
|
|
- [ ] Configure Azure Policy for compliance
|
|
- [ ] Enable Microsoft Defender for Cloud
|