Claude Skill

sota-network-security

State-of-the-art network security as a discipline (2026) for BOTH designing/building secure networks AND auditing segmentation and exposure. Use for zero-trust architecture, network segmentation and blast-radius control, Kubernetes NetworkPolicy/CiliumNetworkPolicy depth, service

LLM Mart · 0 points · 8 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download martinholovsky-SOTA-skills-skills_sota-network-security-ec2abf6.zip · 36 KB
Part of martinholovsky/sota-skills — 39 skills

Install

skills CLI npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-network-security
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
Git git clone https://github.com/martinholovsky/SOTA-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole martinholovsky/sota-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

SOTA Network Security

Purpose

This skill encodes the 2026 state of the art for network security as a discipline: how to verify rather than trust, contain blast radius, encrypt traffic in motion, control what enters and leaves, and secure the naming and transport plumbing (DNS, TLS, PKI). Every rule exists to prevent a real failure class — lateral movement after one foothold, plaintext credentials on the wire, an over-broad rule that exposes a secrets store to the world, a renewal nobody automated, or an SSRF that reaches the cloud metadata endpoint.

Ownership — reference siblings, do not duplicate:

  • sota-cloud-infrastructure (rules/03 networking) owns cloud-provider network setup: VPC/subnet layout, CIDR/IPAM, route tables, LB/CDN provisioning, registrar hygiene, DNS zone setup. This skill owns the security posture layered on top and the on-prem / Kubernetes / mesh side.
  • sota-kubernetes owns admission control and RBAC — where NetworkPolicy is admitted and CNI enforcement is wired; this skill owns the policy content and depth.
  • sota-identity-access owns SPIFFE/SPIRE workload identity issuance, ZTNA user identity, and the identity-aware proxy's auth plane; this skill consumes those identities for network authorization.
  • sota-detection-engineering owns network IDS (Suricata), DNS-exfil detection, and flow-log detection content; this skill produces the telemetry (Hubble flows, flow logs) it consumes.
  • sota-sandboxing (rules/02–03) owns single-host nftables/seccomp and container hardening; this skill owns the inter-host / cluster-wide fabric.
  • sota-code-security (rules/01 SSRF, rules/05 CORS/CSP), sota-api-design (rules/07 rate limiting), sota-secrets-management (TLS private keys) — referenced where they intersect.

BUILD mode

Use when designing or extending a secure network (zero-trust plan, NetworkPolicy set, mesh rollout, ingress/egress controls, PKI/DNS posture, remote access).

  1. Establish context first: on-prem vs cloud vs hybrid; CNI and orchestrator (e.g. on-prem Talos K8s + Cilium); existing PKI (step-ca), edge (Caddy + CRS WAF, Cloudflare in front); data sensitivity; who needs remote access. A 3-node homelab and a regulated fleet get different answers from the same rules.
  2. Read the matching rules file before writing config. Segmentation (rules/02) precedes policy detail; identity-aware access (rules/01) frames everything.
  3. Default-deny in both directions, always. Ingress and egress deny by default, per namespace and per zone. Every allow is explicit, justified in a comment, and references identity (workload identity, label selector, SG/service account) — never a bare CIDR or world entity wherever the policy language can express identity. Where it genuinely cannot — vanilla NetworkPolicy has no identity selector for a destination outside the cluster — a tight ipBlock is a documented exception, not a default: name the destination and the reason, and prefer a CNI that can (rules/03 §3). Inside the cluster there is no exception: pod IPs are recycled, so a CIDR there silently re-points the allow.
  4. Encrypt every hop that crosses a trust boundary. No plaintext credentials, JWTs, or DB traffic on the pod/internal network — mTLS via mesh or TLS terminated close to the workload.
  5. State the failure mode and the blast radius of what you propose. "If this pod is popped, it can reach X and Y" belongs in the design, not the postmortem.
  6. Produce policy as code (NetworkPolicy/CiliumNetworkPolicy YAML, mesh AuthorizationPolicy, nftables, ACME/cert-manager manifests) — never click-ops, never "we'll lock it down later."

AUDIT mode

Use when reviewing an existing network for segmentation gaps and exposure.

Process: inventory the fabric (zones, namespaces, CNI policies, mesh config, ingress/egress paths, DNS zones, certs, remote-access entry points); walk the Audit checklist at the end of each relevant rules file; confirm reachability before reporting — render the effective policy, run a probe (kubectl exec ... curl, Hubble flow query, nmap), read the actual rule. Do not infer exposure from a resource name.

Severity conventions

Severity Meaning Examples
Critical External or any-workload party can reach a sensitive service or read traffic now Secrets store / DB / registry reachable from a world/0.0.0.0/0 entity; plaintext DB creds or JWTs on the wire (sniffable from any pod); SSH open to the internet on a prod host; admin/dashboard reachable unauthenticated from outside
High One foothold from broad lateral movement, or a guaranteed exposure/outage class "Default-deny" that actually allows all intra-cluster traffic; no egress control (free C2/exfil path); flat L2/L3 network with no segmentation; manual cert renewal on a public endpoint; mTLS in PERMISSIVE everywhere with plaintext still flowing
Medium Weakens containment, transport security, or recovery Ingress-only default-deny (egress still open); CIDR-based internal rules that rot on re-IP; TLS 1.0/1.1 or weak ciphers allowed; no FQDN egress filtering where it's warranted; WAF in detection-only mode; no Hubble/flow visibility
Low Hygiene, drift, headroom Inconsistent policy labels; over-scoped but internal-only allow; missing HSTS; no CAA record; DNSSEC undecided
Info Context for the reader, no action implied Mesh is overkill for a 2-service app (just use TLS); ANP/BANP still alpha — pin behavior

Severity = reachability (anonymous internet > any-workload east-west > same-namespace > insider) × impact (traffic read / sensitive-service compromise > lateral movement > availability).

Finding format

file:line | rule | severity | effort | fix
  • file:line — the policy/manifest/config and line (e.g. netpol/baseline.yaml:14, Caddyfile:30); for runtime-only findings name the resource (ns/payments | cilium effective).
  • rule — the rules-file rule id (e.g. rules/03 R4 or R-egress-default-deny).
  • severity — Critical / High / Medium / Low / Info.
  • effort — trivial / small / medium / large (eng effort to fix).
  • fix — the specific change (the policy diff, the directive, the mesh stanza).

Group repeated instances (e.g. 12 namespaces with no egress policy) into one finding with a count.

Rules index

File Read this when...
rules/01-zero-trust-architecture.md Establishing/auditing the model: never-trust-always-verify, PDP/PEP, identity-aware access over network location, ZTNA vs VPN, de-perimeterization, identity-aware proxy (BeyondCorp)
rules/02-segmentation-blast-radius.md Designing/auditing zones and tiers, north-south vs east-west, the flat-network and over-broad-rule (any/0.0.0.0/0/world) traps, microsegmentation, lateral-movement containment, firewall/SG default-deny, remote access (WireGuard, bastion vs IAP)
rules/03-k8s-network-policy.md Writing/auditing Kubernetes NetworkPolicy, CiliumNetworkPolicy, the namespaced default-deny (ingress AND egress) pattern, the "default-deny that isn't" trap, ANP/BANP, L7/identity policy, DNS-aware egress, egress gateways, Hubble visibility
rules/04-service-mesh-mtls.md The plaintext-internal-traffic problem, choosing/auditing a mesh (Istio sidecar vs ambient, Linkerd, Cilium mesh), mTLS everywhere, mesh authorization policy, SPIFFE identity, and deciding mesh vs plain TLS
rules/05-edge-ingress-egress.md WAF (CRS/Coraza), ingress/API-gateway hardening, TLS termination + re-encryption, trusted-IP handling behind Cloudflare, DDoS posture (edge scrubbing + self-hosted kernel hardening: SYN cookies/synproxy, conntrack, rp_filter, no open UDP reflectors), egress as a first-class control, FQDN allowlisting, blocking the metadata endpoint, the SSRF-meets-egress chain
rules/06-dns-tls-pki.md DNS security (DNSSEC, RPZ/DNS firewall, DoH/DoT, split-horizon, CAA, tunneling), TLS posture (1.3, ciphers, HSTS, OCSP), shrinking cert lifetimes + ACME automation, internal PKI (step-ca), short-lived certs, pinning tradeoffs, email authentication & anti-spoofing (SPF/DKIM/DMARC, MTA-STS/DANE, bulk-sender rules)

Cross-cutting tasks read multiple files: a full network audit touches all six; "lock down our cluster" is rules/02 + rules/03 (+ rules/04 if a mesh exists).

Top 10 non-negotiables

  1. Verify, don't locate-trust. Access decisions bind to authenticated identity (workload or user) and posture, not to "it's on the internal network." A packet's source subnet is not a credential. (NIST SP 800-207; CISA ZTMM v2.0.)
  2. Default-deny in BOTH directions. Every namespace/zone denies ingress and egress by default; allows are explicit and identity-scoped. An ingress-only default-deny leaves the exfil door open.
  3. No any / 0.0.0.0/0 / world to sensitive services. A secrets store, DB, registry, or admin UI reachable from a broad entity is a Critical finding — render the effective rule and prove the path, don't trust the rule's name.
  4. The "default-deny" must actually deny. A baseline policy that allows all intra-cluster traffic is not default-deny; verify with a probe (cross-namespace curl should fail).
  5. Encrypt internal traffic. DB creds, JWTs, and app traffic crossing the pod/host network ride mTLS or TLS — never plaintext ws:///http:///unencrypted DB protocol. Mesh in STRICT, not permissive-forever.
  6. Egress is a control, not a default-open pipe. Default-deny egress, FQDN/IP allowlists, egress gateways/proxies for sensitive zones; block the cloud metadata endpoint (169.254.169.254, fd00:ec2::254) at the pod/host. This is the C2/exfil and SSRF-pivot chokepoint.
  7. Microsegment east-west. Contain blast radius so one popped workload can reach only its declared dependencies. Flat networks turn a single foothold into a cluster-wide incident.
  8. Identity-aware access for humans. ZTNA / identity-aware proxy (per-request identity + device posture) over flat VPN access; if VPN, WireGuard with per-peer keys, never SSH open to the world.
  9. Certs are automated and short-lived. Every cert (public and internal) is ACME/cert-manager issued and auto-renewed. CA/Browser Forum caps public certs at 200 days (2026-03-15) → 47 days (2029-03-15); manual renewal is now an outage generator.
  10. Flows are visible. You can answer "who talked to whom" — Hubble / flow logs / mesh telemetry on, exported to detection (sota-detection-engineering). You cannot secure traffic you can't see.

Operating notes

  • Principles first, then the user's stack (Talos K8s + Cilium, step-ca, Caddy+CRS, Cloudflare); name alternatives when the stack is unknown.
  • Verify versions and API status against current docs before committing them — CNI features, mesh GA status, the ANP/BANP/ClusterNetworkPolicy API state, CRS version, and the CA/B cert schedule all move faster than this text. Be version-agnostic where a claim is unpinnable.
  • When this skill and a compliance mapping conflict, state both; do not silently relax a control.
