On this page

In December, I published a post on securing the container supply chain โ€” SBOM generation, image signing, and build provenance with GitHub Actions. That covered build-time security: making sure the image you ship is the image you built.

But what happens after deployment? Once a container is running on AKS, how do you detect a compromised workload, block an attacker from dropping a cryptominer binary, or prevent a vulnerable image from ever reaching the cluster?

That’s where runtime security comes in. Microsoft has shipped three features in Defender for Containers that, together, form a complete runtime defense stack:

LayerFeatureStatusWhat It Does
Deploy-time gateGated DeploymentGA (Nov 2025)Admission control blocks images with unresolved critical CVEs
Runtime detectionBinary DriftGA detect / Preview blockCatches executables not in the original container image
Runtime protectionContainer Anti-MalwarePreview (Feb 2026)Real-time malware detection and blocking inside running containers

This post walks through deploying all three on AKS with Defender for Cloud, then building Sentinel detections and a workbook to monitor the alerts.

Hands-on Lab: All Bicep templates, KQL queries, and deployment scripts are in the companion lab.


Why Runtime Security Matters

Build-time controls (scanning, signing, attestation) are necessary but not sufficient. Here’s why:

  • Containers drift. Attackers use kubectl exec to drop binaries, install tools, or modify configs inside running containers. The image was clean at deploy time; the runtime is not.
  • Zero-days bypass scanners. A vulnerability unknown at build time can be exploited in production before the next scan runs.
  • Supply chain attacks target runtime. Compromised base images, malicious init containers, and sidecar injection all happen after the image passes CI/CD gates.
  • Legitimate images can be weaponized. An attacker who gains cluster access can deploy a clean ubuntu image and use it as a beachhead โ€” no CVEs, no signatures to catch.

Microsoft’s own threat intelligence found that 51% of workload identities were completely inactive โ€” representing dormant attack vectors that threat actors exploit for lateral movement, making runtime defense essential.

MITRE ATT&CK Mapping

TechniqueIDDetection Layer
Exploit Public-Facing ApplicationT1190Gated Deployment, Anti-Malware
Command and Scripting InterpreterT1059Binary Drift
Ingress Tool TransferT1105Binary Drift, Anti-Malware
Deploy ContainerT1610Gated Deployment
Native APIT1106Binary Drift
Impair DefensesT1562Binary Drift (sensor tampering)

Architecture

The lab deploys a three-layer defense architecture on AKS:

Architecture diagram showing Container Registry, Defender for Cloud with Gated Deployment, Binary Drift Detection, and Anti-Malware Protection, AKS Cluster with Defender Sensor DaemonSet monitoring pods, and Log Analytics feeding into Microsoft Sentinel

Three-layer runtime defense architecture: Container Registry pulls pass through Gated Deployment admission control, the Defender Sensor DaemonSet monitors running pods for binary drift and malware, and all alerts flow to Log Analytics and Sentinel for SOC visibility.

Components

  1. AKS Cluster โ€” Single-node cluster with workload identity and Azure CNI
  2. Defender for Containers โ€” Plan enabled at subscription level
  3. Defender Sensor โ€” DaemonSet deployed via Helm chart with --antimalware flag (sensor v0.10.2+ required for anti-malware, v0.10.1+ for drift blocking)
  4. Log Analytics Workspace โ€” Collects SecurityAlert, ContainerLogV2, and KubePodInventory tables
  5. Sentinel โ€” Analytics rules and workbook for SOC visibility

Lab Deployment

Prerequisites

One-Command Deploy

git clone https://github.com/j-dahl7/aks-runtime-security-lab.git
cd aks-runtime-security-lab
# Deploy everything
./scripts/Deploy-Lab.ps1 -Location "eastus"

# Infrastructure only (skip Sentinel rules)
./scripts/Deploy-Lab.ps1 -Location "eastus" -SkipSentinel

# Run test scenarios after deployment
./scripts/Test-RuntimeSecurity.ps1

The script creates:

  1. AKS cluster (single node, Standard_D4s_v3) via Bicep
  2. Log Analytics workspace with Container Insights
  3. Defender for Containers plan enablement (with AntiMalware extension)
  4. Defender sensor via Helm chart (v0.10.2+ with anti-malware collector)
  5. 4 Sentinel analytics rules
  6. 1 Sentinel workbook

Portal step required: After deployment, configure the binary drift policy in Defender for Cloud > Environment Settings > Containers drift policy. The default is “Ignore drift detection” โ€” change it to “Drift detection alert” (or “Block” for Preview). There is no REST API for this setting.

Manual Deploy (Step-by-Step)

If you prefer to deploy component by component:

