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
Install
npx skills add https://github.com/martinholovsky/SOTA-skills/tree/main/skills/sota-network-security
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install martinholovsky-sota-skills@llmmart
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).
- 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.
- Read the matching rules file before writing config. Segmentation (rules/02) precedes policy detail; identity-aware access (rules/01) frames everything.
- 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
worldentity wherever the policy language can express identity. Where it genuinely cannot — vanilla NetworkPolicy has no identity selector for a destination outside the cluster — a tightipBlockis 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. - 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.
- 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.
- 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 R4orR-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
- 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.)
- 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.
- No
any/0.0.0.0/0/worldto 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. - 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
curlshould fail). - 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 inSTRICT, not permissive-forever. - 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.
- 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.
- 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.
- 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.
- 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.
Reviews (0)
No reviews yet.
No comments yet.