Configuration¶
terok-shield uses XDG-compliant directories and an optional YAML config file.
Config file¶
Optional. Located at ~/.config/terok/shield/config.yml
(or $XDG_CONFIG_HOME/terok/shield/config.yml).
The file is validated with Pydantic (extra="forbid"). Typos and wrong
types produce a clear error at startup instead of being silently ignored.
See Config Reference for the full field table
and annotated YAML example.
If the config file is missing, defaults are used. Unparseable YAML (syntax errors) falls back to defaults with a warning.
Library vs CLI
The config file is only read by the CLI. When using terok-shield as a
library, all configuration is passed programmatically via ShieldConfig.
Mode selection¶
Currently there is only one mode, the "hook mode". Setting mode: auto or mode: hook
both resolve to hook mode. Future modes may be added for different network
topologies.
Directories¶
State directory¶
Default: ~/.local/state/terok/shield/
Override: TEROK_SHIELD_STATE_DIR or --state-dir flag
Each container gets an isolated state bundle under containers/:
~/.local/state/terok/shield/
└── containers/
└── my-container/
├── policy/ # v15 tiered +/- policy, one file per tier set
│ ├── 10-override # → nft set t10_override (break-glass allow)
│ ├── 20-security-deny # → nft set t20_security_deny (vault hosts + operator deny)
│ ├── 30-provider-allow # → nft set t30_provider_allow (provider egress)
│ ├── 40-project-allow # → nft set t40_project_allow (project allowlist)
│ └── live # Runtime allow/deny overlay (+/- lines)
├── resolved.ips # Resolved allow IPs (t40 seed; every tier but dnsmasq-live)
├── ruleset.nft # Pre-generated nft ruleset (gateways baked in)
├── dnsmasq.conf # Generated dnsmasq config (dnsmasq tiers)
├── dnsmasq.command # Launch command, resolved on each start
├── dnsmasq.pid # dnsmasq PID (dnsmasq tiers)
├── dnsmasq.bin # Live dnsmasq identity (cleanup only)
├── resolv.conf # Bind-mounted /etc/resolv.conf (every tier)
├── upstream.dns # Persisted upstream DNS address
├── dns.tier # Persisted active DNS tier
└── audit.jsonl # Per-container audit log
Global hooks.
terok-shield setupinstalls hooks under<state_root>/shield/hooksand registers them incontainers.conf. Task preparation never modifies them; bare Podman restarts remain protected.
| File | Written by | Purpose |
|---|---|---|
ruleset.nft |
pre_start() |
Pre-generated nft ruleset applied by the hook (gateways baked in) |
policy/10-override |
pre_start() |
Break-glass allow tier (t10_override) |
policy/20-security-deny |
pre_start() / deny() |
Vault-host + operator deny tier (t20_security_deny) |
policy/30-provider-allow |
pre_start() |
Provider-egress allow tier (t30_provider_allow) |
policy/40-project-allow |
pre_start() |
Project allowlist tier (t40_project_allow) — authored domains and IPs |
policy/live |
allow() / deny() |
Runtime allow/deny overlay (+/- lines; a later verdict flips an earlier one) |
resolved.ips |
pre_start() / resolve() |
Resolved allow IPs seeding t40_project_allow (every tier but dnsmasq-live) |
dnsmasq.conf |
pre_start() |
Generated dnsmasq configuration (dnsmasq tiers) |
dnsmasq.command |
pre_start() |
Requested launch command: dnsmasq or the configured dnsmasq_path |
dnsmasq.pid |
OCI hook | dnsmasq PID for lifecycle management |
dnsmasq.bin |
OCI hook / reload | Selected executable identity for cleanup matching, not future launches |
resolv.conf |
pre_start() |
Points container DNS at dnsmasq, or at the upstream forwarder on the tiers without it |
upstream.dns |
pre_start() |
Persisted upstream DNS forwarder address |
dns.tier |
pre_start() |
Persisted tier (dnsmasq-live, dnsmasq-static, lookup, or getent) |
audit.jsonl |
Hook + Shield methods | Per-container audit log |
Config directory¶
Default: ~/.config/terok/shield/
Override: TEROK_SHIELD_CONFIG_DIR
| Path | Contents |
|---|---|
profiles/ |
Custom allowlist profiles (override bundled ones) |
config.yml |
Shield configuration |
DNS resolution¶
The active tier decides how the allowlist reaches the nft sets; see DNS tiers for what each tier provides.
On dnsmasq-live there is no resolution at launch: dnsmasq populates the sets
per query, before the reply reaches the workload (cache-size=0).
On every other tier the allowlist is resolved at launch into the per-container
resolved.ips cache, one IP per line, and the terminal reports the batch
before and after. Domains resolve concurrently with a 2 s per-lookup budget.
Entries older than 1 hour, or older than the authored policy, are re-resolved.
A shared host cache under <state root>/dns-cache/ (or
ShieldConfig.dns_cache_dir) lets every container with the same allowlist
reuse one resolution; a resolve where every domain failed is never shared.
dnsmasq binary¶
Shield finds dnsmasq on the current host PATH. To run a dnsmasq
installed elsewhere, for example one built with nftset support in your home,
set dnsmasq_path in config.yml (or ShieldConfig.dnsmasq_path). A path
that is not an executable file stops the launch.
If a runtime omits PATH (not merely leaves it empty), hooks use setup's
captured search path instead.
Force a cache refresh (all tiers):
Environment variables¶
| Variable | Purpose |
|---|---|
TEROK_SHIELD_STATE_DIR |
Override state directory location |
TEROK_SHIELD_CONFIG_DIR |
Override config directory location |
XDG_STATE_HOME |
XDG state base (default: ~/.local/state) |
XDG_CONFIG_HOME |
XDG config base (default: ~/.config) |
OCI annotations¶
These annotations are set automatically by terok-shield run (or
pre_start() in the Python API) and read by the OCI hook:
| Annotation | Value | Purpose |
|---|---|---|
terok.shield.profiles |
Colon-separated names | Which profiles to apply |
terok.shield.name |
Container name | Audit log identification |
terok.shield.state_dir |
Absolute path | Where the hook finds its state bundle |
terok.shield.loopback_ports |
Colon-separated ints | Ports for ruleset generation |
terok.shield.version |
Integer | Bundle version (hard-fail on mismatch — re-create the task to fix) |
terok.shield.audit_enabled |
true / false |
Whether to write audit logs |
terok.shield.upstream_dns |
IP address | Upstream DNS forwarder for dnsmasq |
terok.shield.dns_tier |
dnsmasq-live / dnsmasq-static / lookup / getent |
Active DNS resolution tier |