# 1. Deploy infrastructure (AKS cluster + Log Analytics)
az deployment sub create \
  --location eastus \
  --template-file bicep/main.bicep \
  --parameters location=eastus

# 2. Enable Defender for Containers plan
az security pricing create --name Containers --tier Standard

# 3. Get cluster credentials
az aks get-credentials \
  --resource-group aks-runtime-lab-rg \
  --name aks-runtime-lab

# 4. Deploy Defender sensor via Helm (with anti-malware)
CLUSTER_ID=$(az aks show -g aks-runtime-lab-rg -n aks-runtime-lab --query id -o tsv)
curl -sL https://raw.githubusercontent.com/microsoft/Microsoft-Defender-For-Containers/main/scripts/install_defender_sensor_aks.sh \
  -o install_defender_sensor_aks.sh
chmod +x install_defender_sensor_aks.sh
./install_defender_sensor_aks.sh --id "$CLUSTER_ID" --version latest --antimalware

Then configure the drift policy in the portal:

  1. Navigate to Defender for Cloud > Environment Settings
  2. Select Containers drift policy
  3. Modify the Default binary drift rule: change from Ignore drift detection to Drift detection alert (or Drift detection blocking in Preview)

Cleanup

./scripts/Deploy-Lab.ps1 -Destroy

Or manually:

az group delete --name aks-runtime-lab-rg --yes --no-wait

Layer 1: Gated Deployment

Gated deployment uses Kubernetes admission control to block container images that fail vulnerability assessment. It’s the first line of defense โ€” preventing vulnerable images from ever running on your cluster.

How It Works

  1. When a pod creation request hits the Kubernetes API server, the Defender admission webhook intercepts it
  2. The webhook checks the image digest against Defender for Cloud’s vulnerability assessment database
  3. If the image has unresolved critical/high CVEs that match your security rules, the deployment is denied
  4. In audit mode, the deployment proceeds but generates a recommendation in Defender for Cloud

Configuration

Enable gated deployment in the Defender for Cloud portal:

  1. Navigate to Environment Settings > your subscription > Settings & Monitoring
  2. Under Containers, enable the Defender Sensor (with Security Gating) and Registry Access (with Security Findings) extensions
  3. Go to Environment Settings > Security Rules > Vulnerability Assessment tab
  4. Select Add Rule:
    • Action: Start with Audit (generates recommendations), switch to Deny after testing
    • Scope: Select your AKS cluster or apply subscription-wide
    • Conditions: Block images with Critical or High severity CVEs with available fixes

Testing Gated Deployment

# Deploy an image with known critical CVEs (old nginx)
kubectl run vuln-test --image=nginx:1.14.0 --restart=Never

# In Audit mode: pod deploys, but check Defender recommendations
# In Deny mode: pod creation is rejected
Error from server (Forbidden): admission webhook
"defender-admission-controller.kube-system.svc" denied the request:
Image nginx:1.14.0 has critical vulnerabilities with available fixes.

KQL โ€” Gated Deployment Blocks

SecurityAlert
| where AlertType has "GatedDeployment" or AlertName has "deployment was blocked"
| extend ImageName = extract(@"Image[:\s]+([^\s,]+)", 1, Description)
| extend ClusterName = extract(@"cluster[:\s]+([^\s,]+)", 1, Description)
| extend Namespace = extract(@"namespace[:\s]+([^\s,]+)", 1, Description)
| project TimeGenerated, AlertName, AlertSeverity,
    ImageName, ClusterName, Namespace, Description
| sort by TimeGenerated desc

Layer 2: Binary Drift Detection

Binary drift is the crown jewel of runtime detection. Container images are designed to be immutable โ€” every process running inside a container should trace back to the original image manifest. When an attacker drops a new binary (cryptominer, reverse shell, enumeration tool) into a running container, the Defender sensor catches the drift.

How It Works

The Defender sensor (DaemonSet) monitors process creation events on every node:

  1. Process starts โ€” The sensor intercepts every execve syscall inside containers
  2. Image comparison โ€” The binary’s hash is compared against the original container image layers
  3. Verdict โ€” If the binary doesn’t exist in any layer of the original image, it’s flagged as drift
  4. Action โ€” Depending on policy: Detect (alert only) or Block (kill the process and alert)

Configuring Drift Policy

Navigate to Defender for Cloud > Environment Settings > Containers drift policy:

SettingValue
Rule nameBlock drift in production namespaces
ActionBlock (Preview) or Detect (GA)
Scope โ€” Namespaceequals: default, production, app
Allow list/usr/bin/apt-get, /usr/bin/dpkg (if needed for init scripts)

