mode
mode
¶
Hook mode: OCI hooks + per-container netns.
Uses OCI hooks to apply per-container nftables rules inside each container's network namespace. No root required — only podman and nft.
Orchestrates collaborators per lifecycle phase:
- RulesetBuilder (
nft.rules) — generates and verifies nft rulesets - DnsResolver (
dns.resolver) — pre-start domain resolution - ProfileLoader (
profiles) — allowlist profile composition - AuditLogger (
audit) — event logging - CommandRunner (
run) — subprocess execution (nft, nsenter) - dnsmasq (
dns.dnsmasq) — runtime DNS with nftset auto-population - hook_install (
hooks.install) — OCI hook file generation - state (
state) — per-container state bundle I/O
logger = logging.getLogger(__name__)
module-attribute
¶
HookMode(*, config, runner, audit, dns, profiles, ruleset)
¶
Hook-mode shield backend (Strategy, implements ShieldModeBackend).
Coordinates the full lifecycle of OCI-hook-based container firewalling.
Delegates to RulesetBuilder for nft generation, DnsResolver for
name resolution, ProfileLoader for allowlists, dnsmasq for
runtime DNS, and state for per-container persistence.
Create a hook mode backend with all collaborators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ShieldConfig
|
Shield configuration (provides state_dir). |
required |
runner
|
CommandRunner
|
Command runner for subprocess calls. |
required |
audit
|
AuditLogger
|
Audit logger for event logging. |
required |
dns
|
DnsResolver
|
DNS resolver for domain resolution and caching. |
required |
profiles
|
ProfileLoader
|
Profile loader for allowlist profiles. |
required |
ruleset
|
RulesetBuilder
|
Ruleset builder for nft generation and verification. |
required |
Source code in src/terok_shield/hooks/mode.py
pre_start(container, profiles, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Prepare for container start in hook mode.
Verifies setup, composes profiles, resolves DNS, writes allowlist, detects DNS tier, sets annotations, and returns the podman CLI arguments needed for shield protection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
security_deny
|
Sequence[str]
|
Hosts/IPs an upstream layer (executor's roster projection, carried by sandbox) generates for the t20 security-deny tier — vault hosts denied direct egress. |
()
|
provider_allow
|
Sequence[str]
|
Hosts/IPs generated for the t30 provider-allow tier — agent/provider egress endpoints. |
()
|
project_allow
|
Sequence[str]
|
Hosts/IPs authored by the orchestrator for the t40 project-allow tier (git remote, custom domains) — merged with the composed profiles. |
()
|
override
|
Sequence[str]
|
Hosts/IPs/CIDRs authored for the t10 break-glass override
tier, which sits above the security-deny; statically resolved
and seeded into a separate nft set. A CIDR opens a whole
subnet above the deny — accepted, but logged as a warning and
an |
()
|
Raises:
| Type | Description |
|---|---|
SetupRequiredError
|
When global hooks are not installed or need refreshing. |
Source code in src/terok_shield/hooks/mode.py
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
refresh(container, profiles, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Recompute an existing container's policy bundle before a plain restart.
The policy-authoring half of
pre_start without the
launch half: rewrites every tier from the caller's current data,
refreshes the static-resolution caches, and regenerates
ruleset.nft + the dnsmasq config — so the OCI hook applies
current policy at the next podman start instead of replaying
the bundle frozen at creation. Reuses every launch-detected fact the
bundle persisted — DNS tier, upstream DNS, network mode, loopback
ports — rather than re-detecting: the container's mounts and
annotations were built for those, a fresh detection could disagree
with them, and a restart stays free of podman info.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When the bundle carries no persisted DNS tier /
upstream DNS / network mode ( |
Source code in src/terok_shield/hooks/mode.py
allow_domain(container, domain)
¶
Record +domain in the runtime overlay and reload dnsmasq.
The overlay (policy/live) flips any prior deny of domain and
survives reloads; the dnsmasq restart picks up the new nftset=
line so future IP rotations of domain are auto-populated. The
IP-level allow (nft set update) is handled separately by allow_ip().
No-op when the container runs no dnsmasq (the static IP-level allow
already happened via allow_ip()).
Source code in src/terok_shield/hooks/mode.py
deny_domain(container, domain)
¶
Record -domain in the runtime overlay and reload dnsmasq.
Counterpart of allow_domain(): the dnsmasq restart picks up the
local= sinkhole, so domain stops resolving (NXDOMAIN) and the
deny fails fast in the DNS plane instead of timing out against the
filter.
No-op when the container runs no dnsmasq.
Source code in src/terok_shield/hooks/mode.py
allow_ip(container, ip)
¶
Live-allow an IP for a running container via nsenter.
Source code in src/terok_shield/hooks/mode.py
deny_ip(container, ip)
¶
Live-deny an IP for a running container via nsenter.
Removes from the nft allow set (best-effort), adds to the nft deny set,
and records -ip in policy/live so the deny sticks across
shield up / restart and flips any prior allow.
Source code in src/terok_shield/hooks/mode.py
shield_down(container, *, disengaged=False)
¶
Switch a running container to the DOWN posture (DISENGAGED when disengaged).
Plain DOWN accepts by default but keeps the deny set and both range
floors; DISENGAGED enforces nothing, so its deny sets are left empty —
the next shield up repopulates them from the composed policy.
Source code in src/terok_shield/hooks/mode.py
shield_quarantine(container)
¶
Total network blackout — drop all traffic, log dropped traffic.
Reads no settings — no DNS, no allowlists, no loopback ports,
no gateway probe, no profile lookup. build_quarantine /
verify_quarantine are static; the only inputs are the
container name and the live ruleset state (table-or-no-table).
Any config-conditional branch added here is a bug.
Source code in src/terok_shield/hooks/mode.py
shield_up(container)
¶
Restore normal deny-all mode for a running container.
The rebuild is delete table + re-apply, which would forget every
dnsmasq-learned allow-set element — a container coming out of the down
posture would suddenly lose IPs its workload already resolved (clients
cache answers, so they do not necessarily re-query). The allow sets
are therefore snapshotted before the rebuild and restored after it.
Source code in src/terok_shield/hooks/mode.py
shield_reset(container)
¶
Forget learned allow-set state — back to the just-launched contents.
Flushes both tier-40 project-allow sets and re-seeds them from the
effective policy in a single nft transaction, so authored literals
never blink out. dnsmasq-learned IPs vanish until the workload
resolves the corresponding names again; the operator overlay
(policy/live) and the deny tier are untouched.
Source code in src/terok_shield/hooks/mode.py
shield_state(container)
¶
Query the live nft ruleset to determine the container's shield state.
Source code in src/terok_shield/hooks/mode.py
list_rules(container)
¶
List current nft rules for a running container.
Source code in src/terok_shield/hooks/mode.py
preview(*, down=False, disengaged=False)
¶
Generate the ruleset that would be applied to a container.