Aks Network Capture
MicrosoftOfficialCreated by Microsoft
- Skill
- Workflow
- Cloud and DevOps
- MIT
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The aks-network-capture skill provides a governed procedure for collecting wire-level evidence from Azure Kubernetes Service (AKS) nodes. It is designed for scenarios requiring explicit packet capture after initial read-only diagnostics have been exhausted. The skill includes scripts for setting up capture configurations, executing the capture, generating test traffic, and retrieving the resulting data, alongside tools for collecting relevant Azure network metadata. It emphasizes strict authorization, target binding, and resource cleanup to ensure that captures are performed securely and within defined operational boundaries.
What it does
- Configures AKS nodes for packet capture using dedicated scripts.
- Executes bounded packet captures on specific nodes or pods.
- Collects Azure network configuration metadata for context.
- Generates test traffic to facilitate network troubleshooting.
- Retrieves capture artifacts and manages resource cleanup by run ID.
When to use it
- When you need to perform a packet capture on an AKS node.
- When you need to prove where network packets are being dropped.
- After read-only diagnostics have failed to resolve a connectivity issue.
- When you require wire-level evidence for network troubleshooting.
Advantages
- Provides a structured, repeatable process for network evidence collection.
- Includes built-in authorization and target binding steps to maintain security.
- Supports both Bash and PowerShell environments for flexibility.
- Ensures resource cleanup is tied to specific capture run IDs.
Usage examples
- “Capture packets on an AKS node to investigate a drop.”
- The agent initiates the capture workflow, starting with authorization and target binding, followed by the deployment of the capture configuration.
- “Run tcpdump on AKS to troubleshoot connectivity.”
- The agent guides you through the setup and execution of the packet capture scripts, ensuring the capture is bounded and authorized.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/microsoft/aks-network-capture/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip aks-network-capture.zip -d .agents/skills
Documentation by Microsoft
AKS Network Capture
Quick Reference
| Use | Requires | Safety |
|---|---|---|
| AKS pcap evidence | kubectl; az for Azure evidence | Bounded, pinned, least privilege |
When to Use This Skill
Use for explicit packet capture after read-only checks, not generic connectivity failures.
MCP Tools
Azure MCP's AKS area provides cluster and node-pool metadata, not Kubernetes command execution or packet capture.
Host Capability Gate
Before executing the workflow, confirm that the host permits the required
Bash or PowerShell execution, kubectl access to the bound cluster, az for
Azure evidence, access to the bundled scripts, and an approved artifact
destination. A governed Azure CLI tool alone does not establish that shell,
Kubernetes commands, or artifact operations are supported.
If a required capability is unavailable or prohibited, state that capture
execution is unavailable in this host. Analyze supplied, appropriately
redacted evidence or give the operator a target-bound collection/capture plan;
do not claim to have run it. Never route kubectl through Azure MCP, add an
unapproved execution path, or bypass host policy. Host support does not replace
the mutation and sensitive-data approvals below.
Run bundled scripts from the skill root only after this gate is satisfied.
Workflow/Steps
Check host capabilities, then complete authorization and target binding. Capture intent is not mutation consent. Stop for separate approval before any debug-container fallback.
Install Bash / PowerShell.
Capture nodes or pods with Bash / PowerShell.
Generate traffic if approved with Bash / PowerShell.
Retrieve the exact run with Bash / PowerShell.
Gather Azure evidence with Bash / PowerShell.
The ConfigMap runs run-capture.sh inside its pinned Linux image.
Error Handling
| Error | Action |
|---|---|
| Invalid input | Correct it before retrying. |
| Missing/stale ConfigMap | Run setup again. |
| Capture/retrieval failure | Inspect Job logs; missing evidence is not success. |
Files
- ATTRIBUTION.mdAdded by AutoGineer983 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md2.9 KiB
- references/capture-authorization.md1.4 KiB
- scripts/collect-azure-network-info.ps1Script7.9 KiB
- scripts/collect-azure-network-info.shScript7.0 KiB
- scripts/create-capture.ps1Script11.5 KiB
- scripts/create-capture.shScript12.4 KiB
- scripts/generate-test-traffic.ps1Script5.4 KiB
- scripts/generate-test-traffic.shScript6.1 KiB
- scripts/retrieve-captures.ps1Script9.4 KiB
- scripts/retrieve-captures.shScript8.1 KiB
- scripts/run-capture.shScript2.4 KiB
- scripts/setup-capture-configmap.ps1Script1.7 KiB
- scripts/setup-capture-configmap.shScript1.2 KiB
scripts/create-capture.ps1
# Create bounded packet-capture Jobs on selected AKS nodes.
# Exit codes: 0 = success/help, 1 = execution failure, 2 = usage/argument error.
[CmdletBinding()]
param(
[string]$Name,
[string]$Duration = "60s",
[string]$NodeSelector = "kubernetes.io/os=linux",
[string]$NodeNames,
[string]$PodSelector,
[string]$PodNames,
[string]$Namespace = "default",
[string]$TcpdumpFilter = "",
[string]$PacketSize = "0",
[switch]$Help
)
$CaptureImage = "mcr.microsoft.com/containernetworking/retina-shell:v1.2.3@sha256:c7dfe8e0c0dc7fa28e4cfbad04ade270c3051c42a5495488d4d897b49fb3366f"
$ConfigMapName = "network-capture-scripts"
$OutputBase = "/var/log/aks-network-captures"
$CaptureMountPath = "/capture-output"
$MaxDurationSeconds = 1800
function Write-Usage {
@"
Usage: create-capture.ps1 -Name <capture-name> [options]
Target options:
-NodeSelector <key=val> Node selector (default: kubernetes.io/os=linux)
-NodeNames <n1,n2> Comma-separated node names
-PodSelector <key=val> Pod selector; resolves pods to nodes and IPs
-PodNames <p1,p2> Comma-separated pod names
-Namespace <name> Pod, ConfigMap, and Job namespace
Capture options:
-Duration <Ns|Nm|Nh> Duration (default: 60s; maximum: 1800s)
-PacketSize <bytes> Snapshot length (default: 0, full packet)
-TcpdumpFilter <expr> Plain BPF expression; no flags or metacharacters
"@
}
function Stop-Usage {
param([string]$Message)
[Console]::Error.WriteLine("Error: $Message")
[Console]::Error.WriteLine((Write-Usage))
exit 2
}
function Stop-Failure {
param([string]$Message)
[Console]::Error.WriteLine("Error: $Message")
exit 1
}
function Test-Rfc1123Label {
param([string]$Value)
return $Value.Length -le 63 -and
$Value -cmatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"
}
function Test-DnsSubdomain {
param([string]$Value)
return $Value.Length -le 253 -and
$Value -cmatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$"
}
function Invoke-KubectlText {
param([string[]]$KubectlArguments, [string]$FailureMessage)
$output = (& kubectl @KubectlArguments 2>&1) -join "`n"
if ($LASTEXITCODE -ne 0) {
Stop-Failure "$FailureMessage`n$output"
}
return $output.Trim()
}
if ($Help) {
Write-Usage
exit 0
}
if ([string]::IsNullOrWhiteSpace($Name)) {
Stop-Usage "-Name is required"
}
if (-not (Test-Rfc1123Label $Name)) {
Stop-Usage "-Name must be an RFC 1123 label"
}
if (-not (Test-Rfc1123Label $Namespace)) {
Stop-Usage "-Namespace must be an RFC 1123 label"
}
if ($Duration -cnotmatch "^([0-9]+)([smh])$") {
Stop-Usage "-Duration must look like 30s, 5m, or 1h"
}
$durationNumber = [int64]$Matches[1]
$durationSeconds = switch ($Matches[2]) {
"s" { $durationNumber }
"m" { $durationNumber * 60 }
"h" { $durationNumber * 3600 }
}
if ($durationSeconds -lt 1 -or $durationSeconds -gt $MaxDurationSeconds) {
Stop-Usage "-Duration must be between 1s and ${MaxDurationSeconds}s"
}
if ($PacketSize -notmatch "^[0-9]+$") {
Stop-Usage "-PacketSize must be a non-negative integer"
}
$packetSizeValue = [int64]$PacketSize
if ($packetSizeValue -gt 262144) {
Stop-Usage "-PacketSize exceeds 262144 bytes"
}
if ($NodeSelector -notmatch "^[A-Za-z0-9._/=,!()-]+$") {
Stop-Usage "-NodeSelector has invalid characters"
}
if ($PodSelector -and $PodSelector -notmatch "^[A-Za-z0-9._/=,!()-]+$") {
Stop-Usage "-PodSelector has invalid characters"
}
if ($TcpdumpFilter.Length -gt 1024 -or $TcpdumpFilter -match "[`r`n]") {
Stop-Usage "-TcpdumpFilter must be one line and no more than 1024 characters"
}
if ($TcpdumpFilter -and $TcpdumpFilter -notmatch "^[A-Za-z0-9 ._:/()&|<>=!-]+$") {
Stop-Usage "-TcpdumpFilter contains disallowed characters"
}
foreach ($token in ($TcpdumpFilter -split "\s+")) {
if ($token.StartsWith("-")) {
Stop-Usage "-TcpdumpFilter may not contain flag tokens"
}
}
$selectionCount = 0
foreach ($selection in @($NodeNames, $PodSelector, $PodNames)) {
if (-not [string]::IsNullOrWhiteSpace($selection)) {
$selectionCount++
}
}
if ($selectionCount -gt 1) {
Stop-Usage "choose only one of -NodeNames, -PodSelector, or -PodNames"
}
if (-not (Get-Command kubectl -ErrorAction SilentlyContinue)) {
Stop-Failure "kubectl is not installed or not on PATH"
}
$deployedRunner = Invoke-KubectlText -KubectlArguments @(
"get", "configmap", $ConfigMapName, "-n", $Namespace,
"-o", "go-template={{ index .data `"run-capture.sh`" }}"
) -FailureMessage "ConfigMap '$ConfigMapName' was not found; run setup-capture-configmap first"
$runnerPath = Join-Path $PSScriptRoot "run-capture.sh"
try {
$localRunner = Get-Content -LiteralPath $runnerPath -Raw -ErrorAction Stop
} catch {
Stop-Failure "could not read the local capture runner: $($_.Exception.Message)"
}
if ($deployedRunner.TrimEnd("`r", "`n") -ne $localRunner.TrimEnd("`r", "`n")) {
Stop-Failure "ConfigMap '$ConfigMapName' is stale; run setup-capture-configmap again"
}
$targetNodes = New-Object System.Collections.Generic.List[string]
$podIpTerms = New-Object System.Collections.Generic.List[string]
$podJsonPath = '{range .items[*]}{.metadata.name}{"|"}{.spec.nodeName}{"|"}{range .status.podIPs[*]}{.ip}{","}{end}{"\n"}{end}'
if ($PodNames -or $PodSelector) {
if ($PodNames) {
$pods = @($PodNames.Split(","))
foreach ($pod in $pods) {
if (-not (Test-Rfc1123Label $pod)) {
Stop-Usage "pod name '$pod' is not an RFC 1123 label"
}
}
$podOutput = Invoke-KubectlText -KubectlArguments (@(
"get", "pods", "-n", $Namespace
) + $pods + @("-o", "jsonpath=$podJsonPath")) -FailureMessage "could not resolve the selected pods"
} else {
$podOutput = Invoke-KubectlText -KubectlArguments @(
"get", "pods", "-n", $Namespace, "-l", $PodSelector,
"-o", "jsonpath=$podJsonPath"
) -FailureMessage "could not resolve the pod selector"
}
foreach ($record in @($podOutput -split "`n")) {
if ([string]::IsNullOrWhiteSpace($record)) {
continue
}
$parts = $record.TrimEnd("`r").Split("|")
if ($parts.Count -ne 3 -or [string]::IsNullOrWhiteSpace($parts[1])) {
Stop-Failure "Kubernetes returned an invalid pod placement record"
}
$targetNodes.Add($parts[1])
$ips = @($parts[2].Split(",") | Where-Object { $_ })
if ($ips.Count -eq 0) {
Stop-Failure "selected pod '$($parts[0])' has no assigned IP"
}
foreach ($ip in $ips) {
if ($ip -notmatch "^[A-Fa-f0-9.:]+$") {
Stop-Failure "selected pod '$($parts[0])' returned an invalid IP"
}
$podIpTerms.Add("host $ip")
}
}
} elseif ($NodeNames) {
foreach ($node in $NodeNames.Split(",")) {
$targetNodes.Add($node)
}
} else {
$nodeOutput = Invoke-KubectlText -KubectlArguments @(
"get", "nodes", "-l", $NodeSelector,
"-o", 'jsonpath={range .items[*]}{.metadata.name}{"\n"}{end}'
) -FailureMessage "could not resolve the node selector"
foreach ($node in @($nodeOutput -split "`n")) {
if (-not [string]::IsNullOrWhiteSpace($node)) {
$targetNodes.Add($node.TrimEnd("`r"))
}
}
}
if ($targetNodes.Count -eq 0) {
Stop-Failure "no nodes matched the selection"
}
$uniqueNodes = New-Object System.Collections.Generic.List[string]
foreach ($node in $targetNodes) {
if (-not (Test-DnsSubdomain $node)) {
Stop-Usage "node name '$node' is invalid"
}
if (-not $uniqueNodes.Contains($node)) {
$uniqueNodes.Add($node)
}
}
$effectiveFilter = $TcpdumpFilter
if ($podIpTerms.Count -gt 0) {
$podFilter = "(" + ($podIpTerms -join " or ") + ")"
$effectiveFilter = if ($effectiveFilter) {
"($effectiveFilter) and $podFilter"
} else {
$podFilter
}
}
$randomBytes = New-Object byte[] 12
$rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
try {
$rng.GetBytes($randomBytes)
} finally {
$rng.Dispose()
}
$runToken = -join ($randomBytes | ForEach-Object { $_.ToString("x2") })
$timestamp = (Get-Date).ToUniversalTime().ToString("yyyyMMdd-HHmmss")
$runId = "$timestamp-$runToken"
$captureOutputPath = "$OutputBase/$Name/$runId"
$createdJobs = New-Object System.Collections.Generic.List[string]
function Remove-PartialJobs {
if ($createdJobs.Count -eq 0) {
return $true
}
& kubectl delete jobs -n $Namespace @($createdJobs) --ignore-not-found | Out-Null
return $LASTEXITCODE -eq 0
}
Write-Output "Creating capture '$Name' on $($uniqueNodes.Count) node(s)"
foreach ($node in $uniqueNodes) {
$nodeSlug = (($node -replace "[._]", "-").Substring(0, [Math]::Min(40, $node.Length))).TrimEnd("-")
$captureSlug = $Name.Substring(0, [Math]::Min(15, $Name.Length)).TrimEnd("-")
$nodePrefix = $nodeSlug.Substring(0, [Math]::Min(8, $nodeSlug.Length)).TrimEnd("-")
$jobPrefix = "$captureSlug-$nodePrefix-$runToken-"
$jobYaml = @"
apiVersion: batch/v1
kind: Job
metadata:
generateName: $jobPrefix
namespace: $Namespace
labels: { app: aks-network-capture, capture-id: "$Name", capture-node: "$nodeSlug", capture-run: "$runId" }
spec:
ttlSecondsAfterFinished: 3600
backoffLimit: 0
template:
metadata:
labels: { app: aks-network-capture, capture-id: "$Name", capture-run: "$runId" }
spec:
hostNetwork: true
nodeName: "$node"
restartPolicy: Never
terminationGracePeriodSeconds: $MaxDurationSeconds
containers:
- name: capture
image: "$CaptureImage"
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsUser: 0
capabilities:
drop: ["ALL"]
add: ["NET_ADMIN", "NET_RAW"]
env:
- { name: PCAP_FILTER, value: "$effectiveFilter" }
- { name: CAPTURE_DURATION, value: "$durationSeconds" }
- { name: PACKET_SIZE, value: "$packetSizeValue" }
- { name: OUT_DIR, value: "$CaptureMountPath" }
- { name: CAPTURE_ID, value: "$Name" }
- { name: RUN_ID, value: "$runId" }
- { name: NODE_NAME, valueFrom: { fieldRef: { fieldPath: spec.nodeName } } }
command: ["/bin/sh", "/capture-scripts/run-capture.sh"]
volumeMounts:
- { name: capture-output, mountPath: "$CaptureMountPath" }
- { name: capture-scripts, mountPath: /capture-scripts, readOnly: true }
volumes:
- name: capture-output
hostPath: { path: "$captureOutputPath", type: DirectoryOrCreate }
- name: capture-scripts
configMap:
name: $ConfigMapName
defaultMode: 0555
"@
$createdJobOutput = ($jobYaml | & kubectl create -f - -o 'jsonpath={.metadata.name}' 2>&1) -join "`n"
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($createdJobOutput)) {
$cleanupSucceeded = Remove-PartialJobs
if (-not $cleanupSucceeded) {
Stop-Failure "failed to create a Job for '$node' and failed to remove partial Jobs"
}
Stop-Failure "failed to create a capture Job for '$node': $createdJobOutput"
}
$createdJobs.Add($createdJobOutput.Trim())
Write-Output "Job created for node: $node ($($createdJobOutput.Trim()))"
}
Write-Output ""
Write-Output "Capture started."
Write-Output "Run ID: $runId"
Write-Output "Monitor: kubectl get jobs -n $Namespace -l capture-id=$Name -w"
Write-Output "Retrieve: ./scripts/retrieve-captures.ps1 -Name $Name -RunId $runId -Namespace $Namespace"
Write-Output "Bundles: $captureOutputPath"
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Microsoft
- Publisher
- Microsoft (Official)
- Source
- microsoft/skills · .github/plugins/aks-skills/skills/aks-network-capture · commit 0696600
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from .github/plugins/aks-skills/LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Microsoft.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download aks-network-capture.zip