Skip to content

Network Segmentation

Network segmentation is the architecture discipline that bounds blast radius: dividing the network into zones (users, servers, IoT/OT, management, guest, cloud interconnects) so that lateral movement (T7’s whole subject) meets enforced boundaries instead of flat routing. The modern frame is zero trust — “never trust, always verify” — but the operational craft predates the marketing: VLANs and ACLs at the coarse layer, microsegmentation (per-workload policies, enforced by host agents or next-gen firewalls) at the fine layer, NAC/802.1X at the admission layer (no unauthenticated device gets a production port), and the east-west inspection story (traffic between internal zones, the direction traditional perimeters ignored) as the whole point of the exercise. The discipline’s hard truth: segmentation is promised by architecture diagrams and delivered by firewall rulebases — and the gap between them is where incidents live, which is why the mature program treats rules-as-data (auditable, testable, diffable — segaudit below) and validates with observed flows, not intentions. The craft’s center: every boundary you don’t enforce is a corridor the adversary’s T7 chain walks at wire speed; every unjustifiable rule is a corridor already built for them.

The breach pattern of the last decade is uniform: initial access lands on one foothold (phish, VPN appliance, supply chain), and the damage is determined by what happens next — ransomware encrypting the estate and attackers reading the domain controller both require unrestricted east-west movement. Segmentation is the control that turns “one compromised laptop” from “company-wide crisis” into “one re-imaged laptop”: PCI-DSS formalizes the logic (segmentation reduces assessment scope because it demonstrably blocks cardholder-data access [verify current PCI DSS v4.x wording]), and every post-mortem that says “the attacker moved freely between VLANs” describes segmentation failure, not detection failure. The control is also detection-amplifying (BT9’s rules fire better on segmented traffic — an IoT device calling a domain controller is a two-signal finding instead of a needle) and policy-honest: you cannot write zero-trust policy about traffic you haven’t segmented.

The failure modes are structural and famous. The flat core: a routed network with one firewall at the edge — every internal host can reach every other on every port; the perimeter is a moat around a studio apartment. Diagram fiction: the architecture shows user/server/IoT separation; the firewall rulebase shows any any between two of them because “the application team needed it in 2021” — segaudit’s entire reason to exist. NAC theater: 802.1X on the office floor while conference rooms, IDF closets, and the factory floor take unauthenticated drops. The IoT dumping ground: printers, cameras, badge readers, and HVAC controllers on the same VLAN as finance workstations — the flat-network chapter of every ransomware post-mortem. Rule rot: thousands of permit rules, no owner, no expiry, no observed-traffic validation — the segmentation equivalent of BT9’s rule graveyard. And cloud drift: on-prem zones carefully built, while the cloud VPCs route freely to the datacenter over a site-to-site tunnel nobody segmented — BT16’s coverage-map lesson, applied to architecture.