Files (sota-skills)
  • rules
    • 01-zero-trust-architecture.md 8 KB
      # 01 — Zero-Trust Architecture
      
      Scope: the model that frames every other rule — never trust, always verify; policy decision/
      enforcement points; identity-aware access over network-location trust; microsegmentation as
      strategy; ZTNA vs traditional VPN; de-perimeterization; the identity-aware (BeyondCorp-style) proxy.
      This file is the *why*; rules/02–06 are the *how*.
      
      Anchors (verified 2026-07-09): **NIST SP 800-207** "Zero Trust Architecture" (Aug 2020) is the
      definitive reference; **NIST SP 1800-35** "Implementing a Zero Trust Architecture" (final, Jun 2025)
      is its practical companion — NCCoE example builds mapped to 800-207, useful in BUILD mode; **CISA
      Zero Trust Maturity Model v2.0** (Apr 2023, current) gives the maturity ladder across five pillars —
      Identity, Devices, Networks, Applications & Workloads, Data.
      
      ---
      
      ## 1. The core tenet: location is not a credential
      
      **R1 — Trust is never granted by network position.** The classic perimeter model trusts anything
      "inside" the firewall. Zero trust assumes the network is already hostile (the attacker may be on it)
      and grants access per-request, per-resource, based on *authenticated identity + device/workload
      posture + context*, re-evaluated continuously. Being on the corporate LAN, the VPN, or the pod
      network confers nothing.
      
      Practical consequences that recur as findings:
      - A service that authenticates callers only by source IP/subnet (`allow 10.0.0.0/8`) is trusting
        location. Any workload that lands in that range inherits the trust — this is how one popped pod
        reaches a DB.
      - "It's internal" is not a reason to skip TLS or authz. East-west traffic gets the same scrutiny as
        north-south (see rules/04 for the plaintext-internal-traffic failure).
      
      ## 2. PDP / PEP: the decision and enforcement split
      
      NIST SP 800-207 splits the control plane into a **Policy Decision Point (PDP)** — Policy Engine
      (decides allow/deny from identity, posture, threat signals) + Policy Administrator (issues the
      session token/credential) — and **Policy Enforcement Points (PEP)** that sit in the data path and
      let the connection through or not.
      
      **R2 — Every protected resource sits behind a PEP; the PEP consults a PDP.** Map your stack onto
      this so gaps are visible:
      
      | Plane | PDP (decides) | PEP (enforces) |
      |---|---|---|
      | User → app | IdP + access proxy policy engine (identity-access owns the IdP) | Identity-aware proxy / ZTNA gateway / mesh ingress gateway |
      | Workload → workload | Mesh control plane authz policy (SPIFFE identity) | Mesh sidecar / ztunnel; CNI (Cilium) policy enforcement |
      | Pod → pod (L3/4) | NetworkPolicy/CNP objects | CNI datapath (eBPF in Cilium) |
      | Host → host | Firewall/SG policy | nftables / cloud SG (sota-cloud-infrastructure setup) |
      
      A resource with no PEP in front of it is implicitly "trust everyone who can route to it" — find it.
      
      **R3 — Decisions are dynamic and context-aware where it matters.** The PDP should consume more than
      static identity: device posture, request risk, time, geo, prior behavior. Don't over-engineer a
      homelab, but for human access to crown-jewel systems, a static allow that never re-checks posture is
      a weaker control than the model promises.
      
      ## 3. Identity-aware access over network-location access
      
      **R4 — Authenticate the *who*, not the *where*.** For users: per-request identity from an IdP
      (OIDC/SAML), ideally + device trust. For workloads: cryptographic workload identity (SPIFFE SVID,
      mesh cert, cloud workload identity) — see sota-identity-access for issuance, rules/04 for mesh
      consumption. The network policy then references *identity* (label/SA/SPIFFE ID), not a CIDR that any
      new workload could land inside.
      
      ```yaml
      # BAD: location trust — any pod that gets this IP range reaches the DB
      - from: { ipBlock: { cidr: 10.0.0.0/8 } }   # "the internal network"
      
      # GOOD: identity trust — only the api service account, in this namespace
      - from:
        - podSelector: { matchLabels: { app: api } }
        # in Cilium, prefer endpointSelector / identity; in a mesh, the SPIFFE ID of the caller
      ```
      
      ## 4. De-perimeterization mindset
      
      **R5 — There is no single hard shell; there are many small ones.** Stop investing in a thicker
      perimeter and a soft interior. Push enforcement *to the workload*: every service is its own
      perimeter (its PEP), every namespace its own segment. The firewall at the edge still matters (DDoS,
      coarse filtering — rules/05), but it is one layer, not *the* control. This is why microsegmentation
      (rules/02) and mesh mTLS (rules/04) are zero-trust load-bearing, not nice-to-haves.
      
      ## 5. ZTNA vs traditional VPN
      
      **R6 — Prefer ZTNA / identity-aware proxy to flat VPN for human access.**
      
      | | Traditional VPN | ZTNA / identity-aware proxy |
      |---|---|---|
      | Grants | Network access (a route onto the LAN) | Access to a *specific application*, per request |
      | Trust after connect | Implicit — you're "inside" | None — every request re-authorized |
      | Blast radius if creds stolen | Whole reachable network | One app, posture-gated |
      | Model fit | Perimeter | Zero trust |
      
      A VPN that drops a user onto a flat internal network is a perimeter pattern wearing a crypto
      overcoat: one stolen credential = lateral movement. ZTNA (BeyondCorp-style) brokers each
      application individually, checks identity + device posture per request, and never exposes the
      network itself. **WireGuard still has a place** — for site-to-site links, machine-to-machine, or as
      the transport *under* an app-level access decision — but per-user "VPN onto the LAN" is the
      anti-pattern (see rules/02 §5 for the access-method decision).
      
      ## 6. Identity-aware proxy (BeyondCorp pattern)
      
      **R7 — Front internal web apps with an identity-aware proxy, not a network ACL.** The proxy
      (Cloudflare Access, Pomerium, oauth2-proxy + ingress, Teleport, cloud IAP) authenticates the user
      via the IdP, evaluates device/context policy, and only then forwards to the backend — which is *not*
      otherwise reachable. The user's identity-access skill owns the IdP and auth plane; this skill owns
      making the backend unreachable except through the PEP.
      
      The recurring failure: app reachable both via the IAP *and* directly on its cluster IP / a `world`
      NetworkPolicy entity. The direct path bypasses every check. The backend must accept traffic *only*
      from the proxy (mesh authz to the gateway identity, or a NetworkPolicy allowing only the
      ingress/proxy namespace) — verify by hitting the backend directly and confirming it's refused.
      
      ## 7. Maturity, not perfection (CISA ZTMM v2.0)
      
      **R8 — Place each pillar on the ladder and move it, deliberately.** ZTMM v2.0 grades each of the
      five pillars Traditional → Initial → Advanced → Optimal, with cross-cutting Visibility & Analytics,
      Automation & Orchestration, Governance. Use it to scope work: e.g. Networks pillar at "Initial"
      (macro-segmentation, some default-deny) → target "Advanced" (microsegmentation + dynamic policy).
      Don't claim "we did zero trust"; name the pillar and the level. The honest audit output is a
      per-pillar maturity placement with the next concrete step, not a binary.
      
      ## Audit checklist
      
      - [ ] Is any service authenticating callers by source IP/subnet alone (location trust)? Grep configs
            for broad `allow`/`trusted` CIDRs: `grep -rEn '10\.0\.0\.0/8|0\.0\.0\.0/0|allow .*internal'`.
      - [ ] Does every protected resource sit behind an identifiable PEP, or can something route straight
            to it? List Services/Ingresses and ask "what enforces authz here?"
      - [ ] Do network/mesh policies reference *identity* (label/SA/SPIFFE) rather than bare CIDRs for
            internal flows?
      - [ ] For human access: is it ZTNA / identity-aware proxy, or flat VPN onto the LAN? If VPN, is it
            per-user-keyed and scoped (not whole-network)?
      - [ ] Are identity-aware-proxied backends reachable *only* through the proxy? Probe the backend
            directly (`kubectl exec ... curl backend.svc`) — it must be refused.
      - [ ] Is access dynamic (re-evaluated, posture-aware) for crown-jewel systems, or a one-time static
            allow?
      - [ ] Can you state, per CISA ZTMM pillar, your current maturity level and the next step?
      
    • 02-segmentation-blast-radius.md 8.8 KB
      # 02 — Segmentation & Blast-Radius Containment
      
      Scope: network zones/tiers, north-south vs east-west, the flat-network anti-pattern, the over-broad
      `any`/`0.0.0.0/0`/`world` rule trap, microsegmentation, lateral-movement containment, choke points,
      stateful firewall/SG default-deny, and remote-access methods (WireGuard vs ZTNA, bastion vs
      identity-aware proxy, the SSH-open-everywhere anti-pattern). Kubernetes-specific policy depth is
      rules/03; this file is the topology/strategy and the host/edge firewall layer.
      
      ---
      
      ## 1. North-south vs east-west; segment both
      
      - **North-south** = traffic crossing the trust boundary (internet ↔ your network, client ↔ cluster).
        Historically the only thing firewalled.
      - **East-west** = traffic *inside* — service↔service, pod↔pod, host↔host. Where attackers move after
        the first foothold, and historically wide open.
      
      **R1 — Containment is an east-west property.** A hardened edge with a flat interior means one popped
      front-end pod can reach the database, the secrets store, and the registry. The whole point of
      segmentation is to make east-west reachability *declared*, so a foothold reaches only its
      dependencies. North-south hardening (rules/05) without east-west segmentation (rules/03) is half a
      control.
      
      ## 2. The flat-network anti-pattern
      
      **R2 — A flat network is a single blast radius.** Symptoms: every host/pod can reach every other on
      any port; "internal" = "trusted"; one VLAN/subnet/namespace for everything; security groups that
      allow the whole VPC CIDR to itself. Impact: lateral movement is free; one CVE, one stolen
      credential, one SSRF, and the incident is cluster-wide.
      
      Fix direction: carve **zones/tiers** with deny-by-default between them, then microsegment *within*
      zones (rules/03 for K8s, mesh authz for service-level).
      
      **R3 — Standard zone model (map your real topology onto it):**
      
      | Zone | Holds | Reachable from |
      |---|---|---|
      | Edge/DMZ | Reverse proxy, WAF, ingress, LB | Internet (north-south, 80/443 only) |
      | App / service tier | Stateless workloads | Edge + declared peers only |
      | Data / stateful tier | DBs, queues, secrets store, registry, PKI | *Only* the specific services that use them — never `any` |
      | Management | CI runners, bastion/IAP, observability | Tightly scoped admin paths |
      
      The data tier is the crown jewels. Reachability into it is the audit's first target.
      
      ## 3. The over-broad-rule trap (`any` / `0.0.0.0/0` / `world`)
      
      **R4 — A broad source/destination on a sensitive service is a Critical finding.** This is the most
      common real exposure. Examples seen in audits:
      - A Cilium/firewall rule letting the `world` entity (everything, including off-cluster/internet)
        reach OpenBao (secrets), Grafana, and the container registry.
      - A security group whose ingress is `0.0.0.0/0` on a DB port "for debugging."
      - An ingress allow of `any → any` that someone added to "make it work."
      
      **R5 — Prove reachability before downgrading severity, and prove the fix.** A rule *named* `restrict`
      that effectively allows the world is still Critical. Render the *effective* policy and probe:
      
      ```bash
      # Cilium: what can actually reach this endpoint?
      cilium policy get
      hubble observe --to-pod openbao/ -f          # are unexpected sources getting through?
      # Generic: from an unrelated pod, can you reach the sensitive service?
      kubectl -n scratch exec deploy/test -- sh -c 'curl -sm3 https://openbao.vault:8200/v1/sys/health && echo REACHABLE'
      # Firewall: hunt the broad rules
      grep -rEn '0\.0\.0\.0/0|::/0|\bany\b|\bworld\b' ./policies ./firewall
      ```
      
      A reachable secrets store / DB / registry / admin UI from a broad entity → Critical, fix is usually
      *small* (tighten the source to the one identity that needs it) but the exposure is severe.
      
      ## 4. Microsegmentation & choke points
      
      **R6 — Segment to the workload, then funnel cross-zone traffic through choke points.**
      - *Microsegmentation*: the unit of isolation is the workload/identity, not the subnet. Within the
        app tier, service A reaches service B only if declared. Enforced by CNI policy (rules/03) and/or
        mesh authz (rules/04).
      - *Choke points*: cross-zone traffic (app→data, internal→internet) passes through a small number of
        inspectable, enforceable points — an egress gateway/proxy (rules/05), a mesh waypoint, a firewall.
        Choke points are where you log, allowlist, and rate-limit. A topology with no choke points cannot
        be inspected or contained.
      
      **R7 — Right-size the segmentation effort.** Macro-segmentation (zones, deny between tiers) is the
      high-value baseline — do it everywhere. Full per-workload microsegmentation is *Advanced* (CISA
      ZTMM) — apply it first to the data tier and crown-jewel paths, then broaden. Don't let "perfect
      microsegmentation everywhere" block shipping the deny-between-tiers baseline.
      
      ## 5. Stateful firewall / security-group policy
      
      **R8 — Default-deny, identity/tag-referenced, audited for breadth.** (Host nftables for a single box
      is sota-sandboxing rules/02; cloud SG/VPC *setup* is sota-cloud-infrastructure rules/03 — this skill
      owns the *posture*.)
      - Deny inbound and outbound by default; allow specific flows.
      - Reference **security groups / tags / service accounts, not CIDRs**, for internal flows so rules
        survive re-IP (`sg-app → sg-db:5432`, not `10.2.0.0/16 → :5432`).
      - No `0.0.0.0/0`/`::/0` ingress except 80/443 on the edge tier. Audit IPv6 `::/0` exactly like
        IPv4 — every IPv6 address is globally routable, no NAT safety blanket.
      - eBPF-based enforcement (Cilium host firewall, Tetragon for L7/syscall visibility) scales better
        than iptables rule sprawl on busy nodes; where Cilium is the CNI it can enforce host-level policy
        too — verify the current stable line at the project's releases page rather than pinning one here.
      
      ## 6. Remote access: WireGuard vs ZTNA, bastion vs identity-aware proxy
      
      **R9 — SSH/RDP open to the internet is a finding, even "temporarily."** It is the perennial
      brute-force and 0-day target. There is always a better option.
      
      **R10 — Choose the access method by what's being accessed:**
      
      | Need | SOTA choice | Avoid |
      |---|---|---|
      | Human → internal *web* app | Identity-aware proxy / ZTNA (rules/01 §6) | Exposing the app; flat VPN |
      | Human → *shell* on a host | Identity-aware bastion (Teleport/IAP/SSM-style: per-session identity, recording, short-lived certs) | Static SSH keys; `0.0.0.0/0:22` |
      | Site-to-site / machine-to-machine | **WireGuard** (per-peer keys, modern crypto, in-kernel since Linux 5.6) | Legacy IPsec sprawl; bespoke tunnels |
      | Per-user "get on the network" | ZTNA (scoped to apps) | Flat VPN onto the LAN (rules/01 §5) |
      
      - **WireGuard discipline:** one keypair per peer (never shared), `AllowedIPs` scoped to exactly the
        destinations that peer needs (it is also the routing/ACL — a wide `AllowedIPs = 0.0.0.0/0` makes
        it a flat VPN again), rotate keys on offboarding, keys handled as secrets (sota-secrets-management).
      - **Bastion vs IAP:** a plain jump-host with shared SSH keys is barely better than direct SSH.
        Prefer an identity-aware bastion that issues short-lived per-session certs (your step-ca can back
        this), records sessions, and is itself fronted by the IdP. The bastion must be the *only* SSH
        path — hosts deny SSH from everywhere except the bastion's identity/SG.
      
      ## 7. Containment in depth
      
      **R11 — Assume one layer fails; have the next.** A defense-in-depth network has: edge filtering →
      zone deny-by-default → workload microsegmentation → mTLS authz → egress control. An attacker who
      clears the WAF still hits zone deny; who lands in the app tier still can't reach data; who reaches a
      service still needs a valid mTLS identity; who wants to exfil still hits egress allowlisting. Audit
      asks: *if this layer were bypassed, what's the next thing stopping lateral movement?* If the answer
      is "nothing," that's the finding.
      
      ## Audit checklist
      
      - [ ] Are zones/tiers defined with deny-by-default *between* them, or is the network flat? Probe
            cross-tier reachability (app pod → data tier on a non-declared port must fail).
      - [ ] Hunt over-broad rules: `grep -rEn '0\.0\.0\.0/0|::/0|\bany\b|\bworld\b' policies firewall`.
            For each hit touching a sensitive service (secrets/DB/registry/admin/PKI), prove reachability
            and rate Critical until fixed.
      - [ ] Do internal firewall/SG rules reference identities/tags/SGs, not bare CIDRs?
      - [ ] Is the data tier reachable only by the specific services that use it (not `any`, not the
            whole VPC/cluster CIDR)?
      - [ ] Are cross-zone flows funneled through inspectable choke points (egress gateway, firewall,
            mesh waypoint)?
      - [ ] Is SSH/RDP exposed to `0.0.0.0/0`? (`nmap`/SG scan for 22/3389 from outside → finding.)
      - [ ] Remote access: ZTNA/IAP for web, identity-aware bastion for shells, WireGuard (scoped
            `AllowedIPs`, per-peer keys) for site/machine links — not flat VPN, not shared keys?
      - [ ] For each control layer, is there a next layer if it's bypassed (defense in depth)?
      
    • 03-k8s-network-policy.md 14.5 KB
      # 03 — Kubernetes Network Policy Depth
      
      Scope: Kubernetes `NetworkPolicy`, `CiliumNetworkPolicy` (CNP/CCNP), the namespaced default-deny
      pattern (ingress AND egress), the "default-deny that isn't" trap, the cluster-scoped
      AdminNetworkPolicy / BaselineAdminNetworkPolicy (ANP/BANP) API, L7 + identity-based policy,
      DNS-aware egress, egress gateways, and Hubble flow visibility. Examples here assume a **Cilium**-based cluster (e.g. Talos K8s + Cilium).
      
      Where this sits: **sota-kubernetes** owns admission/RBAC and *that NetworkPolicy is admitted and the
      CNI is wired*; this skill owns the policy *content and depth*. sota-cloud-infrastructure rules/03
      owns the cluster's VPC/subnet/IPAM. sota-detection-engineering consumes Hubble flows.
      
      Verified (2026-07): Cilium fully implements the upstream `networking.k8s.io/v1` NetworkPolicy and
      adds L7 via Envoy — run the latest stable patch and verify the current release with a quick search
      at time of use (the 2026 patch train fixed policy-bypass CVEs; see §4). The sig-network working
      group merged ANP+BANP into a single **`ClusterNetworkPolicy` CRD at
      `policy.networking.k8s.io/v1alpha2`** (Oct 2025; a `tier` field selects Admin vs Baseline) — still
      ALPHA, out-of-tree; ANP/BANP `v1alpha1` remain usable at v0.1.7. Pin behavior; don't assume GA
      semantics.
      
      ---
      
      ## 1. The two failure modes this file exists to kill
      
      1. **No policy at all** — Kubernetes is allow-all by default. A namespace with zero NetworkPolicies
         lets every pod talk to every other pod, cluster-wide. Each such namespace is a flat segment.
      2. **The "default-deny that isn't"** — a baseline policy that *looks* restrictive but effectively
         allows all intra-cluster traffic (e.g. an allow-from `namespaceSelector: {}` matching every
         namespace, or an egress allow to `0.0.0.0/0`, or a "deny" policy that only covers ingress while
         egress stays open). This is more dangerous than no policy because it reads as "we're covered."
      
      **R1 — Always verify default-deny empirically.** Don't trust the policy's name or that one exists.
      Probe:
      
      ```bash
      # From an unrelated namespace, traffic to a target must be REFUSED if default-deny works.
      kubectl -n scratch run probe --rm -it --image=nicolaka/netshoot --restart=Never -- \
        sh -c 'curl -sm3 http://target.othernamespace:8080 && echo LEAKED || echo denied'
      # Egress test: can a pod reach the internet when it shouldn't?
      kubectl -n payments exec deploy/api -- sh -c 'curl -sm3 https://example.com && echo EGRESS_OPEN'
      ```
      
      ## 2. The namespaced default-deny pattern (ingress AND egress)
      
      **R2 — Every namespace gets a default-deny for BOTH directions, then explicit allows.** An
      ingress-only default-deny leaves egress wide open (free C2/exfil — see §5 and rules/05).
      
      ```yaml
      # GOOD: per-namespace default-deny, both directions
      apiVersion: networking.k8s.io/v1
      kind: NetworkPolicy
      metadata: { name: default-deny, namespace: payments }
      spec:
        podSelector: {}            # all pods in the namespace
        policyTypes: [Ingress, Egress]   # BOTH — the common mistake is omitting Egress
        # no ingress/egress rules => deny all in both directions
      ```
      
      Then add narrow allows. Note the subtle trap below — it is the "default-deny that isn't":
      
      ```yaml
      # BAD: reads as a policy, but allows the whole cluster in.
      spec:
        podSelector: {}
        policyTypes: [Ingress]
        ingress:
        - from: [{ namespaceSelector: {} }]   # {} matches EVERY namespace = allow-all ingress
      ```
      
      **R3 — Allow DNS explicitly, or default-deny egress breaks everything.** Once egress is denied,
      pods can't resolve names. Allow egress to kube-dns/CoreDNS on 53 (and prefer the L7 DNS-aware form
      in §5 so you also constrain *which* names resolve):
      
      ```yaml
      egress:
      - to: [{ namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: kube-system } } }]
        ports: [{ protocol: UDP, port: 53 }, { protocol: TCP, port: 53 }]
      ```
      
      ## 3. Standard NetworkPolicy: powers and limits
      
      `networking.k8s.io/v1` NetworkPolicy is namespaced, additive (allows union; deny is the absence of
      allow), and selects by pod labels, namespace labels, or `ipBlock`. Limits to know:
      - **No L7** (no HTTP path/method), **no FQDN** (only IPs/CIDRs in `ipBlock`), **no explicit deny**
        (no priority/deny — you express deny by *not* allowing), **no cluster-scoped** baseline.
      - `ipBlock` and in-cluster IPs — **state which implementation you mean, the default is
        opposite between them.** Under upstream `NetworkPolicy` a broad `ipBlock` can re-open
        intra-cluster paths, because pod IPs fall inside the CIDR. Under **Cilium — which this
        file's examples assume** — *"CIDR-based selectors do not match in-cluster entities (pods or
        nodes)"* by default, and matching pods that way needs `--policy-cidr-match-mode=pods`
        (which allocates an identity per matching pod, so it is not free)
        ([Cilium L3 policy](https://docs.cilium.io/en/stable/security/policy/layer3/), verified
        2026-09-16). So the same manifest has two different blast radii depending on the CNI:
        check the mode before filing either the finding or the all-clear, and prefer identity
        selectors for pod-to-pod policy as §below advises.
      
      **R3a — where the "never a bare CIDR" non-negotiable does and does not bind.** This skill's
      non-negotiable 3 says every allow references identity, never a bare CIDR. Read literally against
      vanilla NetworkPolicy it is unsatisfiable for **external** destinations, because the list above is
      exhaustive: there is no identity selector for anything outside the cluster, so `ipBlock` is the only
      expressible form. Scope it explicitly, in both directions:
      
      - **In-cluster peer → identity, no exception.** `podSelector`/`namespaceSelector`, never the pod
        CIDR. Pod IPs are ephemeral and reused, so a CIDR grants whatever occupies the range next — the
        allow widens with no change to the manifest, and nothing reports it.
      - **Out-of-cluster peer → `ipBlock` is a documented exception, not a default.** Name the destination
        and the reason in a comment, keep the prefix to what the peer actually needs (a `/32` for one
        host, not the VPC `/16`), and treat it as a standing argument to move that policy to a CNI that
        *can* express identity — Cilium `toFQDNs` for named services, `toEntities` for cluster/host
        classes (§4, §5). Auditing it is a two-step: an `ipBlock` whose CIDR overlaps the pod or service
        range is the in-cluster case wearing the exception's clothes, and is a finding.
      
      Recorded 2026-09-11 from a measured cross-skill conflict: `sota-sandboxing` rules/03 R3.4 shipped a
      reference egress policy using `ipBlock` for an in-cluster backend, which this file's absolute
      forbade and which was the weaker pattern anyway. Both sides were fixed — the example now selects by
      identity, and the absolute now says where it binds.
      
      For the user's Cilium cluster, prefer **CiliumNetworkPolicy** for anything needing identity-based,
      L7, FQDN, or cluster-wide policy; keep plain NetworkPolicy for portable baselines.
      
      ## 4. CiliumNetworkPolicy: identity, L7, FQDN
      
      **R4 — Use identity-based selectors and L7 where they tighten the rule.** Cilium enforces by
      *identity* (derived from labels) in eBPF, not by IP, so policy survives churn.
      
      ```yaml
      # GOOD: identity + L7 — api may call billing ONLY on POST /charge
      apiVersion: cilium.io/v2
      kind: CiliumNetworkPolicy
      metadata: { name: api-to-billing, namespace: payments }
      spec:
        endpointSelector: { matchLabels: { app: billing } }
        ingress:
        - fromEndpoints: [{ matchLabels: { app: api } }]
          toPorts:
          - ports: [{ port: "8080", protocol: TCP }]
            rules:
              http: [{ method: "POST", path: "/charge" }]
      ```
      
      Beware the **`world` / `reserved:world` entity** and `toCIDR: 0.0.0.0/0` — allowing them on a
      sensitive endpoint is the Critical over-broad finding from rules/02 (the real OpenBao/Grafana/
      registry-from-`world` case). Audit every CNP for `world`, `all`, `0.0.0.0/0`.
      
      **Patch floor (2026 CVEs):** Cilium below **1.19.4 / 1.18.10 / 1.17.16** has known
      policy-bypass/hijack CVEs that undermine this file's guarantees — CVE-2026-33726 (L7 proxy could
      bypass NetworkPolicy for same-node traffic; fixed 1.19.2/1.18.8/1.17.14), CVE-2026-49445 (Envoy
      admin socket exposure with L7 enabled — info disclosure/cluster disruption; fixed 1.19.2), and
      CVE-2026-53935 (`CiliumLocalRedirectPolicy` `addressMatcher` cross-namespace service-traffic
      hijack; fixed 1.19.4). Check the running version against Cilium's security advisories.
      
      ## 5. Egress control & DNS-aware egress
      
      **R5 — Egress is first-class; allowlist by FQDN, not open `0.0.0.0/0`.** Cilium's DNS-aware policy
      snoops DNS to map allowed names to IPs, so you can allowlist destinations by domain:
      
      ```yaml
      # GOOD: pod may resolve+reach only api.stripe.com; everything else denied
      apiVersion: cilium.io/v2
      kind: CiliumNetworkPolicy
      metadata: { name: egress-stripe-only, namespace: payments }
      spec:
        endpointSelector: { matchLabels: { app: billing } }
        egress:
        - toEndpoints: [{ matchLabels: { k8s:io.kubernetes.pod.namespace: kube-system, k8s-app: kube-dns } }]
          toPorts:
          - ports: [{ port: "53", protocol: UDP }]
            rules: { dns: [{ matchPattern: "*.stripe.com" }] }   # constrain WHICH names resolve
        - toFQDNs: [{ matchName: "api.stripe.com" }]
          toPorts: [{ ports: [{ port: "443", protocol: TCP }] }]
      ```
      
      **R6 — Block the cloud metadata endpoint from pods.** `169.254.169.254` (and `fd00:ec2::254`) is
      the SSRF pivot to cloud credentials. Default egress should not include `169.254.0.0/16`; if a
      broad egress exists, explicitly deny the link-local range. This is the egress side of the SSRF chain
      (sota-code-security rules/01 owns the app-side SSRF; rules/05 here covers the edge/egress side).
      On the user's on-prem Talos cluster there's no IMDS, but the habit prevents the finding if they
      ever burst to cloud — and IMDSv2 (token-required, account-enforceable, default on new EC2 types)
      is the cloud-side mitigation (sota-cloud-infrastructure).
      
      **R7 — Egress gateways for stable, inspectable egress.** When external partners allowlist your
      source IP, or you want all egress through one inspected choke point, use a **Cilium egress gateway**
      (SNAT cluster egress to fixed node IPs). This pairs with FQDN policy: gateway = where you route and
      log, FQDN policy = what's allowed.
      
      ## 6. Cluster-scoped baselines: ANP / BANP (alpha — handle with care)
      
      **R8 — Use ANP/BANP for cluster-wide guardrails the way RBAC uses ClusterRoles — but pin the alpha.**
      - **AdminNetworkPolicy (ANP)**: cluster-scoped, *priority-ordered*, supports explicit **Deny/Allow/
        Pass** (unlike namespaced NetworkPolicy). Use for non-overridable org rules: "no namespace may
        egress to the metadata IP," "deny all cross-tenant traffic." Evaluated *before* NetworkPolicy.
      - **BaselineAdminNetworkPolicy (BANP)**: a single cluster-scoped default (e.g. cluster-wide
        default-deny) that namespaced NetworkPolicy can *override*. Use it to make default-deny the
        cluster baseline so a new namespace isn't accidentally allow-all.
      
      Status: the `policy.networking.k8s.io` CRDs are **alpha**, out-of-tree. Since Oct 2025 ANP+BANP
      are consolidated into **`ClusterNetworkPolicy` (v1alpha2)** — `tier: Admin` replaces ANP,
      `tier: Baseline` replaces BANP — and the working group will base the beta on ClusterNetworkPolicy,
      so plan migration toward it. Cilium and others implement subsets; **verify your CNI's support
      matrix and pin versions** — don't build a control you can't test. Until it's solid in your
      cluster, a Cilium *clusterwide* policy (CCNP) achieves the cluster-scoped default-deny today.
      
      ```yaml
      # Cilium clusterwide default-deny baseline (works today on the user's stack)
      apiVersion: cilium.io/v2
      kind: CiliumClusterwideNetworkPolicy
      metadata: { name: default-deny-all }
      spec:
        endpointSelector: {}
        ingress: [{ }]   # empty rule list under enableDefaultDeny => deny; pair with explicit allows
        egress: [{ }]
      ```
      
      ## 7. Hubble flow visibility
      
      **R9 — Turn on Hubble; you cannot secure flows you can't see.** Hubble gives L3/4 and L7 flow
      visibility and is how you (a) verify a policy actually denies, (b) author tight policies from
      observed traffic, (c) feed network telemetry to detection (sota-detection-engineering owns the
      detection content — DNS-exfil, anomalous flows). Export flows; don't leave Hubble UI-only.
      
      ```bash
      hubble observe --namespace payments --verdict DROPPED   # what's being denied (tighten or fix)
      hubble observe --to-fqdn '*.metadata*'                  # anyone reaching metadata-ish names?
      hubble observe --from-pod payments/api --protocol http  # author L7 policy from real traffic
      ```
      
      ## 8. Cluster mesh (multi-cluster)
      
      **R10 — Cluster mesh extends *identity and policy*, not a flat L3.** With Cilium Cluster Mesh,
      identities and CNP selectors span clusters — but that means a too-broad cross-cluster allow now has
      multi-cluster blast radius. Apply the same default-deny + identity-scoped allows across the mesh;
      audit cross-cluster policies for `world`/wildcard exactly as single-cluster.
      
      ## Audit checklist
      
      - [ ] Does *every* namespace have a default-deny for **both** Ingress and Egress? List namespaces
            with no NetworkPolicy/CNP: those are flat segments.
      - [ ] Is any "default-deny" actually allow-all? Hunt `namespaceSelector: {}`, `podSelector: {}` on
            the *from* side, missing `Egress` in `policyTypes`, `0.0.0.0/0`/`world`/`reserved:world` in
            CNPs. Then **probe** cross-namespace and egress reachability to confirm.
      - [ ] Is DNS allowed explicitly under default-deny egress (else everything breaks), and is the DNS
            policy constraining *which* names resolve?
      - [ ] Is egress FQDN-allowlisted for sensitive namespaces, not open to `0.0.0.0/0`?
      - [ ] Is `169.254.0.0/16` (metadata) blocked from pod egress?
      - [ ] Every `ipBlock` justified: `grep -n 'ipBlock' -r policies/` and for each hit ask which side
            of §3's R3a it is on — an **in-cluster** peer written as a CIDR is a finding (recycled pod
            IPs re-point it silently), and an external one needs the destination, the reason, and a
            prefix no wider than the peer. A CIDR overlapping the pod/service range is in-cluster
            regardless of what the comment claims
      - [ ] Are sensitive services (secrets/DB/registry/admin) selected by identity and reachable only
            from declared callers? Prove with Hubble + a probe.
      - [ ] Is there a cluster-scoped default-deny baseline (BANP/ANP if stable, else
            CiliumClusterwideNetworkPolicy) so new namespaces aren't allow-all?
      - [ ] Is Hubble enabled and flows exported to detection?
      - [ ] ANP/BANP/ClusterNetworkPolicy usage: is the alpha API status pinned, CNI support verified,
            and migration to `ClusterNetworkPolicy` (v1alpha2, tiered) planned?
      - [ ] Is Cilium at/above the 2026 CVE-fix floor (1.19.4 / 1.18.10 / 1.17.16) and checked against
            current security advisories?
      
    • 04-service-mesh-mtls.md 8.6 KB
      # 04 — Service Mesh & mTLS / Internal Encryption
      
      Scope: the plaintext-internal-traffic problem and how mesh/mTLS solves it structurally; choosing and
      auditing a mesh (Istio sidecar vs ambient/ztunnel, Linkerd, Cilium service mesh); mTLS everywhere
      (STRICT, not permissive-forever); mesh authorization policy; SPIFFE/SPIRE workload identity; and when
      a mesh is overkill vs just using TLS.
      
      Where this sits: **sota-identity-access** owns SPIFFE/SPIRE *issuance* and the workload-identity
      trust domain; this skill consumes those identities for network authorization. rules/03 owns L3/4 CNI
      policy; this file owns the L7/identity/encryption layer that complements it. sota-secrets-management
      owns TLS private-key handling.
      
      Verified (2026-07): **Istio ambient mode (ztunnel + waypoints)** reached **GA in Istio 1.24 (Nov
      2024)** — sidecar and ambient are both production data planes today. **Linkerd** (CNCF Graduated)
      added **SPIFFE identities and mesh expansion in 2.15** (Feb 2024). **SPIFFE/SPIRE** are CNCF
      Graduated, production-ready. **Cilium** mTLS is now the **ztunnel integration** (per-node proxy
      adopted from Istio ambient) — **Beta in 1.19, TCP-only** (UDP/other protocols aren't redirected),
      enrolled per-namespace; the older out-of-band Mutual Authentication beta is disabled by default in
      1.19. Pin exact versions against the projects' docs before committing.
      
      ---
      
      ## 1. The plaintext-internal-traffic problem
      
      **R1 — Credentials and tokens on an unencrypted internal hop are a Critical finding.** The real
      case: DB root credentials and JWTs flowing over `ws://` (and unencrypted DB protocol) across the pod
      network. Anyone with a foothold on the network — a sniffing sidecar, a compromised node, a misrouted
      pod — reads them. "It's internal" does not make plaintext safe (rules/01: location is not trust).
      
      Symptoms to hunt: `http://`/`ws://` between services, DB connections without TLS, gRPC without TLS,
      `mode: PERMISSIVE` in a mesh that's been "temporary" for months, app config trusting that the
      network is private.
      
      **R2 — Solve it structurally with mTLS, not per-app TLS plumbing.** You *can* terminate TLS in every
      service, but that means every team correctly configures certs, validates peers, and rotates — which
      fails in practice. A mesh (or Cilium mTLS) makes mutual TLS the *default transport* for all
      service-to-service traffic, transparently, with automatic short-lived certs. The structural property:
      plaintext becomes impossible, not merely discouraged.
      
      ## 2. mTLS everywhere — STRICT, not permissive-forever
      
      **R3 — Drive mesh mTLS to STRICT; PERMISSIVE is a migration state, not a destination.** PERMISSIVE
      accepts both mТLS and plaintext — useful while onboarding, but it means plaintext *still flows* and
      an attacker can simply speak plaintext. Auditing a mesh that's been PERMISSIVE for a long time = the
      plaintext problem is unsolved.
      
      ```yaml
      # GOOD: Istio — STRICT mTLS mesh-wide (then per-workload exceptions if truly needed)
      apiVersion: security.istio.io/v1
      kind: PeerAuthentication
      metadata: { name: default, namespace: istio-system }
      spec:
        mtls: { mode: STRICT }
      ```
      
      Verify it's actually STRICT and enforced: send plaintext to a meshed workload and confirm it's
      rejected (`kubectl exec ... curl http://svc` from an unmeshed pod must fail).
      
      ## 3. Workload identity (SPIFFE) is the foundation
      
      **R4 — Authorization binds to cryptographic workload identity, not IP.** Mesh mTLS issues each
      workload a short-lived identity — a **SPIFFE SVID** (`spiffe://trust-domain/ns/<ns>/sa/<sa>`) or
      mesh-native cert. Authz policy then references *that identity*, so "only the api service may call
      billing" survives pod churn and can't be spoofed by landing on the right IP. sota-identity-access
      owns SPIFFE/SPIRE setup and the trust domain; here, ensure policies reference identities, not CIDRs.
      
      ## 4. Mesh authorization policy (the L7 PEP)
      
      **R5 — mTLS proves *who*; authorization decides *what they may do*. You need both.** mTLS alone
      authenticates peers but, by default, any authenticated workload can call any other. Add
      deny-by-default authorization keyed on identity + L7 attributes:
      
      ```yaml
      # GOOD: Istio — only the api SA may POST /charge on billing; default-deny otherwise
      apiVersion: security.istio.io/v1
      kind: AuthorizationPolicy
      metadata: { name: billing-allow-api, namespace: payments }
      spec:
        selector: { matchLabels: { app: billing } }
        action: ALLOW
        rules:
        - from: [{ source: { principals: ["cluster.local/ns/payments/sa/api"] } }]
          to: [{ operation: { methods: ["POST"], paths: ["/charge"] } }]
      # Pair with a default-deny (empty ALLOW selector or explicit DENY) so unlisted callers are refused.
      ```
      
      Linkerd uses `Server` + `AuthorizationPolicy`/`MeshTLSAuthentication`; Cilium uses CNP L7 rules
      (rules/03 §4) keyed on identity. Same principle: default-deny, identity-scoped, L7 where it tightens.
      
      ## 5. Choosing a mesh (or not)
      
      **R6 — Don't deploy a mesh you don't need; don't hand-roll mTLS you can't maintain.** Decision:
      
      | Situation | Choice |
      |---|---|
      | 1–3 services, simple topology | **Plain TLS** between them (or Cilium WireGuard node-to-node encryption) — a full mesh is overkill |
      | Many services, need mTLS + L7 authz + telemetry, want it transparent | A mesh |
      | Already on Cilium, want mTLS without a full mesh | **Cilium ztunnel mTLS** — per-node proxy, **Beta in 1.19, TCP-only, per-namespace enrollment**; pin-and-evaluate, and prefer Istio ambient or Linkerd where production mTLS is required today. Cilium **WireGuard** gives stable node-to-node encryption (no per-workload identity) |
      | Want the lightest dedicated mesh, Kubernetes-only | **Linkerd** (simple, fast, Graduated, SPIFFE in 2.15) |
      | Need the richest L7/traffic-management, multi-cluster, VM mesh | **Istio** — prefer **ambient mode** (ztunnel + waypoints, GA since 1.24) to avoid per-pod sidecar cost; sidecar mode still valid |
      
      **R7 — Ambient vs sidecar (Istio).** Ambient splits the data plane: a per-node **ztunnel** handles
      L4 mTLS for all pods (no sidecar injection, lower overhead), and **waypoint** proxies add L7
      (authz, routing) only where needed. GA since 1.24. Prefer ambient for new rollouts to cut the
      sidecar tax; the security properties (STRICT mTLS, identity-based authz) are the same — audit them
      the same way.
      
      **R8 — A mesh is not a substitute for L3/4 CNI policy.** Mesh mTLS+authz covers meshed,
      TCP/HTTP traffic. CNI NetworkPolicy (rules/03) still default-denies for non-meshed pods, non-TCP
      traffic, egress, and anything that bypasses the mesh (e.g. a pod talking straight to a DB outside
      the mesh). Run both: CNI for the L3/4 floor, mesh for L7/identity. A mesh-only posture with
      allow-all NetworkPolicy still has a flat L3 underneath.
      
      ## 6. Operational pitfalls
      
      - **PERMISSIVE drift** (R3) — the top one. Track which namespaces are still permissive; treat
        long-lived PERMISSIVE as a High finding.
      - **Authz default-allow** — mTLS on but no AuthorizationPolicy means any workload calls any other.
        Default-deny then allow.
      - **mTLS bypass paths** — traffic that skips the mesh (hostNetwork pods, direct IP, ports the mesh
        doesn't capture, the DB outside the mesh). Hunt for unmeshed sensitive endpoints; cover them with
        CNI policy (rules/03).
      - **Cert rotation = the mesh's job** — short-lived SVIDs auto-rotate; if you're manually managing
        mesh certs, something is wrong. Internal root/intermediate (your step-ca) feeds the mesh CA;
        rotate per rules/06.
      - **Don't double-encrypt blindly** — if Cilium already encrypts transparently at L4 (WireGuard or
        ztunnel) and you add a full mesh on top, justify it; usually pick one transport-security layer.
      
      ## Audit checklist
      
      - [ ] Hunt plaintext on internal hops: `grep -rEn 'ws://|http://[a-z].*\.svc|sslmode=disable|tls: *false'`
            across manifests/config. Any credential/token/DB traffic in plaintext → Critical.
      - [ ] If a mesh exists, is mTLS **STRICT** (not PERMISSIVE)? Prove by sending plaintext to a meshed
            workload — it must be rejected.
      - [ ] Is there a default-deny **AuthorizationPolicy**, with allows keyed on workload identity
            (SPIFFE principal / SA), not IP?
      - [ ] Are there mesh-bypass paths (hostNetwork, direct-IP, out-of-mesh DB) reaching sensitive
            services? Are those covered by CNI NetworkPolicy (rules/03)?
      - [ ] Is CNI L3/4 default-deny still in place *underneath* the mesh (mesh is not a CNI replacement)?
      - [ ] Are mesh/workload certs short-lived and auto-rotated (not hand-managed)?
      - [ ] Is the mesh choice justified for the service count (not a mesh for 2 services; not hand-rolled
            mTLS at scale)? For Istio, is ambient considered to cut sidecar overhead?
      - [ ] Are mesh/CNI/SPIFFE versions pinned and verified against current project docs?
      
    • 05-edge-ingress-egress.md 12.5 KB
      # 05 — Edge, Ingress & Egress
      
      Scope: WAF (OWASP CRS, Coraza/ModSecurity), ingress/API-gateway hardening, DDoS posture (edge
      scrubbing + self-hosted L3/4 kernel hardening), TLS
      termination + re-encryption to backends, reverse-proxy trusted-IP / allowlist handling (behind
      Cloudflare), Cloudflare-tunnel / identity-aware-proxy patterns, and **egress as a first-class
      control**: default-deny egress, egress gateways/proxies, FQDN allowlisting, preventing C2/exfil, and
      blocking the cloud metadata endpoint (the SSRF-meets-egress chain). A representative edge stack: **Caddy with a
      CRS WAF, Cloudflare in front**.
      
      Where this sits: sota-cloud-infrastructure rules/03 owns LB/CDN *provisioning* and registrar/DNS
      *setup*; this skill owns the *security posture*. rules/03 here owns the K8s-internal egress
      mechanics (CNP/FQDN); this file owns the edge and the egress *discipline*. sota-api-design rules/07
      owns API rate-limiting design; sota-code-security rules/01 (SSRF) and rules/05 (CORS/CSP) own the
      app side.
      
      Verified (2026-07-09): **OWASP CRS** current line **4.x** (4.25 is the first CRS-4 LTS, patched
      through Q3 2027; verify latest at coreruleset.org). **CRS 3.3.x support ends Q3 2026** — a WAF still
      on 3.3 is a finding. CRS runs on **OWASP ModSecurity** *and* **OWASP Coraza** (Go,
      SecLang-compatible, the modern engine; both are now OWASP projects). **IMDSv2** is token-required
      and account-enforceable; metadata IP `169.254.169.254` / `fd00:ec2::254`. Pin CRS version.
      
      ---
      
      ## 1. Ingress / edge proxy hardening
      
      **R1 — One hardened, inspectable edge; backends not directly reachable.** All north-south HTTP
      enters through the edge proxy (Caddy) / ingress controller, which terminates TLS, applies the WAF,
      sets security headers, and forwards. Backends accept traffic *only* from the edge/ingress (mesh authz
      to the gateway identity, or NetworkPolicy allowing only the ingress namespace — rules/03/04). A
      backend reachable directly bypasses the WAF, rate limits, and auth — verify by hitting a backend pod
      IP directly; it must be refused.
      
      **R2 — Minimal exposure and version hygiene at the edge.** Expose only 443 (and 80→443 redirect);
      disable unused methods/modules; keep the proxy and WAF engine patched (a WAF with a known bypass CVE
      is theater). Don't leak backend topology in headers (`Server`, `X-Powered-By`, internal hostnames).
      
      **R2.1 — No EOL controllers in the L7 data path.** `kubernetes/ingress-nginx` — long the most common
      Kubernetes ingress controller — was retired in **March 2026** (repo read-only, **no further security
      fixes**); the Kubernetes Steering/Security Response Committees state that remaining on it leaves you
      vulnerable to attack. Finding it running is a High finding: migrate to a maintained **Gateway API**
      implementation (`ingress2gateway` automates much of the conversion) or another maintained ingress
      controller.
      
      ## 2. WAF (OWASP CRS on Coraza / ModSecurity)
      
      **R3 — Run CRS in blocking mode at a tuned paranoia level — not detection-only forever.** CRS in
      "DetectionOnly" logs but blocks nothing; a WAF that never blocks is monitoring, not a control. The
      rollout is: deploy in detection → tune out false positives → flip to blocking. A long-lived
      detection-only WAF is a Medium finding (it's not enforcing).
      
      - **Paranoia Level (PL):** PL1 default; raise to PL2+ for sensitive apps, accepting more tuning.
      - **Anomaly scoring:** CRS scores requests and blocks past a threshold; tune the threshold and add
        per-rule exclusions rather than disabling whole rule files.
      - **Engine:** **Coraza** (Go, embeddable — pairs well with Caddy/Envoy/modern proxies) or
        **ModSecurity v3**; both run the same CRS. Caddy + a Coraza module is the SOTA self-hosted combo.
      - CRS is **not** input validation or authz — it's a generic-attack net (SQLi/XSS/RCE patterns,
        scanner signatures). Defense in depth: app-layer validation (sota-code-security) still required.
      
      **R4 — Don't let the WAF lull you on SSRF/business logic.** CRS catches generic payloads, not
      app-specific SSRF or IDOR. Pair the edge WAF with app-side SSRF defenses (sota-code-security
      rules/01) and the egress controls below (§6) — the WAF is the north-south net; egress is the
      south-bound net.
      
      ## 3. TLS termination + re-encryption
      
      **R5 — Terminate TLS at the edge; re-encrypt to backends crossing a trust boundary.** Edge
      terminates the public cert (auto-managed — rules/06), inspects, then originates a *new* TLS/mTLS
      connection to the backend. Plaintext from edge→backend across the cluster network is the
      plaintext-internal-traffic problem (rules/04) at the ingress hop. Inside a mesh, the edge gateway
      hands off to mTLS automatically; otherwise configure backend TLS explicitly.
      
      ## 4. Reverse-proxy trusted-IP handling (behind Cloudflare)
      
      **R6 — Trust `X-Forwarded-For` / `CF-Connecting-IP` ONLY from your proxy's real IPs, or attackers
      spoof client identity.** Behind Cloudflare → Caddy → app, two recurring bugs:
      - **Spoofable client IP:** if the app reads `X-Forwarded-For` from *any* source, a request that
        reaches the app directly (bypassing Cloudflare) can forge any client IP — breaking IP allowlists,
        rate limits, and logs. Configure the proxy to trust XFF only from the upstream's known ranges, and
        prefer Cloudflare's `CF-Connecting-IP` validated against current Cloudflare IP ranges.
      - **Origin exposure (the bypass):** if the origin is reachable on its public IP, an attacker skips
        Cloudflare and the WAF entirely. **Lock the origin to Cloudflare:** firewall/SG allow only
        Cloudflare IP ranges (or use **Cloudflare Tunnel** so the origin has *no* inbound public IP at
        all — strongly preferred). Verify by resolving and hitting the origin directly from outside.
      
      ```caddyfile
      # Caddy: trust forwarded headers only from Cloudflare; everything else is untrusted
      {
        servers {
          trusted_proxies static <cloudflare-ipv4-ranges...> <cloudflare-ipv6-ranges...>
          client_ip_headers Cf-Connecting-Ip X-Forwarded-For
        }
      }
      ```
      
      **R7 — Cloudflare Tunnel / identity-aware proxy for non-public or admin surfaces.** Internal/admin
      apps go behind Cloudflare Access (identity-aware proxy, rules/01 §6) or a tunnel — never a public
      origin guarded only by a path or a guessed-URL. The origin stays unreachable except through the
      authenticated proxy.
      
      ## 5. DDoS posture
      
      **R8 — Absorb at the edge, rate-limit per-identity, cap autoscaling.** A scrubbing edge (e.g.
      Cloudflare, a cloud provider's DDoS tier, or an Anycast scrubbing provider) absorbs L3/4 and much
      L7; add WAF rate-limiting rules and per-route/per-identity limits (design owned by
      sota-api-design rules/07). Cap autoscaling so a flood can't scale your bill or cluster infinitely
      (economic/"yo-yo" DoS). "We never considered DDoS" is the finding; record the stance. Best DDoS
      surface is none — keep non-public surfaces non-public (tunnels, IAP). Cloud L3/4 mitigation posture
      (Shield/Cloud Armor/Azure DDoS tiers) is sota-cloud-infrastructure rules/03 §10; this rule owns the
      edge you operate.
      
      **R8.1 — Self-hosted / bare-metal edge: harden the kernel, you are the scrubber.** When there is no
      Anycast provider in front (e.g. a bare-metal or Talos edge exposed directly), L3/4 defense is yours.
      Baseline, matched to the exposed protocols:
      - **SYN floods:** enable TCP **SYN cookies** (`net.ipv4.tcp_syncookies=1`) — the kernel answers with
        a cryptographic cookie instead of holding half-open state when the SYN backlog overflows. For a
        high-rate edge, add a **synproxy** (nftables) in front of the listener so flood SYNs never create
        conntrack entries: it needs `tcp_syncookies` **and** `tcp_timestamps` on, `notrack` on SYNs in the
        raw table, `nf_conntrack_tcp_loose=0`, and a rule matching `ct state invalid,untracked`
        (per the nftables synproxy wiki). Note syncookies disable some TCP options — expected trade-off
        under attack, not a steady-state default concern.
      - **Conntrack exhaustion** is its own DoS: a stateful firewall drops new flows once
        `nf_conntrack_max` fills. Size it (and the hashsize) to expected concurrency, alert on
        `nf_conntrack_count` / "table full" drops, and `notrack` high-volume stateless traffic so it never
        consumes a slot.
      - **Anti-spoofing:** enable **reverse-path filtering** (`rp_filter`, strict where routing allows;
        RFC 3704) so spoofed-source packets are dropped at ingress.
      - **Don't be an amplifier (BCP 38 / RFC 2827):** never expose an **open** UDP reflector — recursive
        DNS resolver, NTP `monlist`, memcached, SSDP, chargen — to the internet; bind them internally or
        require auth. An exposed open resolver makes you a weapon in someone else's reflection attack and a
        target for the return traffic. Prefer TCP or authenticated protocols on the public edge; rate-limit
        or drop unsolicited UDP you don't serve.
      
      ## 6. Egress as a first-class control
      
      **R9 — Default-deny egress; allow named destinations only.** Exfiltration and C2 leave through
      egress. Treat broad outbound (`0.0.0.0/0` from app/data tiers) as a finding. Tiers:
      1. **Data/isolated tier:** no egress; reach internal deps via private paths only.
      2. **App tier:** egress only to an **allowlist** — FQDN-based where possible (Cilium FQDN policy,
         rules/03 §5; or an egress proxy like a forward-Squid/Envoy with a domain allowlist).
      3. **Egress gateway/proxy:** funnel all egress through one inspectable, loggable choke point (Cilium
         egress gateway for stable source IPs; a forward proxy for L7 domain allowlisting + logging).
      
      **R10 — Block the cloud metadata endpoint — the SSRF-meets-egress chain.** An SSRF in an app
      (sota-code-security rules/01 owns finding/fixing it) becomes credential theft only if the workload
      can actually *reach* `169.254.169.254`. Close the egress side: deny `169.254.0.0/16` (and
      `fd00:ec2::254`) from workload egress at the CNI/firewall, and on cloud use **IMDSv2** (token
      required, hop-limit 1, account-level enforcement so v1 can't be used). Defense in depth: the app
      should also not be SSRF-able, but egress denial is the backstop that turns "credential theft" into
      "connection refused." On the user's on-prem Talos there's no IMDS, but keep the egress default-deny
      so a future cloud burst is safe by default.
      
      ```
      # Egress allowlisting, layered:
      #  - CNI FQDN policy (rules/03 §5) for in-cluster app egress
      #  - forward proxy w/ domain allowlist for L7 inspection + logging (the choke point)
      #  - DENY 169.254.0.0/16 and ::ffff:169.254.0.0/112 everywhere
      #  - egress flow logs -> detection (sota-detection-engineering: C2/DNS-exfil detection)
      ```
      
      **R11 — Egress visibility feeds detection.** Export egress flow logs / proxy logs to
      sota-detection-engineering (C2 beaconing, DNS exfil, anomalous destinations). An allowlist plus
      logging beats either alone: the allowlist blocks the easy path, the logs catch the clever one.
      
      ## Audit checklist
      
      - [ ] Are backends reachable only via the edge/ingress? Hit a backend pod IP / origin public IP
            directly from outside — must be refused.
      - [ ] Is the WAF (CRS on Coraza/ModSecurity) in **blocking** mode at a tuned PL, current version —
            not detection-only-forever, not unpatched? A ruleset still on CRS 3.3.x (end of support
            Q3 2026) is a finding.
      - [ ] Is the ingress controller maintained? `kubernetes/ingress-nginx` is EOL (March 2026, no
            security fixes) → High; migrate to a maintained Gateway API implementation.
      - [ ] Is the public cert terminated at the edge and traffic re-encrypted (not plaintext) to
            backends across the cluster network?
      - [ ] Does the app trust `X-Forwarded-For`/`CF-Connecting-IP` **only** from known proxy ranges
            (not spoofable)? Is the origin locked to Cloudflare (IP allowlist or Tunnel — verify the
            origin isn't directly reachable)?
      - [ ] Internal/admin surfaces behind an identity-aware proxy / tunnel, not a public origin?
      - [ ] Is egress **default-deny** with an FQDN/IP allowlist for app tiers and none for data tiers?
            Probe: a pod reaching an arbitrary internet host must fail.
      - [ ] Is `169.254.0.0/16` (metadata) blocked from workload egress? On cloud, is **IMDSv2**
            enforced (hop limit, account-level)?
      - [ ] Is egress funneled through an inspectable choke point and are egress/proxy logs exported to
            detection?
      - [ ] DDoS stance recorded; per-identity rate limits and autoscale caps set?
      - [ ] Self-hosted/bare-metal edge with no scrubbing provider in front: `tcp_syncookies` on,
            `rp_filter` enabled, `nf_conntrack_max` sized + drops alerted, synproxy on high-rate TCP
            listeners? (`sysctl net.ipv4.tcp_syncookies net.ipv4.conf.all.rp_filter`)
      - [ ] No open UDP reflector (recursive DNS, NTP monlist, memcached, SSDP, chargen) exposed to the
            internet — you are not an amplification source (BCP 38)?
      
    • 06-dns-tls-pki.md 14 KB
      # 06 — DNS, TLS & PKI
      
      Scope: DNS security (DNSSEC, DNS firewall / RPZ, DoH/DoT, split-horizon, registrar/CAA hygiene,
      DNS-tunneling/exfil), TLS posture (1.3, cipher/version policy, HSTS, OCSP/CRL), certificate
      lifecycle automation (ACME) and the shrinking max cert lifetimes that force it, internal PKI
      (e.g. step-ca — short-lived certs, private CA trust distribution, cert-pinning
      tradeoffs), and email authentication / anti-spoofing (SPF, DKIM, DMARC, MTA-STS/DANE).
      
      Where this sits: sota-cloud-infrastructure rules/03 owns DNS zone/registrar *setup* and
      provider-managed cert provisioning; this skill owns the *security posture* (DNS firewalling,
      tunneling defense, TLS policy, internal PKI). sota-secrets-management owns TLS private-key storage/
      rotation mechanics. sota-detection-engineering owns DNS-exfil *detection* content; this file owns the
      *controls* that reduce its surface.
      
      Verified (2026-06-14): **CA/Browser Forum SC-081v3** (approved Apr 2025) phases public TLS cert max
      lifetime down: **200 days from 2026-03-15 → ~100 days from 2027-03-15 → 47 days from 2029-03-15**
      (DCV reuse → 10 days by 2029). **NIST SP 800-207** frames identity over location. **IMDSv2** for the
      metadata cross-reference (rules/05). Pin the CA/B schedule against cabforum.org.
      
      ---
      
      ## 1. The cert-lifetime collapse forces automation
      
      **R1 — Every certificate is auto-issued and auto-renewed. Manual renewal is now an outage
      generator.** Public cert max lifetime drops to 200 days (2026-03), then ~100, then 47 (2029). A
      human cannot reliably re-issue every ~6 weeks across a fleet. Therefore:
      - **ACME everywhere** — Let's Encrypt / your CA's ACME endpoint for public certs; **cert-manager** on
        Kubernetes (Issuer/ClusterIssuer + Certificate) for both public and internal.
      - **Provider-managed certs** on managed LB/CDN/Cloudflare where applicable (no private key you can
        leak).
      - **Expiry monitoring as a backstop** (alert at 30/14/7 days) *even with* automation — automation
        fails silently. (Alert wiring: sota-observability.)
      - Any cert renewed by hand, or living past the current CA/B cap, is a finding (High on a public
        endpoint — guaranteed future outage).
      
      ## 2. TLS posture
      
      **R2 — TLS 1.3 preferred, 1.2 minimum; everything below is disabled.** No TLS 1.0/1.1, no SSLv3.
      Cipher policy: AEAD suites only (1.3 enforces this; for 1.2 allow only ECDHE + AES-GCM/ChaCha20).
      Audit edges, ingress, mesh, and internal services alike.
      
      ```bash
      # Hunt weak TLS quickly
      nmap --script ssl-enum-ciphers -p 443 host        # flags TLS<1.2, weak ciphers, no PFS
      grep -rEn 'TLSv1\.0|TLSv1\.1|SSLv3|min_version.*1\.0' ./config
      ```
      
      **R3 — HSTS on web origins, short lifetimes as the revocation story, modern key types.**
      `Strict-Transport-Security` with a sensible max-age (and `includeSubDomains` once you're sure) so
      browsers refuse plaintext. Short-lived certs (R1) are now the primary revocation mechanism — a
      47-day compromised cert expires fast. **Let's Encrypt ended OCSP** (URLs dropped from certs May
      2025, responders off Aug 2025; revocation is CRL-only), so OCSP stapling is impossible on LE certs;
      enable stapling only where the CA still operates OCSP. Prefer ECDSA (P-256) certs for performance;
      RSA-2048+ acceptable.
      
      **R3.1 — Enable hybrid post-quantum key exchange where the stack supports it.** Offer the
      `X25519MLKEM768` hybrid group on TLS 1.3 (already default-on in modern stacks, e.g. Go 1.24+;
      configurable in current OpenSSL/BoringSSL and major CDNs). It defends *confidentiality* against
      harvest-now-decrypt-later — relevant for EU/long-lived-sensitive traffic — at negligible cost,
      and being hybrid it's no weaker than X25519 if the PQ part is ever broken. (Signatures/PKI stay
      classical for now.) See sota-code-security rules/04 §1.
      
      ## 3. DNS security
      
      **R4 — Registrar & issuance hygiene.** (Setup is cloud-infra rules/03; the *security* controls:)
      - **CAA records on every public zone** restricting issuance to your CA(s) — limits who can mint a
        cert for your domains.
      - Registrar in a corporate account with MFA + transfer/registry lock for crown-jewel domains.
      - **Dangling records** (CNAME/A pointing at deprovisioned resources) = subdomain-takeover vector;
        lifecycle-couple DNS to resources in IaC and scan zones for danglers.
      
      **R5 — Split-horizon: internal names stay in private zones.** Internal hostnames in public DNS leak
      topology and aid recon. Public zones hold only public entry points; internal records live in private
      zones served to internal resolvers only.
      
      **R6 — DNSSEC where the registrar+provider support is solid and rotation is automated.** Sign zones
      used as identity anchors (email/SPF/DKIM-bearing); use *managed* DNSSEC (avoid hand-rolled key
      rollover). DNSSEC protects integrity (anti-spoofing), not confidentiality.
      
      **R7 — DNS firewall / RPZ + DoH/DoT for confidentiality and policy.**
      - **Resolver-level DNS firewall (RPZ)** blocks resolution of known-malicious / newly-registered /
        C2 / DGA domains — a cheap, high-value control that kills many malware and exfil paths at the
        *name* layer before any packet leaves. Pair with FQDN egress allowlisting (rules/05 §6) and a
        blocklist feed.
      - **DoH/DoT** encrypts client↔resolver DNS so on-path observers can't see/modify queries. Decide a
        stance: force internal clients to your resolver (which does logging + RPZ), and consider blocking
        *unauthorized* external DoH (rogue DoH bypasses your DNS firewall and exfil monitoring — a known
        evasion). The control is "all DNS goes through *our* policy-applying, logging resolver."
      
      **R8 — DNS tunneling / exfil: reduce surface here, detect in detection-engineering.** DNS is a
      classic covert channel (data encoded in subdomains/TXT to an attacker NS). Controls this skill owns:
      funnel all resolution through your resolver (R7), RPZ-block/limit lookups to attacker-controlled
      zones, rate-limit/length-limit queries, and FQDN-allowlist egress so workloads can't reach arbitrary
      authoritative servers. **Detection** of the exfil pattern (entropy, query volume, long labels) is
      sota-detection-engineering — feed it your resolver logs.
      
      ## 4. Internal PKI (step-ca)
      
      **R9 — Run a private CA with short-lived certs; distribute trust deliberately.** A common self-hosted choice is
      **step-ca**. SOTA internal PKI:
      - **Short lifetimes + ACME automation:** step-ca speaks ACME — issue internal certs (services, mTLS,
        bastion sessions) with hours-to-days lifetimes and auto-renew. Short-lived internal certs make
        revocation largely moot (the window is tiny) — this is *why* you prefer them over long-lived certs
        with CRL/OCSP plumbing.
      - **Trust distribution:** push the internal root/intermediate to the trust stores of clients/
        workloads that must validate it (node trust bundle, container base image, mesh CA config). The
        recurring bug: a service can't validate internal certs because the root isn't distributed →
        someone "fixes" it with `InsecureSkipVerify`/`--insecure` (R11). Distribute the root, never skip
        verification.
      - **Control what gets *into* the trust store** (OWASP Key Management): adding a root to a
        workload's trust bundle is a privileged, audited change — a rogue/extra CA is silent MITM for
        everything that workload talks to. Ship trust bundles as immutable, version-controlled artifacts
        (baked into the image / GitOps-managed), not mutated at runtime; alert on drift in the bundle.
      - **Protect the CA key** (sota-secrets-management): the private CA's signing key is a crown jewel —
        HSM/KMS-backed or tightly access-controlled; its compromise mints trusted certs for everything.
      - **Separate intermediates** per purpose/environment so one can be rotated/revoked without
        re-trusting the root.
      
      **R10 — Feed the mesh from the internal CA.** The service mesh / Cilium mTLS CA (rules/04) chains to
      step-ca (or its own intermediate). Short-lived SVIDs auto-rotate; don't copy a long-lived wildcard
      between services.
      
      **R11 — Cert pinning: deliberate, with a rotation story, or not at all.** Pinning a peer's cert/CA
      adds MITM resistance but turns rotation into an outage if the pin isn't updated in lockstep — and
      short-lived certs (R1/R9) rotate constantly. Pin to the *CA/intermediate* (stable) rather than the
      *leaf* (rotates), keep backup pins, and only pin where the threat justifies the operational cost
      (mobile apps, high-value B2B). For internal mesh traffic, identity-validated mTLS already gives the
      property; extra leaf-pinning is usually net-negative. `InsecureSkipVerify` / `--insecure` /
      `verify=false` is never the answer — distribute trust (R9).
      
      ```bash
      # Hunt disabled verification — each hit is a finding
      grep -rEn 'InsecureSkipVerify|verify=false|--insecure|NODE_TLS_REJECT_UNAUTHORIZED *= *0|sslmode=disable' .
      ```
      
      ## 5. Email authentication & anti-spoofing (SPF / DKIM / DMARC)
      
      Your domain is an identity anyone can forge until you publish these DNS records. An unprotected
      domain gets spoofed for phishing/BEC (your brand, your users); it also lands legitimate mail in
      spam. All three are DNS records this skill owns; the *content* law of marketing mail (CAN-SPAM,
      consent) is sota-copywriting rules/04.
      
      **R12 — Publish SPF, DKIM, and DMARC; DMARC is the one that actually stops spoofing.**
      - **SPF** (RFC 7208): a TXT record listing IPs/includes allowed to send for the domain, ending in
        `-all` (hard fail). Watch the **10-DNS-lookup limit** — too many `include:` chains → `permerror`
        → SPF silently stops protecting. SPF alone breaks on forwarding (the relay's IP isn't yours), so
        it is necessary but not sufficient.
      - **DKIM** (RFC 6376): sign outbound mail with a private key; publish the public key at
        `<selector>._domainkey`. Use a **>=2048-bit key**, rotate it (per-selector rotation lets you roll
        without downtime), and keep the private key in a secret store (sota-secrets-management), never in
        the repo.
      - **DMARC** (RFC 9989, which obsoletes the original RFC 7489; aggregate/failure reporting are
        RFC 9990/9991): a `_dmarc` TXT policy that ties SPF/DKIM to the visible `From:` domain via
        **alignment** — a pass only counts if the SPF or DKIM domain *aligns* with the From domain, which
        is what blocks look-alike spoofing. **Roll the policy forward, monitoring aggregate (RUA) reports
        at each step:** `p=none` (observe only — collect reports, fix your legitimate senders) →
        `p=quarantine` → `p=reject` (the goal; forged mail is refused). Stopping at `p=none` gives
        visibility but **zero protection** — a common finding.
      
      **R13 — Lock down transport and non-sending domains too.**
      - **MTA-STS** (RFC 8461) + **TLS-RPT** (RFC 8460): MTA-STS publishes a policy requiring senders to
        use authenticated TLS to your inbound MX (defeating STARTTLS-stripping downgrade attacks);
        TLS-RPT emails you JSON reports of TLS/policy failures. Roll MTA-STS `testing` → `enforce` using
        the reports, same discipline as DMARC. **DANE for SMTP** (RFC 7672) is the DNSSEC-anchored
        alternative/complement (TLSA records) — only where the zone is DNSSEC-signed (R6).
      - **Parked/non-sending domains and subdomains** are prime spoofing targets: publish
        `v=spf1 -all` + `p=reject` (and an empty DKIM) on every domain that never sends mail, so
        attackers can't send *as* them. Set the DMARC subdomain policy (`sp=`) explicitly.
      - **ARC** (RFC 8617) preserves authentication results across legitimate forwarders/mailing lists
        that would otherwise break SPF/DKIM — enable it if you forward mail.
      
      **R14 — Bulk-sender rules are now table stakes.** Since Feb 2024, Gmail and Yahoo require senders of
      **5,000+ messages/day** to their users to authenticate with SPF *and* DKIM, publish DMARC (at least
      `p=none`), keep the From domain aligned, offer **one-click unsubscribe** (List-Unsubscribe with
      RFC 8058) on bulk mail, and hold the spam-complaint rate **below 0.3%** (aim <0.1%); Microsoft added
      equivalent requirements (enforcement from 2025). Treat these as the minimum for any transactional or
      marketing sender. Monitor DMARC RUA reports as a *spoofing-detection* feed as well — hand them to
      sota-detection-engineering. (Logo display via **BIMI** is an IETF draft, not yet an RFC, and rewards
      reaching DMARC enforcement; a Verified Mark Certificate is optional evidence, not required.)
      
      ## Audit checklist
      
      - [ ] Are all public certs ACME/managed and auto-renewed, with expiry alerts as backstop? Any
            manual renewal or cert older than the current CA/B cap (200d in 2026) → finding.
      - [ ] TLS 1.2 minimum (1.3 preferred), weak ciphers/protocols disabled across edge, ingress, mesh,
            internal? (`nmap --script ssl-enum-ciphers`.)
      - [ ] HSTS on web origins? OCSP stapling only where the CA still runs OCSP (Let's Encrypt ended it
            Aug 2025 — don't flag its absence on LE certs)?
      - [ ] CAA records on public zones restrict issuance to your CA(s)?
      - [ ] Split-horizon: no internal hostnames in public DNS; zones scanned for dangling records?
      - [ ] DNSSEC stance decided (managed, on identity-anchor zones)?
      - [ ] All DNS funneled through a policy-applying, logging resolver with RPZ/DNS-firewall blocking
            malicious/newly-registered domains? Unauthorized external DoH blocked?
      - [ ] DNS-tunneling surface reduced (FQDN egress allowlist, resolver funnel) and resolver logs fed
            to detection?
      - [ ] Internal PKI (step-ca): short-lived certs + ACME automation; root distributed to trust stores
            (not worked around with `--insecure`); CA key HSM/KMS-protected; per-purpose intermediates?
      - [ ] Cert pinning (if used) pins CA/intermediate with backup pins and a rotation story — not leaf,
            not skipped verification? Hunt `InsecureSkipVerify|--insecure|sslmode=disable`.
      - [ ] Email: SPF (`-all`, under the 10-lookup limit), DKIM (>=2048-bit, rotated), and DMARC
            published — and is DMARC actually enforcing (`p=quarantine`/`p=reject`), not stuck at
            `p=none`? (`dig TXT _dmarc.<domain>`.) RUA reports monitored?
      - [ ] Parked/non-sending domains and subdomains publish `v=spf1 -all` + `p=reject` so they can't be
            spoofed? Inbound transport hardened (MTA-STS enforce + TLS-RPT, or DANE on DNSSEC zones)?
      - [ ] Bulk senders (5,000+/day to Gmail/Yahoo): SPF+DKIM+aligned DMARC, RFC 8058 one-click
            unsubscribe, spam rate <0.3%?
      
  • SKILL.md 12.1 KB
    ---
    name: sota-network-security
    description: >-
      State-of-the-art network security as a discipline (2026) for BOTH
      designing/building secure networks AND auditing segmentation and exposure.
      Use for zero-trust architecture, network segmentation and blast-radius
      control, Kubernetes NetworkPolicy/CiliumNetworkPolicy depth, service mesh
      and mTLS, edge/ingress/egress security (WAF, egress allowlisting,
      metadata-endpoint blocking), DNS/TLS/PKI transport and certificate
      lifecycle, and remote access (WireGuard, ZTNA, bastion vs identity-aware
      proxy). Triggers — network security, zero trust, ZTNA, microsegmentation,
      east-west traffic, lateral movement, NetworkPolicy, default-deny, Cilium,
      Hubble, service mesh, mTLS, Istio, Linkerd, SPIFFE, ingress, egress, WAF,
      CRS, Coraza, egress gateway, FQDN allowlist, IMDS, DNS firewall, DNSSEC,
      DoH, TLS 1.3, ACME, step-ca, private CA, bastion, identity-aware proxy,
      DDoS, email spoofing, SPF, DKIM, DMARC. Owns SECURITY posture on
      top of cloud network setup (sota-cloud-infrastructure owns VPC/subnet/DNS
      setup).
    ---
    
    # SOTA Network Security
    
    ## Purpose
    
    This skill encodes the 2026 state of the art for **network security as a discipline**: how to
    verify rather than trust, contain blast radius, encrypt traffic in motion, control what enters and
    leaves, and secure the naming and transport plumbing (DNS, TLS, PKI). Every rule exists to prevent a
    real failure class — lateral movement after one foothold, plaintext credentials on the wire, an
    over-broad rule that exposes a secrets store to the world, a renewal nobody automated, or an SSRF
    that reaches the cloud metadata endpoint.
    
    **Ownership — reference siblings, do not duplicate:**
    - **sota-cloud-infrastructure** (rules/03 networking) owns cloud-provider network *setup*: VPC/subnet
      layout, CIDR/IPAM, route tables, LB/CDN provisioning, registrar hygiene, DNS zone setup. **This
      skill owns the security posture layered on top** and the on-prem / Kubernetes / mesh side.
    - **sota-kubernetes** owns admission control and RBAC — *where NetworkPolicy is admitted and CNI
      enforcement is wired*; this skill owns the policy *content and depth*.
    - **sota-identity-access** owns SPIFFE/SPIRE workload identity issuance, ZTNA user identity, and the
      identity-aware proxy's auth plane; this skill consumes those identities for network authorization.
    - **sota-detection-engineering** owns network IDS (Suricata), DNS-exfil detection, and flow-log
      detection content; this skill produces the telemetry (Hubble flows, flow logs) it consumes.
    - **sota-sandboxing** (rules/02–03) owns single-host nftables/seccomp and container hardening; this
      skill owns the inter-host / cluster-wide fabric.
    - **sota-code-security** (rules/01 SSRF, rules/05 CORS/CSP), **sota-api-design** (rules/07 rate
      limiting), **sota-secrets-management** (TLS private keys) — referenced where they intersect.
    
    ## BUILD mode
    
    Use when designing or extending a secure network (zero-trust plan, NetworkPolicy set, mesh rollout,
    ingress/egress controls, PKI/DNS posture, remote access).
    
    1. **Establish context first:** on-prem vs cloud vs hybrid; CNI and orchestrator (e.g.
       on-prem Talos K8s + Cilium); existing PKI (step-ca), edge (Caddy + CRS WAF, Cloudflare in front);
       data sensitivity; who needs remote access. A 3-node homelab and a regulated fleet get different
       answers from the same rules.
    2. **Read the matching rules file before writing config.** Segmentation (rules/02) precedes policy
       detail; identity-aware access (rules/01) frames everything.
    3. **Default-deny in both directions, always.** Ingress *and* egress deny by default, per namespace
       and per zone. Every allow is explicit, justified in a comment, and references identity (workload
       identity, label selector, SG/service account) — never a bare CIDR or `world` entity **wherever the
       policy language can express identity**. Where it genuinely cannot — vanilla NetworkPolicy has no
       identity selector for a destination *outside* the cluster — a tight `ipBlock` is a **documented
       exception, not a default**: name the destination and the reason, and prefer a CNI that can
       (rules/03 §3). Inside the cluster there is no exception: pod IPs are recycled, so a CIDR there
       silently re-points the allow.
    4. **Encrypt every hop that crosses a trust boundary.** No plaintext credentials, JWTs, or DB
       traffic on the pod/internal network — mTLS via mesh or TLS terminated close to the workload.
    5. **State the failure mode and the blast radius** of what you propose. "If this pod is popped, it
       can reach X and Y" belongs in the design, not the postmortem.
    6. **Produce policy as code** (NetworkPolicy/CiliumNetworkPolicy YAML, mesh AuthorizationPolicy,
       nftables, ACME/cert-manager manifests) — never click-ops, never "we'll lock it down later."
    
    ## AUDIT mode
    
    Use when reviewing an existing network for segmentation gaps and exposure.
    
    Process: inventory the fabric (zones, namespaces, CNI policies, mesh config, ingress/egress paths,
    DNS zones, certs, remote-access entry points); walk the Audit checklist at the end of each relevant
    rules file; **confirm reachability before reporting** — render the effective policy, run a probe
    (`kubectl exec ... curl`, Hubble flow query, `nmap`), read the actual rule. Do not infer exposure
    from a resource name.
    
    ### Severity conventions
    
    | Severity | Meaning | Examples |
    |---|---|---|
    | **Critical** | External or any-workload party can reach a sensitive service or read traffic now | Secrets store / DB / registry reachable from a `world`/`0.0.0.0/0` entity; plaintext DB creds or JWTs on the wire (sniffable from any pod); SSH open to the internet on a prod host; admin/dashboard reachable unauthenticated from outside |
    | **High** | One foothold from broad lateral movement, or a guaranteed exposure/outage class | "Default-deny" that actually allows all intra-cluster traffic; no egress control (free C2/exfil path); flat L2/L3 network with no segmentation; manual cert renewal on a public endpoint; mTLS in `PERMISSIVE` everywhere with plaintext still flowing |
    | **Medium** | Weakens containment, transport security, or recovery | Ingress-only default-deny (egress still open); CIDR-based internal rules that rot on re-IP; TLS 1.0/1.1 or weak ciphers allowed; no FQDN egress filtering where it's warranted; WAF in detection-only mode; no Hubble/flow visibility |
    | **Low** | Hygiene, drift, headroom | Inconsistent policy labels; over-scoped but internal-only allow; missing HSTS; no CAA record; DNSSEC undecided |
    | **Info** | Context for the reader, no action implied | Mesh is overkill for a 2-service app (just use TLS); ANP/BANP still alpha — pin behavior |
    
    Severity = reachability (anonymous internet > any-workload east-west > same-namespace > insider)
    × impact (traffic read / sensitive-service compromise > lateral movement > availability).
    
    ### Finding format
    
    ```
    file:line | rule | severity | effort | fix
    ```
    
    - **file:line** — the policy/manifest/config and line (e.g. `netpol/baseline.yaml:14`,
      `Caddyfile:30`); for runtime-only findings name the resource (`ns/payments | cilium effective`).
    - **rule** — the rules-file rule id (e.g. `rules/03 R4` or `R-egress-default-deny`).
    - **severity** — Critical / High / Medium / Low / Info.
    - **effort** — trivial / small / medium / large (eng effort to fix).
    - **fix** — the specific change (the policy diff, the directive, the mesh stanza).
    
    Group repeated instances (e.g. 12 namespaces with no egress policy) into one finding with a count.
    
    ## Rules index
    
    | File | Read this when... |
    |---|---|
    | rules/01-zero-trust-architecture.md | Establishing/auditing the model: never-trust-always-verify, PDP/PEP, identity-aware access over network location, ZTNA vs VPN, de-perimeterization, identity-aware proxy (BeyondCorp) |
    | rules/02-segmentation-blast-radius.md | Designing/auditing zones and tiers, north-south vs east-west, the flat-network and over-broad-rule (`any`/`0.0.0.0/0`/`world`) traps, microsegmentation, lateral-movement containment, firewall/SG default-deny, remote access (WireGuard, bastion vs IAP) |
    | rules/03-k8s-network-policy.md | Writing/auditing Kubernetes NetworkPolicy, CiliumNetworkPolicy, the namespaced default-deny (ingress AND egress) pattern, the "default-deny that isn't" trap, ANP/BANP, L7/identity policy, DNS-aware egress, egress gateways, Hubble visibility |
    | rules/04-service-mesh-mtls.md | The plaintext-internal-traffic problem, choosing/auditing a mesh (Istio sidecar vs ambient, Linkerd, Cilium mesh), mTLS everywhere, mesh authorization policy, SPIFFE identity, and deciding mesh vs plain TLS |
    | rules/05-edge-ingress-egress.md | WAF (CRS/Coraza), ingress/API-gateway hardening, TLS termination + re-encryption, trusted-IP handling behind Cloudflare, DDoS posture (edge scrubbing + self-hosted kernel hardening: SYN cookies/synproxy, conntrack, rp_filter, no open UDP reflectors), egress as a first-class control, FQDN allowlisting, blocking the metadata endpoint, the SSRF-meets-egress chain |
    | rules/06-dns-tls-pki.md | DNS security (DNSSEC, RPZ/DNS firewall, DoH/DoT, split-horizon, CAA, tunneling), TLS posture (1.3, ciphers, HSTS, OCSP), shrinking cert lifetimes + ACME automation, internal PKI (step-ca), short-lived certs, pinning tradeoffs, email authentication & anti-spoofing (SPF/DKIM/DMARC, MTA-STS/DANE, bulk-sender rules) |
    
    Cross-cutting tasks read multiple files: a full network audit touches all six; "lock down our
    cluster" is rules/02 + rules/03 (+ rules/04 if a mesh exists).
    
    ## Top 10 non-negotiables
    
    1. **Verify, don't locate-trust.** Access decisions bind to authenticated identity (workload or
       user) and posture, not to "it's on the internal network." A packet's source subnet is not a
       credential. (NIST SP 800-207; CISA ZTMM v2.0.)
    2. **Default-deny in BOTH directions.** Every namespace/zone denies ingress *and* egress by default;
       allows are explicit and identity-scoped. An ingress-only default-deny leaves the exfil door open.
    3. **No `any` / `0.0.0.0/0` / `world` to sensitive services.** A secrets store, DB, registry, or
       admin UI reachable from a broad entity is a Critical finding — render the effective rule and
       prove the path, don't trust the rule's name.
    4. **The "default-deny" must actually deny.** A baseline policy that allows all intra-cluster
       traffic is not default-deny; verify with a probe (cross-namespace `curl` should fail).
    5. **Encrypt internal traffic.** DB creds, JWTs, and app traffic crossing the pod/host network ride
       mTLS or TLS — never plaintext `ws://`/`http://`/unencrypted DB protocol. Mesh in `STRICT`, not
       permissive-forever.
    6. **Egress is a control, not a default-open pipe.** Default-deny egress, FQDN/IP allowlists, egress
       gateways/proxies for sensitive zones; block the cloud metadata endpoint (169.254.169.254,
       fd00:ec2::254) at the pod/host. This is the C2/exfil and SSRF-pivot chokepoint.
    7. **Microsegment east-west.** Contain blast radius so one popped workload can reach only its
       declared dependencies. Flat networks turn a single foothold into a cluster-wide incident.
    8. **Identity-aware access for humans.** ZTNA / identity-aware proxy (per-request identity + device
       posture) over flat VPN access; if VPN, WireGuard with per-peer keys, never SSH open to the world.
    9. **Certs are automated and short-lived.** Every cert (public and internal) is ACME/cert-manager
       issued and auto-renewed. CA/Browser Forum caps public certs at 200 days (2026-03-15) → 47 days
       (2029-03-15); manual renewal is now an outage generator.
    10. **Flows are visible.** You can answer "who talked to whom" — Hubble / flow logs / mesh telemetry
        on, exported to detection (sota-detection-engineering). You cannot secure traffic you can't see.
    
    ## Operating notes
    
    - Principles first, then the user's stack (Talos K8s + Cilium, step-ca, Caddy+CRS, Cloudflare);
      name alternatives when the stack is unknown.
    - **Verify versions and API status against current docs before committing them** — CNI features,
      mesh GA status, the ANP/BANP/ClusterNetworkPolicy API state, CRS version, and the CA/B cert
      schedule all move faster than this text. Be version-agnostic where a claim is unpinnable.
    - When this skill and a compliance mapping conflict, state both; do not silently relax a control.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related