state
state
¶
Per-container state bundle layout contract.
Every shielded container gets an isolated state directory. This module
is the single source of truth for where files live within it — all paths
are derived from a single state_dir root. Zero dependencies beyond
pathlib.
Bundle layout::
{state_dir}/
├── hooks/
│ ├── terok-shield-createRuntime.json
│ └── terok-shield-poststop.json
├── terok-shield-hook # entrypoint script (stdlib-only Python)
├── ruleset.nft # pre-generated nft ruleset (written by pre_start)
├── gateway # discovered gateway IP (written by OCI hook)
├── gateway_v6 # discovered IPv6 gateway
├── upstream.dns # upstream DNS address
├── dns.tier # active DNS tier (dig/getent/dnsmasq)
├── profile.allowed # IPs from DNS resolution
├── profile.domains # domain names for dnsmasq config
├── live.allowed # IPs from allow/deny
├── live.domains # domain overrides from allow_domain
├── deny.list # persistent deny overrides
├── denied.domains # denied domains from deny_domain
├── dnsmasq.conf # generated dnsmasq configuration
├── dnsmasq.pid # dnsmasq PID (in container netns)
├── dnsmasq.log # dnsmasq query log (for shield watch)
├── resolv.conf # bind-mounted over /etc/resolv.conf (dnsmasq tier)
├── interactive # interactive tier marker (e.g. "nflog")
├── container.id # podman container ID (short, 12-char hex)
└── audit.jsonl # per-container audit log
BUNDLE_VERSION = 4
module-attribute
¶
Integer version of the state bundle layout.
Bumped whenever the file layout changes in a backwards-incompatible way. The OCI hook hard-fails if the annotation version does not match.
hooks_dir(state_dir)
¶
hook_entrypoint(state_dir)
¶
hook_json_path(state_dir, stage)
¶
ruleset_path(state_dir)
¶
gateway_path(state_dir)
¶
gateway_v6_path(state_dir)
¶
upstream_dns_path(state_dir)
¶
dns_tier_path(state_dir)
¶
profile_allowed_path(state_dir)
¶
profile_domains_path(state_dir)
¶
live_allowed_path(state_dir)
¶
live_domains_path(state_dir)
¶
deny_path(state_dir)
¶
denied_domains_path(state_dir)
¶
dnsmasq_conf_path(state_dir)
¶
dnsmasq_pid_path(state_dir)
¶
dnsmasq_log_path(state_dir)
¶
resolv_conf_path(state_dir)
¶
Return the path to the pre-written resolv.conf for the dnsmasq tier.
pre_start() writes nameserver 127.0.0.1 here and passes
--volume {path}:/etc/resolv.conf:ro to podman. Podman detects the
user-supplied mount and skips its automatic pasta-generated resolv.conf,
so the container's DNS is directed to the per-container dnsmasq instance
at 127.0.0.1:53. The read-only mount prevents the container payload
from redirecting DNS away from dnsmasq.
Source code in src/terok_shield/core/state.py
interactive_path(state_dir)
¶
container_id_path(state_dir)
¶
audit_path(state_dir)
¶
read_interactive_tier(state_dir)
¶
Read the interactive tier from the state bundle.
Returns "nflog" (or whatever tier string is stored) if the file
exists and contains a non-empty value, otherwise None.
Source code in src/terok_shield/core/state.py
read_allowed_ips(state_dir)
¶
Merge IPs from profile.allowed and live.allowed, deduplicated.
Returns a stable-order list: profile IPs first, then live IPs, with duplicates removed (first occurrence wins).
Source code in src/terok_shield/core/state.py
read_denied_ips(state_dir)
¶
Read IPs from deny.list.
Returns an empty set if the file does not exist.
Source code in src/terok_shield/core/state.py
read_effective_ips(state_dir)
¶
Compute effective allowed IPs: (profile ∪ live) − deny.
Returns a stable-order list with denied IPs subtracted.
Source code in src/terok_shield/core/state.py
ensure_state_dirs(state_dir)
¶
Create the state directory and its required subdirectories.