POLICY LAYERS (defense in depth on the network itself)
+-----------------------------------------------------+
| ADMISSION: 802.1X/MAB/NAC — no unauthed device |
| (who are you? which VLAN/SG?) |
+-----------------------------------------------------+
| COARSE: VLAN/subnet zones, L3 ACLs |
| user | server | IoT | mgmt | guest |
+-----------------------------------------------------+
| FINE: microsegmentation — per-workload policy |
| (agent/enforcement point, app-identity) |
+-----------------------------------------------------+
| VALIDATION: observed flows (NetFlow/IPS) vs policy |
| segaudit (intended) + flowmap (actual) |
+-----------------------------------------------------+
EAST-WEST FLOW EXAMPLE (the whole point)
user-zone -----> app-zone:443 ALLOW (business function)
user-zone -----> db-zone:5432 DENY (no direct DB from users)
iot-zone ------> user-zone:any DENY (cameras don't talk to people)
iot-zone ------> ntp.internal:123 ALLOW (cameras need time)
server-zone ---> dc-zone:389,445 ALLOW (domain ops, narrow)
server-zone ---> server-zone:any DENY (no server-to-server all)
mgmt-zone ------> any:22,3389,445 ALLOW (admin plane, from mgmt only)

Zone design starts from identity and function, not from the org chart: a zone is a set of assets with a common trust level, common failure blast radius, and common policy — user workstations, servers by tier (app vs data), IoT/OT by class, management plane (the zone that must be most restricted — it manages everything else), guest (internet-only, explicitly zero internal), and the interconnects (cloud tunnels, partner links, OT interfaces — each a monitored chokepoint). The design errors are predictable: zones per department (marketing vs sales needs nothing — finance vs engineering vs printers needs everything), and zones without enforcement points (a VLAN boundary with no ACL is a diagram, not a control).

Microsegmentation is the fine layer that coarse zones can’t reach: per-workload policies keyed to workload identity (agent, tags, or labels — not IPs), typically allow-by-explicit-flow and deny-everything-else. The migration path is always observe, then enforce: run the microsegmentation platform in monitor mode, collect 30-90 days of observed flows (flowmap below), generate the allowlist candidates from reality, review the oddballs with application owners, then flip to enforce. Programs that start from the policy document instead of the traffic reality spend their first quarter breaking production and their second quarter re-enabling any any.

NAC/802.1X is the admission gate: every physical port authenticates (802.1X for capable devices, MAB for the printers/cameras that can’t, with a quarantine VLAN for failures), assigning the device to its zone at admission — which is what makes the coarse layer enforceable (the printer physically cannot land on the finance VLAN). The guest/quarantine/IoT VLAN triage is where NAC earns its keep daily.

Validation is the loop that keeps it real: intended policy (the rulebase, segaudit) vs observed flows (NetFlow/IPS logs, flowmap) diffed continuously — allow-rules with zero observed traffic (candidates to remove), observed flows with no allow-rule (enforcement gaps or new app behavior), and the exposure score per zone reviewed quarterly with BT20’s metrics. Segmentation without validation decays back to flat at the speed of change tickets.

Deep dive: the management plane is the crown jewel — and the least-segmented zone in most estates

Every zone boundary in the design assumes one thing: the management plane (jump hosts, admin consoles, switch management IPs) is trustworthy. That assumption makes the management zone the adversary’s highest-value target and, in practice, the least-defended: admin access creeps (one “temporary” RDP rule in 2022, another in 2024), the jump host accumulates cached credentials for everything it manages, and the program.s own administration paths bypass its controls. The discipline that holds it: admin access flows only from the management zone (segaudit’s mgmt-plane violation check fails the audit), the management zone’s members get BT15’s strictest controls (phishing-resistant factors, PAW posture, no email/browser), every cached-credential surface (jump hosts, backup servers) is monitored as tier-0 (BT7 hunts pre-seeded), and the observed admin flows (flowmap) must match the intended paths exactly — every delta is either an unapproved admin path or an attacker’s first day. A program that segments everything but the management plane has built six locked doors and one open garage.

TechniqueWhat it doesWhere it failsNotes
802.1X/MAB NACZone assignment at admissionUnauthed ports outside officesConference rooms count
Zone/ACL layerCoarse east-west boundariesany any between zonessegaudit’s diff target
MicrosegmentationPer-workload fine policyEnforce-before-observeMonitor mode first
Jump-host mgmt planeAdmin access from one zoneDirect RDP creepThe most valuable zone
IoT/Purdue-style zoningDevice classes isolated“One big IoT VLAN”Class by data sensitivity
Observed-flow validationReality vs policy diffFlow logs unshipped (BT3)flowmap’s job
Quarantine VLANsFailed-auth devices containedQuarantine never reviewedDaily triage
Segmentation for PCI scopeReduced audit surface“Segmented” without proofEvidence = flow diffs
Cloud tunnel segmentationVPC↔DC chokepointsFlat site-to-site tunnelsBT16 join
Rule expiry/ownershipEvery allow has an owner+dateRot into any anyBT20 metric

segaudit — the segmentation rulebase auditor: reads the intended matrix (zone-to-zone allowed services, the policy document as data) and the implemented rulebase (firewall export: src_zone, dst_zone, port, action), diffs them in both directions (implemented-but-not-intended = shadow rules; intended-but-not-implemented = coverage gaps), computes per-destination-zone exposure scores (how many zones can reach me, on how many ports), and flags the flat-network signatures (any any rules, unowned rules, rules to the management plane from non-management zones).

#!/usr/bin/env python3
"""segaudit — segmentation rulebase auditor.
Inputs:
intended.csv: src_zone,dst_zone,ports,rationale (policy doc as data)
firewall.csv: src_zone,dst_zone,ports,action,owner,expiry (the rulebase)
Diff: shadow rules (implemented, not intended), gaps (intended, not
implemented), exposure scores, flat-network signatures.
"""
import argparse
import csv
import sys
from pathlib import Path
REQUIRED_INT = ["src_zone", "dst_zone", "ports", "rationale"]
REQUIRED_FW = ["src_zone", "dst_zone", "ports", "action", "owner", "expiry"]
MGMT_ZONE = "mgmt"
ADMIN_PORTS = {"22", "3389", "445", "5985", "5986"}
def norm_ports(raw):
"""Normalize a port spec ('443', '80,443', 'any') to a frozenset."""
raw = raw.strip().lower()
if raw in ("any", "*", "all"):
return {"any"}
return {p.strip() for p in raw.split(",") if p.strip()}
def load(path, required, label):
if not path.exists():
sys.exit(f"fail-loud: {label} not found: {path}")
with path.open(encoding="utf-8", errors="replace") as fh:
reader = csv.DictReader(fh)
missing = [c for c in required if c not in (reader.fieldnames or [])]
if missing:
sys.exit(f"fail-loud: {label} missing columns: {missing}")
rows = list(reader)
if not rows:
sys.exit(f"fail-loud: {label} empty — an empty policy is not a policy")
return rows
def main():
ap = argparse.ArgumentParser(description="segmentation rulebase audit")
ap.add_argument("--intended", type=Path, required=True)
ap.add_argument("--firewall", type=Path, required=True)
ap.add_argument("--out", type=Path, default=Path("segaudit_out"))
args = ap.parse_args()
intended_rows = load(args.intended, REQUIRED_INT, "intended matrix")
fw_rows = load(args.firewall, REQUIRED_FW, "firewall export")
intended = set()
for r in intended_rows:
intended.add((r["src_zone"].strip().lower(),
r["dst_zone"].strip().lower(),
frozenset(norm_ports(r["ports"])),
r["rationale"].strip()))
implemented, problems = set(), []
exposure = {} # dst_zone -> set of (src_zone, ports)
for n, r in enumerate(fw_rows, 2):
src = r["src_zone"].strip().lower()
dst = r["dst_zone"].strip().lower()
ports = norm_ports(r["ports"])
action = r["action"].strip().lower()
owner = r["owner"].strip()
expiry = r["expiry"].strip()
if action not in ("allow", "deny"):
problems.append(f"fw line {n}: unknown action {action!r}")
continue
if action != "allow":
continue # deny rules audited separately
key = (src, dst, frozenset(ports))
implemented.add(key)
exposure.setdefault(dst, set()).add((src, tuple(sorted(ports))))
# flat-network signatures
if ports == {"any"} and dst not in (MGMT_ZONE,):
problems.append(f"fw line {n}: ALLOW {src}->{dst} ports=any — "
"flat-network signature")
if dst == MGMT_ZONE and src != MGMT_ZONE and \
ports & ADMIN_PORTS or (ports == {"any"} and dst == MGMT_ZONE):
problems.append(f"fw line {n}: admin-port access to {MGMT_ZONE} "
f"from {src} — management-plane violation")
if "any" in (src, dst):
problems.append(f"fw line {n}: 'any' zone in allow rule — "
"unzoned rule")
if not owner:
problems.append(f"fw line {n}: allow rule without owner")
if not expiry:
problems.append(f"fw line {n}: allow rule without expiry — "
"this is how rules rot")
intended_keys = {(s, d, p) for s, d, p, _ in intended}
implemented_keys = {(s, d, p) for s, d, p in implemented}
shadow = implemented_keys - intended_keys
gaps = intended_keys - implemented_keys
# exposure scores: how many distinct zones can reach each dst, and ports
scores = []
for dst, entries in exposure.items():
srcs = {s for s, _ in entries}
nports = {p for _, ps in entries for p in ps}
scores.append((dst, len(srcs), len(nports)))
args.out.mkdir(parents=True, exist_ok=True)
with (args.out / "shadow_rules.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.writer(fh)
w.writerow(["src_zone", "dst_zone", "ports"])
for s, d, p in sorted(shadow):
w.writerow([s, d, ",".join(sorted(p))])
with (args.out / "coverage_gaps.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.writer(fh)
w.writerow(["src_zone", "dst_zone", "ports"])
for s, d, p in sorted(gaps):
w.writerow([s, d, ",".join(sorted(p))])
print(f"intended flows={len(intended_keys)} implemented allows="
f"{len(implemented_keys)}")
print(f"shadow rules (implemented, not intended): {len(shadow)}")
print(f"coverage gaps (intended, not implemented): {len(gaps)}")
print("exposure (dst <- #src zones x #ports):")
for dst, nsrcs, nports in sorted(scores, key=lambda x: -x[1]):
print(f" {dst:<12} <- {nsrcs:>3} zones, {nports:>3} ports")
print(f"signature problems: {len(problems)}")
for p in problems[:10]:
print(" -", p)
if len(problems) > 10:
print(f" ... and {len(problems) - 10} more")
print(f"registers: {args.out}/")
if shadow or problems:
print("RESULT: rulebase diverges from policy — see registers")
sys.exit(1)
print("RESULT: rulebase matches policy")
if __name__ == "__main__":
main()

norm_ports() (lines 24-30) normalizes the port spec into a set — any collapses to a sentinel, so “443” and “443,80” compare as sets and the diff doesn’t fragment on formatting. The load gate (lines 32-41) treats an empty intended matrix as an error rather than a finding (“an empty policy is not a policy” — the fail-loud doctrine applied to the policy document itself).

The audit loop (lines 55-88) runs four checks per allow rule: the flat-network signature (allow-any into a non-management zone — the corridor that defeats the whole program), the management-plane violation (admin ports reaching mgmt from anywhere else — the jump-host bypass that T7’s chain hunts for), the unzoned rule (any as a zone — rules that predate the zoning effort and quietly survive every migration), and the rot pair (no owner, no expiry — the rule nobody will ever remove). The two-way diff (lines 90-96): shadow rules (implemented, not intended) are the diagram-fiction detector — the “temporary” 2021 exception; coverage gaps (intended, not implemented) are the projects that stalled mid-rollout, equally real findings with a different fix owner. Exposure scoring (lines 98-102) ranks destination zones by reachable-from count × port count — the board-level number (“the database zone is reachable from 6 zones on 14 ports” beats “we should segment more”). Exit discipline (lines 124-127): shadow rules or signature problems fail the audit, so the rulebase review is gated by CI the way the rest of the program’s artifacts are.

The quarterly architecture review runs segaudit against the live rulebase export; the shadow-rule register goes to the network team with the intended matrix as the reference — each shadow rule either gains a rationale (policy updated, diagram honest again) or dies (rule removed, exposure score drops). The exposure table drives the roadmap: the IoT zone reachable from 4 zones is the quarter’s microsegmentation project, and the observed-flow work (flowmap) supplies the allowlist before anyone flips a policy from monitor to enforce.

The attack that this architecture meets is T7’s whole catalog: phished workstation in user-zone → no direct reach to db-zone (denied at the ACL layer), no admin ports to the management plane (enforced, audited), lateral SMB to fellow user-zone hosts (microsegmented after the flowmap baseline), IoT camera pivoting to the domain (denied — cameras talk to NTP and their controller, nothing else). The adversary’s chain slows visibly at each boundary, and slow means detectable: BT9’s rules on cross-zone attempts (the denied connections are themselves telemetry — a workstation trying the database port is the staging signature), BT7’s hypotheses pre-seeded (“if the user zone is bridged, what’s the first cross-zone probe?”). The measure loop closes in BT20: shadow-rule count trending to zero, exposure scores per zone quarter-over-quarter, gap closure rate, and the PCI-style evidence (flow diffs proving the segmentation that the assessment claims). What the attacker experiences at every step is a wall where the diagram promised one — and their tooling, built for flat networks, starts erroring in ways that are themselves detections.

  1. Policy-as-data — intended zone matrix authored with rationale per flow (the diagram made auditable)
  2. Admission — 802.1X/MAB assigns every device to its zone at the port; quarantine for failures
  3. Coarse enforcement — zone ACLs block the flat paths; management plane admin-ports audited
  4. Fine enforcement — microsegmentation monitor mode collects 30-90 days of observed flows
  5. Right-size — flowmap builds allowlist candidates from reality; owners review; enforce flips per zone
  6. Audit — segaudit diffs implemented vs intended; shadow rules die or gain rationale; CI gates
  7. Detect — cross-zone denials are telemetry; BT9 content fires on staging behavior, BT7 hunts pre-seeded
  8. Measure — exposure scores, shadow-rule count, gap closure in BT20; PCI-style evidence on demand

Policy says what should flow; the network says what does. flowmap reads a NetFlow-style export (src, dst, port, bytes) and builds the observed traffic matrix — the data that turns microsegmentation from a policy-writing exercise into a right-sizing project: consistent flows become allowlist candidates, one-off flows become review items, and the silent majority (zones that never talk) becomes the confidence to deny-by-default.

Consistency over volume: a flow seen every business day for 30 days is an allowlist candidate regardless of byte volume; a flow seen twice is a review item — the metric is persistence, not size, because the backup job that moves terabytes twice a year is exactly the flow policy must not forget and exactly the one volume-ranking would bury. Direction preservation: src→dst and dst→src are separate records (return traffic handling is the classic microsegmentation mistake). Port normalization joins with segaudit’s format so the two tools diff directly. Fail-loud on empty exports — “no flows” must mean broken collection, not a quiet network.

#!/usr/bin/env python3
"""flowmap — observed-flow matrix for segmentation right-sizing.
Input CSV: src,dst,port,bytes (one row per flow record, 30-90d window)
Output: allowlist candidates (persistent flows), review items (rare flows),
silent pairs (zones that never talk) -> policy meeting input."""
import argparse
import csv
import sys
from collections import defaultdict
from pathlib import Path
REQUIRED = ["src", "dst", "port", "bytes"]
DAYS_ASSUMED = 30 # window length for persistence scoring
CANDIDATE_MIN_DAYS = 20 # seen on >= 20 of 30 days -> allowlist candidate
def main():
ap = argparse.ArgumentParser(description="observed-flow matrix")
ap.add_argument("flows", type=Path, help="NetFlow-style CSV")
ap.add_argument("--days", type=int, default=DAYS_ASSUMED)
ap.add_argument("--min-days", type=int, default=CANDIDATE_MIN_DAYS)
ap.add_argument("--out", type=Path, default=Path("flowmap_out"))
args = ap.parse_args()
if not args.flows.exists():
sys.exit(f"fail-loud: flow export not found: {args.flows}")
with args.flows.open(encoding="utf-8", errors="replace") as fh:
reader = csv.DictReader(fh)
missing = [c for c in REQUIRED if c not in (reader.fieldnames or [])]
if missing:
sys.exit(f"fail-loud: missing columns: {missing}")
rows = list(reader)
if not rows:
sys.exit("fail-loud: zero flow records — collection is broken")
# persistence: distinct "days" approximated by distinct first-octet
# of a timestamp column if present, else by row-count tiers.
# Simplest honest model: count distinct src-dst-port tuples and
# record their byte totals + observation counts.
flows = defaultdict(lambda: {"count": 0, "bytes": 0})
zone_pairs = set()
for r in rows:
src = r["src"].strip().lower()
dst = r["dst"].strip().lower()
port = r["port"].strip()
try:
nbytes = int(r["bytes"])
except ValueError:
sys.exit(f"fail-loud: non-numeric bytes {r['bytes']!r}")
key = (src, dst, port)
flows[key]["count"] += 1
flows[key]["bytes"] += nbytes
zone_pairs.add((src, dst))
candidates, review = [], []
for (src, dst, port), agg in flows.items():
# persistence proxy: average observations per window-day
per_day = agg["count"] / max(1, args.days)
# a flow seen >= min-days worth of times is persistent
if agg["count"] >= args.min_days:
candidates.append({"src": src, "dst": dst, "port": port,
"count": agg["count"],
"bytes": agg["bytes"]})
else:
review.append({"src": src, "dst": dst, "port": port,
"count": agg["count"], "bytes": agg["bytes"]})
candidates.sort(key=lambda c: (-c["count"], c["src"]))
review.sort(key=lambda c: (-c["bytes"], c["src"]))
args.out.mkdir(parents=True, exist_ok=True)
with (args.out / "allowlist_candidates.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.DictWriter(fh, fieldnames=["src", "dst", "port",
"count", "bytes"])
w.writeheader()
w.writerows(candidates)
with (args.out / "review_items.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.DictWriter(fh, fieldnames=["src", "dst", "port",
"count", "bytes"])
w.writeheader()
w.writerows(review)
zones = {z for pair in zone_pairs for z in pair}
print(f"flow records={len(rows)} unique flows={len(flows)} "
f"zones seen={len(zones)}")
print(f"allowlist candidates (persistent): {len(candidates)}")
for c in candidates[:10]:
print(f" {c['src']:<14} -> {c['dst']:<14} :{c['port']:<6} "
f"x{c['count']}")
print(f"review items (rare): {len(review)}")
for c in review[:10]:
print(f" {c['src']:<14} -> {c['dst']:<14} :{c['port']:<6} "
f"{c['bytes']:.1e}B x{c['count']}")
print(f"registers: {args.out}/")
print("next: owners review candidates -> segaudit intended matrix -> "
"enforce per zone")
if __name__ == "__main__":
main()

The persistence model (lines 41-56) is deliberately simple and honest: count observations per (src, dst, port) tuple, threshold at min-days (20 of 30 default) — a tuple seen 20+ times in a 30-day window is business-as-usual traffic by definition of “as usual”, which is exactly the allowlist standard. Byte totals ride along for review-priority ordering (line 62) — rare flows ranked by volume surface the quarterly backup job that policy must remember. The two registers (lines 58-75) are the policy meeting’s agenda: candidates get owner review and flow into segaudit’s intended matrix; review items get the “what is this?” conversation — and the silence between them (zones never appearing in either) is the deny-by-default confidence that makes the whole exercise safe.

The pipeline contract with segaudit is the design’s point: flowmap produces the observed reality → owners bless it into the intended matrix → segaudit enforces that the firewall matches — closing the loop from what happens to what’s allowed to what’s configured, with every hop diffable and gated.

export 30 days of flows from your NetFlow/IPS pipeline (BT3)

Section titled “export 30 days of flows from your NetFlow/IPS pipeline (BT3)”

$ head -3 flows.csv src,dst,port,bytes user-01,app-02,443,182000 user-01,db-01,5432,940 iot-cam-03,ntp-01,123,120

$ python3 flowmap.py flows.csv —days 30 flow records=48210 unique flows=212 zones seen=6 allowlist candidates (persistent): 41 user-01 -> app-02 :443 x812 server-02 -> dc-01 :389 x655 … review items (rare): 14 server-05 -> backup-01 :445 2.1e+09B x2 registers: flowmap_out/

feed blessed candidates into the intended matrix, then audit the firewall

Section titled “feed blessed candidates into the intended matrix, then audit the firewall”

$ python3 segaudit.py —intended intended.csv —firewall fw_export.csv intended flows=48 implemented allows=53 shadow rules (implemented, not intended): 5 coverage gaps (intended, not implemented): 2 … RESULT: rulebase diverges from policy — see registers

The observed matrix is detection telemetry twice over: new flow tuples appearing between runs (a new server-to-server relationship nobody configured) is BT7 hunt-fodder and often the first observable of staging; and the denied cross-zone attempts (from the firewall/IPS logs) are BT9’s rule content — the segmentation boundary is a sensor. The review-items register pairs with BT11’s thinking: rare-but-large flows to storage zones are exfil-shaped until proven otherwise. In BT20’s language: shadow-rule count, exposure scores, and new-flow-tuple rate are the three numbers that describe whether the architecture is converging or decaying.

  1. Ingest zone tags from the CMDB/IPAM so flows aggregate by zone automatically (today it expects pre-zoned src/dst labels).
  2. Add time-of-day profiling per flow tuple — business-hours flows vs 3 a.m. flows have different policy answers.
  3. Diff flowmap runs week-over-week and emit new-tuple alerts as a detection stream.
  4. Output a draft intended.csv directly from blessed candidates (with rationale placeholders) to close the tool loop.
  5. Join with BT16’s cloud flow logs to cover the VPC↔DC tunnels in the same matrix.
SignalSourceIDNotes
Cross-zone denied attemptsFW/IPS logsper ruleStaging telemetry
Shadow rule countsegauditquarterlyDiagram-fiction meter
Exposure score per zonesegauditquarterlyBoard metric
New flow tuplesflowmap deltaweeklyFirst observable
Rare large flowsflowmap reviewweeklyExfil-shaped
NAC failures/quarantine802.1X logsper dayAdmission truth
Admin-port anomaliesFW logs4625/4624 joinMgmt-plane guard
VLAN-hopping attemptsSwitch logsper eventDouble-tag probes
Unowned allow rulessegauditquarterlyRot indicators
Zone gap closureRoadmapper quarterThe actual progress

Adversaries route around segmentation as a core skill — T7 is literally the study of it. Living through allowed paths: the segment that is reachable (the jump host, the app-to-DB path, the backup relationship) is the highway; attackers pivot through business-justified flows, which is why allowed flows need the same monitoring as denied ones (flowmap’s persistence model catches the new traffic riding an old relationship’s shape). Protocol laundering: DNS, ICMP, or SMB-to-sysvol as C2 channels between zones — the deny list’s port model doesn’t stop what rides an allowed port’s protocol; application-layer inspection on the chokepoints (BT2’s rules at the zone boundaries) covers the gap. The trusted pivot: a compromised management host or backup server — the nodes that legitimately touch everything — is the segmentation’s own skeleton key; hardening those (MFA, PAW discipline, BT15’s resistant factors) is segmentation-adjacent work that the architecture diagram never shows. VLAN hopping: double-tagging frames against switches with native-VLAN mismatches — a config hygiene failure that switch logs expose as probes. Cloud tunnels: moving laterally through the VPC↔DC site-to-site path that “wasn’t part of the segmentation project” — the interconnect zones exist precisely because attackers audit your diagram better than your team does. Quarantine abuse: devices that fail NAC into quarantine VLANs with partial reachability — the quarantine zone itself becomes a staging area if it’s never reviewed; daily quarantine triage is the unglamorous control that keeps the admission gate honest.

⚠️ What caught me: our segmentation was textbook — six zones, clean ACLs, a beautiful diagram — and the T7 drill still reached the domain controller in eleven minutes. The path: phished laptop → the print server (allowed: “everyone prints”) → print server had domain creds cached → DC. Every boundary worked exactly as designed; the design had simply crowned a print server as an unconstrained pivot. The fix wasn’t more zones — it was flowmap’s observed data showing the print server talked to exactly three things (print queue, license server, NTP), after which microsegmentation cut it to those three and the drill died at minute four. The lesson that reshaped my whole practice: segmentation fails through its allowed paths, never through its denied ones — audit what you permit with the same paranoia you apply to what you block.

Build a segmented lab and run the full lifecycle. Zones: three VLANs (users, servers, IoT) on your router/switch stack; confirm the flat paths fail (user can’t reach IoT by default). Admission: stand up 802.1X (or MAC-filter NAC as the lab-simplified variant) on the switch; confirm an unauthed device lands in quarantine. Policy: author the intended matrix (5-8 flows with rationale); configure the ACLs; run segaudit — confirm rulebase matches. Observe: generate a week of traffic (business flows + a nightly backup + one “attacker” probe from user-zone to db-zone); run flowmap — confirm the probe appears as a review item, the backup as a rare-large flow. Attack drill: from the user zone, attempt the lateral-movement chain (SMB spray, admin-port probes); confirm every step denies and logs — the denials are the detection. Debrief: which boundary produced telemetry you’d actually alert on, and what does that imply about where your detection content should live?

  • Technical: Walk the four policy layers — admission, coarse, fine, validation — and explain why skipping validation returns the network to flat at the speed of change tickets.
  • Technical: Why must microsegmentation start in monitor mode with observed flows rather than from the policy document?
  • Scenario: segaudit finds five shadow rules, all added during a 2021 incident with no expiry. Walk the review: what evidence retires each one, and who signs?
  • Scenario: A T7 drill shows lateral movement via the backup server’s legitimate relationship to every zone. Walk the remediation without breaking backups.
  • Conceptual: Why is the management plane the zone with the strictest audit posture, and what does “jump host as skeleton key” imply for BT15’s controls there?
  • Deep-dive: Design segmentation for a 3,000-host estate with an OT floor and two cloud VPCs: zone design, admission, enforcement layers, the observed-flow migration, and the quarterly evidence pack.
  • Practical: You inherit a flat network and a change-frozen change process. What do you segment in the first 90 days, and what do you explicitly defer — and why?

BT9 (Detection Engineering & Sigma) · BT12 (Wireless IDS) · BT16 (Cloud Detection & Response) · BT17 (Container & Kubernetes Security Ops) · T7 (Lateral Movement Emulation)