config
config
¶
Shield configuration types, enums, and mode protocol.
Defines the vocabulary shared across the entire codebase: what a shield configuration looks like, what modes and states exist, and what contract a mode backend must satisfy.
ANNOTATION_LIST_SEP = ':'
module-attribute
¶
ANNOTATION_KEY = 'terok.shield.profiles'
module-attribute
¶
ANNOTATION_NAME_KEY = 'terok.shield.name'
module-attribute
¶
ANNOTATION_STATE_DIR_KEY = 'terok.shield.state_dir'
module-attribute
¶
ANNOTATION_VERSION_KEY = 'terok.shield.version'
module-attribute
¶
ANNOTATION_AUDIT_ENABLED_KEY = 'terok.shield.audit_enabled'
module-attribute
¶
ANNOTATION_UPSTREAM_DNS_KEY = 'terok.shield.upstream_dns'
module-attribute
¶
ANNOTATION_DNS_TIER_KEY = 'terok.shield.dns_tier'
module-attribute
¶
WILDCARDS_NEED_LIVE_TIER = 'Wildcard entries need live DNS resolution, and this host runs the {tier} tier: {names}. Use an allowlist without wildcard entries, or install dnsmasq with nftset support.'
module-attribute
¶
Launch refusal for a *. entry on a tier that resolves names once.
DnsTier
¶
Bases: Enum
How domain allowlists reach the nft allow sets.
dnsmasq with --nftset adds every answered address to the
allow sets before the reply reaches the workload. Follows IP rotation, covers subdomains, accepts wildcard entries, names blocked domains.
DNSMASQ_STATIC: a dnsmasq built without nftset support. The allow sets are
resolved once at launch; the query log still names blocked domains.
LOOKUP: no dnsmasq. The allow sets are resolved once at launch with
dig or drill.
GETENT: no dnsmasq and no lookup tool. The allow sets are resolved once
at launch with getent hosts.
DNSMASQ_LIVE = 'dnsmasq-live'
class-attribute
instance-attribute
¶
DNSMASQ_STATIC = 'dnsmasq-static'
class-attribute
instance-attribute
¶
LOOKUP = 'lookup'
class-attribute
instance-attribute
¶
GETENT = 'getent'
class-attribute
instance-attribute
¶
live
property
¶
True when the allow sets follow DNS answers as they arrive.
runs_dnsmasq
property
¶
True when a per-container dnsmasq serves the container's DNS.
hint
property
¶
What a degraded tier lacks and what restores the live one; empty for the live tier.
parse(recorded)
classmethod
¶
The tier a recorded name means; None for a name that is not a tier.
A retired name reads as the tier it named, so a container recorded under it restarts instead of being recreated.
Source code in src/terok_shield/config.py
ShieldMode
¶
ShieldState
¶
Bases: Enum
Per-container shield state, derived from the live nft ruleset.
QUARANTINE: Total network blackout — all traffic dropped, dropped traffic logged. UP: Normal enforcing mode (deny-all with allowlists). DOWN: Accept-by-default posture, private-range protection retained (RFC 1918 + RFC 4193). DISENGAGED: Accept-everything posture — no deny set, no private-range or hard-deny reject. OFFLINE: No ruleset found (container stopped or unshielded). ERROR: Ruleset present but unrecognised.
QUARANTINE = 'quarantine'
class-attribute
instance-attribute
¶
UP = 'up'
class-attribute
instance-attribute
¶
DOWN = 'down'
class-attribute
instance-attribute
¶
DISENGAGED = 'disengaged'
class-attribute
instance-attribute
¶
OFFLINE = 'offline'
class-attribute
instance-attribute
¶
ERROR = 'error'
class-attribute
instance-attribute
¶
ShieldRuntime
¶
Bases: Enum
Container runtime category — drives DNS-reachability assumptions.
crun / runc / youki. The container shares the netns,
so dnsmasq on 127.0.0.1 is reachable directly.
KRUN: libkrun microVM. The guest has its own loopback isolated
from the netns, so dnsmasq must bind to a link-local address
on netns lo that the guest can reach via passt.
DEFAULT = 'default'
class-attribute
instance-attribute
¶
KRUN = 'krun'
class-attribute
instance-attribute
¶
from_runtime_name(name)
classmethod
¶
Map a podman --runtime <name> string (or None) to the enum.
Centralises the wire-format vocabulary so callers don't repeat
"krun" → KRUN mappings inline. Anything other than
"krun" (including None and unknown runtime names) maps
to DEFAULT — the loopback-shared-with-netns assumption holds
for every runtime shield has been tested against besides krun.
Source code in src/terok_shield/config.py
ShieldConfig(state_dir, mode=ShieldMode.HOOK, default_profiles=('dev-standard',), loopback_ports=(), audit_enabled=True, profiles_dir=None, runtime=ShieldRuntime.DEFAULT, dns_cache_dir=None, dnsmasq_path=None)
dataclass
¶
Per-container shield configuration.
The library is a pure function of its inputs. Given a
ShieldConfig with state_dir, it writes to that directory
and nowhere else. No env-var reading, no config-file parsing.
state_dir
instance-attribute
¶
mode = ShieldMode.HOOK
class-attribute
instance-attribute
¶
default_profiles = ('dev-standard',)
class-attribute
instance-attribute
¶
loopback_ports = ()
class-attribute
instance-attribute
¶
audit_enabled = True
class-attribute
instance-attribute
¶
profiles_dir = None
class-attribute
instance-attribute
¶
runtime = ShieldRuntime.DEFAULT
class-attribute
instance-attribute
¶
dns_cache_dir = None
class-attribute
instance-attribute
¶
Resolved-allowlist cache shared across containers.
The one deliberate exception to the state_dir-only rule: many tasks with the
same allowlist share one resolve. None selects
dns_cache_dir under the shield state
root. Only the tiers that resolve at launch use it.
dnsmasq_path = None
class-attribute
instance-attribute
¶
The dnsmasq binary to run; None finds one on the current host PATH.
Set it for a dnsmasq built outside the distro package, for example one built with nftset support in the operator's home.
ShieldModeBackend
¶
Bases: Protocol
Strategy protocol for shield mode implementations.
Each concrete backend (e.g. HookMode) provides the full
lifecycle: per-container firewalling, live allow/deny, posture
transitions, and preview.
pre_start(container, profiles, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Prepare for container start; return extra podman args.
security_deny / provider_allow / project_allow / override are the
caller-generated t20 / t30 / t40 / t10 tiers
(see Shield.pre_start).
Source code in src/terok_shield/config.py
refresh(container, profiles, *, security_deny=(), provider_allow=(), project_allow=(), override=())
¶
Recompute an existing container's policy bundle before a plain restart.
Same tier data as
pre_start, no
launch half — rewrites tiers, caches, and pre-applied artifacts only.
Source code in src/terok_shield/config.py
allow_ip(container, ip)
¶
allow_domain(container, domain)
¶
deny_ip(container, ip)
¶
deny_domain(container, domain)
¶
list_rules(container)
¶
shield_down(container, *, disengaged=False)
¶
shield_quarantine(container)
¶
shield_up(container)
¶
shield_reset(container)
¶
shield_state(container)
¶
detect_dns_tier(has, *, dnsmasq_usable=False, nftset=False)
¶
The best tier the host supports.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
has
|
Callable[[str], bool]
|
Says whether a named tool exists on the host ( |
required |
dnsmasq_usable
|
bool
|
A dnsmasq binary was found and can read its config from the state directory. |
False
|
nftset
|
bool
|
That dnsmasq is built with nftset support. |
False
|