Protecting Your Prize-Winning Cyber-Carrots: A Defense-in-Depth Guide to Azure AKS with Crossplane v2
Inspired by Google’s Defense-in-Depth Security module – because even Google knows you need more than one fence to keep the rabbits out!
🥕 Introduction: The Garden of Cloud Resources
Picture this: You’ve spent months cultivating the most beautiful cyber-carrots (your precious application data) in your Azure Kubernetes Service garden. They’re orange, they’re crunchy, and they’re absolutely irresistible to cyber-rabbits (malicious actors) who would love nothing more than to munch on your sensitive customer data.
Now, you could just plant your carrots and hope for the best. But that’s like leaving a “FREE CARROTS” sign on your garden gate. Instead, we’re going to build a proper defense-in-depth security strategy using Crossplane v2 – think of it as building the Fort Knox of vegetable gardens.
In this article, we’ll deploy a production-ready Azure AKS cluster with multiple layers of security controls. Because in cybersecurity, as in gardening, one fence is never enough when rabbits are this motivated.
🐰 Understanding Defense-in-Depth (Or: Why Cyber-Rabbits Are Persistent)
Before we dive into code, let’s understand what defense-in-depth actually means. According to Google’s Cloud Security course, it’s like protecting a community garden with multiple security layers:
- Identity Controls – Who gets a key to the garden gate?
- Protective Controls – The fence around your carrots
- Network Controls – Which paths lead to which carrot beds?
- Detective Controls – Cameras to spot sneaky rabbits
- Responsive Controls – Automatic sprinklers when intruders detected
- Recovery Controls – Replanting carrots after an attack
Each layer works together. If a particularly clever cyber-rabbit gets past the fence, the cameras spot it, the sprinklers activate, and you can restore from your backup seed vault.
🎯 What We’re Building
We’re going to deploy a secure Azure AKS cluster with Crossplane v2 that includes:
- Identity Layer: Azure AD integration and RBAC
- Protective Layer: Network policies and pod security standards
- Network Layer: Private clusters and firewall rules
- Detective Layer: Azure Monitor and Container Insights
- Responsive Layer: Azure Policy auto-remediation
- Recovery Layer: Backup and disaster recovery
All of this will be declared as code, version-controlled, and reproducible. Because infrastructure-as-code is like having a detailed garden plan – you can rebuild your garden exactly the same way every time.
🛠️ Prerequisites
Before we start planting our cyber-carrots, make sure you have:
# Crossplane v2 (the important bit!)
kubectl crossplane --version
# Should show v2.x.x
# Azure CLI
az --version
# kubectl
kubectl version --client
# Helm (for Crossplane installation)
helm version
Full source code available at: github.com/software-journey/defense-in-depth-of-cyber-carrots
📦 Layer 1: Identity Controls (Garden Gate Keys)
First, let’s set up who can access our garden. We’ll create a Crossplane composition that includes Azure AD integration:
# compositions/aks-with-identity.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: secure-aks-cluster
labels:
provider: azure
security-level: defense-in-depth
spec:
compositeTypeRef:
apiVersion: platform.example.com/v1alpha1
kind: SecureAKSCluster
mode: Pipeline
pipeline:
- step: render-aks-cluster
functionRef:
name: function-patch-and-transform
input:
apiVersion: pt.fn.crossplane.io/v1beta1
kind: Resources
resources:
# Identity Control: Azure AD Integration
- name: aks-cluster
base:
apiVersion: containerservice.azure.upbound.io/v1beta1
kind: KubernetesCluster
spec:
forProvider:
# Enable Azure AD integration - our first layer of defense!
azureActiveDirectoryRoleBasedAccessControl:
- managed: true
azureRbacEnabled: true
adminGroupObjectIds:
- $(admin_group_id)
# Enable managed identity (like giving the cluster its own key)
identity:
- type: SystemAssigned
resourceGroupName: $(resource_group)
location: westeurope
dnsPrefix: secure-carrots
defaultNodePool:
- name: system
nodeCount: 3
vmSize: Standard_D2s_v3
# Enable auto-scaling (garden grows with your carrots!)
enableAutoScaling: true
minCount: 3
maxCount: 6
networkProfile:
- networkPlugin: azure
networkPolicy: calico # More on this in Layer 3!
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.resourceGroup
toFieldPath: spec.forProvider.resourceGroupName
- type: FromCompositeFieldPath
fromFieldPath: spec.adminGroupId
toFieldPath: spec.forProvider.azureActiveDirectoryRoleBasedAccessControl[0].adminGroupObjectIds[0]
Why this matters: Without Azure AD integration, anyone with cluster credentials can waltz into your garden. With it, you control exactly who gets which keys – some people can only water the carrots, others can harvest them.
🔒 Layer 2: Protective Controls (The Fence)
Now let’s build our fence – pod security standards that prevent mischievous containers from doing things they shouldn’t:
# compositions/pod-security-standards.yaml
- name: pod-security-policy
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: carrot-storage
labels:
# Enforce restricted pod security standard
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
- name: network-policy-default-deny
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: carrot-storage
spec:
# Default: Trust no one! 🥕
podSelector: {}
policyTypes:
- Ingress
- Egress
This is like putting up a “NO UNAUTHORIZED VEGETABLES” sign. Pods can’t run as root, can’t mount sensitive host paths, and can’t do other risky things that cyber-rabbits love.
🌐 Layer 3: Network Controls (Controlled Pathways)
Let’s ensure only the right paths lead to our carrots:
# compositions/network-security.yaml
- name: allow-frontend-to-backend
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-carrot-api
namespace: carrot-storage
spec:
podSelector:
matchLabels:
app: carrot-api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: frontend
ports:
- protocol: TCP
port: 8080
# Also create an Azure Firewall for cluster egress
- name: azure-firewall
base:
apiVersion: network.azure.upbound.io/v1beta1
kind: Firewall
spec:
forProvider:
location: westeurope
resourceGroupName: $(resource_group)
sku:
- name: AZFW_VNet
tier: Standard
ipConfiguration:
- name: configuration
subnetIdSelector:
matchLabels:
subnet: firewall
- name: firewall-rules
base:
apiVersion: network.azure.upbound.io/v1beta1
kind: FirewallNetworkRuleCollection
spec:
forProvider:
# Only allow outbound to approved vegetable suppliers
rule:
- name: allow-approved-egress
protocols:
- TCP
destinationAddresses:
- "20.20.20.0/24" # Approved package repositories
destinationPorts:
- "443"
sourceAddresses:
- "10.0.0.0/16" # Our AKS subnet
The garden path analogy: You wouldn’t let people walk through your prize tomatoes to get to the carrots. Same with network traffic – everything follows designated paths.
📹 Layer 4: Detective Controls (Security Cameras)
Time to install cameras to watch for suspicious behavior:
# compositions/monitoring.yaml
- name: container-insights
base:
apiVersion: operationsmanagement.azure.upbound.io/v1beta1
kind: SolutionsPlan
spec:
forProvider:
location: westeurope
resourceGroupName: $(resource_group)
plan:
- product: OMSGallery/ContainerInsights
publisher: Microsoft
- name: log-analytics-workspace
base:
apiVersion: operationalinsights.azure.upbound.io/v1beta1
kind: Workspace
spec:
forProvider:
location: westeurope
resourceGroupName: $(resource_group)
sku: PerGB2018
retentionInDays: 30
- name: diagnostic-settings
base:
apiVersion: insights.azure.upbound.io/v1beta1
kind: MonitorDiagnosticSetting
spec:
forProvider:
targetResourceIdSelector:
matchLabels:
resource: aks-cluster
logAnalyticsWorkspaceIdSelector:
matchLabels:
resource: log-analytics
# Watch everything suspicious
log:
- category: kube-audit
enabled: true
- category: kube-apiserver
enabled: true
- category: cluster-autoscaler
enabled: true
metric:
- category: AllMetrics
enabled: true
These logs will alert you when:
- Someone tries to access carrots they shouldn’t (failed auth attempts)
- Unusual API calls happen (suspicious pod creations)
- Resource usage spikes (possible crypto-mining rabbits!)
🚨 Layer 5: Responsive Controls (Automatic Sprinklers)
When the cameras spot trouble, we respond automatically:
# compositions/auto-response.yaml
- name: azure-policy-assignment
base:
apiVersion: authorization.azure.upbound.io/v1beta1
kind: PolicyAssignment
spec:
forProvider:
policyDefinitionId: /providers/Microsoft.Authorization/policyDefinitions/
e3576e28-8b17-4677-84c3-db2990658d64 # Kubernetes cluster containers CPU and memory limits
resourceGroupName: $(resource_group)
# Auto-remediate non-compliant resources
remediation:
- enabled: true
# Create alerting rules
- name: high-privilege-pod-alert
base:
apiVersion: insights.azure.upbound.io/v1beta1
kind: MonitorMetricAlert
spec:
forProvider:
resourceGroupName: $(resource_group)
scopes:
- $(aks_resource_id)
criteria:
- aggregation: Total
metricName: pod_created_with_privileged
operator: GreaterThan
threshold: 0
# Send immediate alerts
action:
- actionGroupId: $(security_team_action_group)
Translation: If a cyber-rabbit tries to create a privileged pod (running as root), Azure Policy blocks it, and your security team gets a text message faster than you can say “organic vegetables.”
🔄 Layer 6: Recovery Controls (Seed Vault)
Finally, our backup plan for when despite all defenses, some rabbits still get through:
# compositions/backup-recovery.yaml
- name: velero-backup
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: v1
kind: Namespace
metadata:
name: velero
- name: backup-storage-account
base:
apiVersion: storage.azure.upbound.io/v1beta1
kind: Account
spec:
forProvider:
resourceGroupName: $(resource_group)
location: westeurope
accountTier: Standard
accountReplicationType: GRS # Geo-redundant - seeds in multiple locations!
- name: backup-schedule
base:
apiVersion: kubernetes.crossplane.io/v1alpha2
kind: Object
spec:
forProvider:
manifest:
apiVersion: velero.io/v1
kind: Schedule
metadata:
name: daily-carrot-backup
namespace: velero
spec:
schedule: "0 2 * * *" # 2 AM daily
template:
includedNamespaces:
- carrot-storage
- carrot-api
storageLocation: azure-backup
Now if cyber-rabbits eat all your carrots, you can restore from last night’s backup. It’s like having a seed vault in Svalbard, but for your data.
🎨 Putting It All Together: The Complete Garden Plan
Here’s our XRD (the blueprint for our secure garden):
# apis/secure-aks-cluster.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: secureaksclusters.platform.example.com
spec:
group: platform.example.com
names:
kind: SecureAKSCluster
plural: secureaksclusters
claimNames:
kind: SecureAKSClusterClaim
plural: secureaksclusterclaims
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
# Garden parameters
resourceGroup:
type: string
description: "Which plot of land (resource group)"
adminGroupId:
type: string
description: "Who gets the master key (admin Azure AD group)"
environment:
type: string
enum: [dev, staging, prod]
description: "Type of garden (affects security levels)"
carrotSensitivity:
type: string
enum: [public, internal, confidential, top-secret]
default: confidential
description: "How valuable are your cyber-carrots?"
required:
- resourceGroup
- adminGroupId
- environment
🚀 Deploying Your Secure Garden
Now let’s plant this garden:
# 1. Install Crossplane v2
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane
crossplane-stable/crossplane
--namespace crossplane-system
--create-namespace
--version v2.0.0
# 2. Install Azure provider
kubectl apply -f - <<EOF
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-azure-containerservice
spec:
package: xpkg.upbound.io/upbound/provider-azure-containerservice:v1.0.0
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-azure-network
spec:
package: xpkg.upbound.io/upbound/provider-azure-network:v1.0.0
EOF
# 3. Configure Azure credentials
az ad sp create-for-rbac --sdk-auth --role Contributor
--scopes /subscriptions/YOUR-SUBSCRIPTION-ID > azure-credentials.json
kubectl create secret generic azure-secret
-n crossplane-system
--from-file=creds=./azure-credentials.json
kubectl apply -f - <<EOF
apiVersion: azure.upbound.io/v1beta1
kind: ProviderConfig
metadata:
name: default
spec:
credentials:
source: Secret
secretRef:
namespace: crossplane-system
name: azure-secret
key: creds
EOF
# 4. Install our composition
kubectl apply -f compositions/
# 5. Claim your secure garden!
kubectl apply -f - <<EOF
apiVersion: platform.example.com/v1alpha1
kind: SecureAKSClusterClaim
metadata:
name: production-carrot-garden
namespace: default
spec:
resourceGroup: rg-secure-carrots-prod
adminGroupId: "YOUR-AZURE-AD-GROUP-ID"
environment: prod
carrotSensitivity: top-secret
EOF
🔍 Verifying Your Defenses
Let’s make sure all our layers are working:
# Check identity layer
kubectl get managedclusters -o jsonpath='{.items[0].spec.forProvider.azureActiveDirectoryRoleBasedAccessControl}'
# Verify network policies are in place
kubectl get networkpolicies -n carrot-storage
# Check monitoring is active
az monitor log-analytics workspace show
--resource-group rg-secure-carrots-prod
--workspace-name YOUR-WORKSPACE
# Test responsive controls (try to create a privileged pod)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: sneaky-rabbit
spec:
containers:
- name: bad-container
image: nginx
securityContext:
privileged: true # This should be blocked!
EOF
# Should see: Error from server (Forbidden): admission webhook denied
# Verify backups are running
kubectl get schedules -n velero
🎓 Lessons from the Garden
What we’ve learned about defense-in-depth:
1. No Single Layer is Perfect
Just like a fence won’t stop a determined rabbit, no single security control is foolproof. Cyber-rabbits are clever – they’ll find ways around your identity controls, they’ll try to tunnel under your network policies, and they’ll attempt to disguise themselves to avoid your cameras.
2. Layers Work Together
When your identity layer catches 80% of attacks, your network layer catches 80% of what gets through, and so on – you end up with 99.9%+ protection. Math!
3. Automate the Boring Stuff
Crossplane v2’s composition pipelines let you codify this entire garden plan. No more manual clicking in Azure Portal at 2 AM because a cyber-rabbit showed up.
4. Defense is Ongoing
Gardens need weeding, fences need mending, and security needs updating. Your Crossplane compositions should evolve as new threats emerge.
🐛 Common Pitfalls (Or: How Rabbits Actually Get In)
Mistake 1: “It’s Just Dev, We Don’t Need Security”
# ❌ DON'T DO THIS
environment: dev
carrotSensitivity: public # "It's fine, it's just test data"
Cyber-rabbits don’t care if it’s production or dev. They’ll happily eat test carrots while learning your garden layout for the real heist later.
Mistake 2: “Too Many Policies Will Slow Us Down”
# ❌ DANGER ZONE
networkPolicy: none # "Network policies are hard, let's skip them"
podSecurityStandards: privileged # "Developers need root everywhere"
You know what’s slower than implementing security? Recovering from a breach. And explaining to your CEO why all the customer carrots are gone.
Mistake 3: “I’ll Add Security Later”
Later never comes. Cyber-rabbits don’t wait for your next sprint.
📊 Measuring Success
How do you know your defense-in-depth is working?
# Check blocked attempts (rabbits turned away at the gate)
kubectl logs -n kube-system -l component=kube-apiserver |
grep "Forbidden"
# Review audit logs for suspicious patterns
az monitor log-analytics query
--workspace YOUR-WORKSPACE
--analytics-query 'KubeAuditLogs | where ResponseStatus == 403'
# Count backup success rate (seed vault health)
kubectl get backups -n velero
-o jsonpath='{.items[?(@.status.phase=="Completed")].metadata.name}' |
wc -l
Success metrics:
- Identity Layer: 99%+ authentication success rate for legitimate users
- Network Layer: Zero unauthorized cross-namespace communication
- Detective Layer: < 5 minutes to detect anomalies
- Responsive Layer: Automatic remediation of 80%+ policy violations
- Recovery Layer: < 15 minutes to restore from backup
🚀 Next Steps
Want to take your garden security further?
- Add Service Mesh: Istio or Linkerd for even more granular network control
- Implement GitOps: Flux or ArgoCD to ensure only approved changes reach your garden
- Add Runtime Security: Falco for detecting suspicious behavior at runtime
- Implement Secrets Management: Azure Key Vault integration for your most sensitive seeds
🎬 Conclusion
We’ve built a production-ready, defense-in-depth secured Azure AKS cluster using Crossplane v2. Your cyber-carrots are now protected by:
✅ Identity controls (Azure AD + RBAC)
✅ Protective controls (Pod Security Standards)
✅ Network controls (Network Policies + Firewall)
✅ Detective controls (Azure Monitor + Insights)
✅ Responsive controls (Azure Policy auto-remediation)
✅ Recovery controls (Velero backups)
Are cyber-rabbits going to try to eat your carrots anyway? Absolutely. Will they succeed? Not if you maintain your garden properly!
Remember: Security isn’t a one-time task – it’s a continuous practice, just like gardening. Water your defenses, weed out vulnerabilities, and keep those cyber-rabbits at bay.
Now go forth and protect those prize-winning cyber-carrots! 🥕🔒
Full source code and examples: github.com/software-journey/defense-in-depth-of-cyber-carrots
Questions? Found a hole in the fence? Drop a comment below!
Special thanks to the Google Cloud Security course for the garden metaphor that made this article possible. If you’re interested in diving deeper into cloud security fundamentals, check out their excellent Defense-in-Depth module.
This article is part of our Crossplane Adventures series. Previously: Testing Your Cloud Infrastructure Like IKEA Furniture: A Guide to Crossplane v2 End-to-End Testing
Tags
crossplane #kubernetes #azure #security #devops #infrastructure-as-code #defense-in-depth #cloudsecurity #aks #devsecops