Warning: The drift policy defaults to Ignore drift detection โ€” no alerts are generated until you explicitly change this setting. There is currently no REST API for drift policy configuration; it must be set in the portal.

Tip: Start with Detect on all namespaces, then switch critical namespaces to Block after reviewing alerts for 7 days.

Testing Binary Drift

# Deploy a clean nginx container
kubectl run drift-test --image=nginx:latest --restart=Never

# Wait for pod to be running
kubectl wait --for=condition=Ready pod/drift-test --timeout=60s

# Exec in and create a binary that doesn't exist in the original image
kubectl exec drift-test -- /bin/sh -c \
  "echo '#!/bin/sh' > /tmp/notinimage.sh && chmod +x /tmp/notinimage.sh && /tmp/notinimage.sh"

Within minutes, Defender generates a Binary drift detected alert:

  • Alert severity: Medium (detect) or High (block)
  • Alert data: Container name, pod name, namespace, cluster, the drifted binary path
  • MITRE mapping: T1105 (Ingress Tool Transfer), T1059 (Command and Scripting Interpreter)
Defender for Cloud alert showing binary drift detected in a container

Defender for Cloud flags the drifted binary with full container context โ€” pod name, namespace, cluster, and MITRE tactic mapping.

KQL โ€” Binary Drift Alerts

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend DriftedBinary = tostring(ExtProps["Suspicious Process"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, ClusterName,
    Namespace, PodName, ContainerName, DriftedBinary
| sort by TimeGenerated desc

KQL โ€” Binary Drift Trend (Last 30 Days)

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ClusterName = CompromisedEntity
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| where isnotempty(ClusterName)
| summarize DriftCount = count() by bin(TimeGenerated, 1d), ClusterName, Namespace
| render timechart

Layer 3: Container Anti-Malware

The newest addition (Preview, February 2026): real-time malware detection and blocking inside running containers. This catches what binary drift alone can’t โ€” known malware signatures, polymorphic threats, and zero-day variants via cloud intelligence.

Important: Anti-malware requires two things: (1) the ContainerSensor extension must have AntiMalwareEnabled: True at the subscription level (REST API or portal), and (2) the Defender sensor on the cluster must be deployed via the Helm chart with the --antimalware flag (sensor v0.10.2+). The standard AKS security profile (az aks update --enable-defender) deploys an older sensor version that does not include the anti-malware collector.

How It Works

Three-component architecture:

  1. Defender Sensor v0.10.2+ (via Helm chart) โ€” Monitors file creation and process execution in real time
  2. Local scan engine โ€” On-node binary analysis for immediate detection
  3. Cloud Protection (MDAV Cloud) โ€” Microsoft Defender Antivirus cloud intelligence providing ML classification, reputation scoring, and zero-day detection

When a malicious file is written or executed inside a container:

  • The local engine scans it immediately
  • If uncertain, the file hash is sent to MDAV Cloud for verdict
  • On detection: Alert (detect mode) or Kill process + Alert (block mode)

Configuring Anti-Malware Rules

Navigate to Defender for Cloud > Environment Settings > Security rules > Antimalware:

SettingValue
Rule nameBlock malware in all namespaces
ActionBlock (kill process) or Detect (alert only)
Scope โ€” Clusterequals: aks-runtime-lab
Scope โ€” Namespaceall

Testing Anti-Malware

The EICAR test file is the industry-standard way to test malware detection without using real malware:

# Deploy a test pod
kubectl run malware-test --image=nginx:latest --restart=Never
kubectl wait --for=condition=Ready pod/malware-test --timeout=60s

# Write the EICAR test string into the container (base64 to avoid shell escaping)
kubectl exec malware-test -- /bin/sh -c \
  "echo 'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo=' | base64 -d > /tmp/eicar.com"

# In block mode, the process writing the file is killed
# In detect mode, an alert is generated

KQL โ€” Anti-Malware Alerts

SecurityAlert
| where AlertType has "MalwareDetected"
    or AlertName has_any ("malware", "Malicious file")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend MalwareName = tostring(ExtProps["Malware Name"])
| extend FilePath = tostring(ExtProps["Suspicious Process"])
| extend ActionTaken = tostring(ExtProps["Action Taken"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, MalwareName,
    FilePath, ActionTaken, ClusterName, Namespace, PodName, ContainerName
| sort by TimeGenerated desc

Sentinel Analytics Rules

Microsoft Defender for Cloud security alerts showing binary drift and container threat detections

Defender for Cloud Security Alerts filtered to the lab cluster โ€” binary drift, malware execution, reverse shell, network scanning, C2 communication, and Defender agent termination alerts from the test scenarios.

Four analytics rules provide SOC coverage for the three runtime defense layers. Each runs every 5 minutes against the last hour of data.

Note: Rules 1-3 query the SecurityAlert table, which is populated by the Defender for Cloud data connector. The table schema is created when the connector is enabled, but alerts may take 15-30 minutes to flow in after test scenarios run. The rules can be deployed immediately โ€” they’ll return zero results until the first alerts arrive, then fire automatically.

Rule 1: Binary Drift in Production Namespace

Fires when binary drift is detected in namespaces tagged as production. High severity โ€” binary drift in production is never legitimate.

SecurityAlert
| where AlertType has_any ("DriftDetection", "BinaryDrift")
    or AlertName has "drift"
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend DriftedBinary = tostring(ExtProps["Suspicious Process"])
| where Namespace in ("default", "production", "kube-system")
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, ClusterName,
    Namespace, PodName, ContainerName, DriftedBinary

Severity: High MITRE: T1105 (Ingress Tool Transfer), T1059 (Command and Scripting Interpreter)

Rule 2: Container Malware Detected

Fires on any anti-malware detection across all clusters. Includes the malware name and action taken (detected vs. blocked).

SecurityAlert
| where AlertType has "MalwareDetected"
    or AlertName has_any ("malware", "Malicious file")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend PodName = tostring(Entity.Pod.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ClusterName = CompromisedEntity
| extend MalwareName = tostring(ExtProps["Malware Name"])
| extend FilePath = tostring(ExtProps["Suspicious Process"])
| extend ActionTaken = tostring(ExtProps["Action Taken"])
| where isnotempty(ContainerName)
| project TimeGenerated, AlertSeverity, MalwareName,
    FilePath, ActionTaken, ClusterName, Namespace,
    PodName, ContainerName

Severity: High MITRE: T1105 (Ingress Tool Transfer), T1204 (User Execution)

Rule 3: Vulnerable Image Deployment Attempted

Fires when gated deployment blocks or audits a vulnerable image deployment.

SecurityAlert
| where AlertType has "GatedDeployment"
    or AlertName has_any ("deployment was blocked", "vulnerable image")
| extend ExtProps = parse_json(ExtendedProperties)
| extend ImageName = coalesce(
    tostring(ExtProps["Image Name"]),
    tostring(ExtProps["ImageName"]),
    extract(@"[Ii]mage[:\s]+([^\s,]+)", 1, Description))
| extend ClusterName = CompromisedEntity
| extend VulnCount = coalesce(
    tostring(ExtProps["Vulnerability Count"]),
    extract(@"(\d+)\s+vulnerabilit", 1, Description))
| where isnotempty(ImageName)
| project TimeGenerated, AlertSeverity, ImageName,
    ClusterName, VulnCount, Description

Severity: Medium (audit) / High (deny) MITRE: T1610 (Deploy Container), T1190 (Exploit Public-Facing Application)

Rule 4: Suspicious kubectl exec into Container

Detects interactive shell sessions via kubectl exec โ€” a common attacker technique for initial access and lateral movement inside clusters.

AzureDiagnostics
| where Category == "kube-audit"
| extend RequestObject = parse_json(log_s)
| extend Verb = tostring(RequestObject.verb)
| extend RequestURI = tostring(RequestObject.requestURI)
| extend UserAgent = tostring(RequestObject.userAgent)
| extend SourceIP = tostring(RequestObject.sourceIPs[0])
| extend Username = tostring(RequestObject.user.username)
| where Verb in ("create", "get")
| where RequestURI has "/exec"
| where RequestURI !has "kube-system"
| extend PodName = extract(@"/pods/([^/]+)/exec", 1, RequestURI)
| extend Namespace = extract(@"namespaces/([^/]+)/", 1, RequestURI)
| project TimeGenerated, Username, SourceIP,
    PodName, Namespace, UserAgent, RequestURI

Severity: Medium MITRE: T1609 (Container Administration Command)


Hunting Queries

Beyond automated detection, three hunting queries support proactive investigation:

Hunt 1: Container Processes Not in Original Image (Extended)

Broader drift hunt that surfaces all unknown processes, not just those that trigger alerts:

SecurityAlert
| where ProductName == "Microsoft Defender for Cloud"
| where AlertType has_any ("DriftDetection", "BinaryDrift",
    "SuspectProcess", "CryptoMiner", "ToolExecution")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "container"
| extend ContainerName = tostring(Entity.Name)
| extend Namespace = tostring(Entity.Pod.Namespace.Name)
| extend ProcessName = tostring(ExtProps["Suspicious Process"])
| where isnotempty(ContainerName)
| summarize AlertCount = count(),
    AlertTypes = make_set(AlertType),
    FirstSeen = min(TimeGenerated),
    LastSeen = max(TimeGenerated)
    by ContainerName, ProcessName, Namespace
| sort by AlertCount desc

Hunt 2: Cluster Admin Actions from Unexpected Users

Surfaces cluster-admin level operations from users who aren’t in the expected admin list:

let KnownAdmins = dynamic(["clusterAdmin", "aksService", "masterClient"]);
AzureDiagnostics
| where Category == "kube-audit"
| extend RequestObject = parse_json(log_s)
| extend Username = tostring(RequestObject.user.username)
| extend Groups = tostring(RequestObject.user.groups)
| extend Verb = tostring(RequestObject.verb)
| extend Resource = tostring(RequestObject.objectRef.resource)
| extend Namespace = tostring(RequestObject.objectRef.namespace)
| where Groups has "system:masters" or Username has "admin"
| where Username !has_any (KnownAdmins)
| where Verb in ("create", "delete", "patch", "update")
| project TimeGenerated, Username, Verb, Resource,
    Namespace, Groups
| summarize ActionCount = count(),
    Actions = make_set(strcat(Verb, ":", Resource)),
    Namespaces = make_set(Namespace)
    by Username
| sort by ActionCount desc

Hunt 3: Network Connections from Containers to External IPs

Detects containers making outbound connections to unexpected destinations โ€” useful for catching C2 callbacks and data exfiltration:

SecurityAlert
| where ProductName == "Microsoft Defender for Cloud"
| where AlertType has_any ("NetworkActivity",
    "SuspiciousConnection", "C2Connection")
| extend ParsedEntities = parse_json(Entities)
| extend ExtProps = parse_json(ExtendedProperties)
| mv-expand Entity = ParsedEntities
| where tostring(Entity.Type) == "ip"
| extend DestinationIP = tostring(Entity.Address)
| extend ContainerName = tostring(ExtProps["Container Name"])
| where isnotempty(DestinationIP)
| where not(ipv4_is_private(DestinationIP))
| project TimeGenerated, ContainerName,
    DestinationIP, AlertName, AlertSeverity
| sort by TimeGenerated desc

Full KQL for all hunting queries is in the companion lab.

Bonus: Microsoft Attack Simulation Tool

Microsoft provides an official Defender for Cloud Attack Simulation tool that generates realistic container threat scenarios. It covers reconnaissance, lateral movement, secrets gathering, crypto mining, and web shell deployment:

curl -O https://raw.githubusercontent.com/microsoft/Defender-for-Cloud-Attack-Simulation/refs/heads/main/simulation.py
python3 simulation.py

This generates multiple alerts across several MITRE ATT&CK techniques โ€” perfect for populating your workbook with real alert data.


Sentinel Workbook

The lab deploys a workbook with four panels providing a unified view of container runtime security:

  • Runtime Alert Timeline โ€” Timechart of binary drift, anti-malware, and gated deployment alerts over time, colored by alert type
  • Binary Drift by Namespace โ€” Bar chart showing which namespaces have the most drift events, highlighting areas that need immutability enforcement
  • Top Drifted Binaries โ€” Table of the most frequently seen unauthorized executables across all clusters
  • kubectl exec Audit Trail โ€” Log of all interactive container sessions with user, source IP, and target pod

The workbook uses the same KQL patterns as the analytics rules, enabling SOC analysts to investigate alerts in context.


Key Takeaways

  1. Build-time and runtime security are complementary โ€” Your CI/CD pipeline catches known vulnerabilities before deployment. Runtime defense catches everything that happens after.
  2. Binary drift is a high-fidelity signal โ€” In production namespaces, any executable not in the original image is suspicious. Start in detect mode, graduate to block.
  3. Container anti-malware fills the zero-day gap โ€” Cloud-backed ML detection catches threats that static scanning misses. The EICAR test file is an easy validation.
  4. Gated deployment is your last line of prevention โ€” Admission control prevents vulnerable images from ever running, even if CI/CD scanning was skipped or bypassed.
  5. Audit everything with kube-audit logs โ€” kubectl exec into production containers should always generate an alert. If your SOC isn’t watching this, attackers can operate undetected.
  6. Layer your defenses โ€” No single feature catches everything. The combination of gated deployment + binary drift + anti-malware + kube-audit monitoring creates defense in depth.

Resources

Jerrad Dahlager

Jerrad Dahlager, CISSP, CCSP

Cloud Security Architect ยท Adjunct Instructor

Marine Corps veteran and firm believer that the best security survives contact with reality.

Have thoughts on this post? I'd love to hear from you.