Policies¶
With the EnforceGate components running, define policies to control which web traffic is permitted, denied, warned about, or shown an Acceptable Use Policy. See the policy reference for the complete file-format specification.
What a policy is¶
A policy is an ordered collection of rule blocks. Each block has at least one match criterion and an associated action (permit, deny, warn, aup). When a request matches, the action determines how it is handled — passed through, blocked, or redirected to the captive portal for a verdict page.
Requests can be matched on the destination URI using regular expressions, a predefined list of domains, the SNI of bumped HTTPS sessions, source IP, user-agent strings, and other request attributes.
Rule files¶
Policy files (rule files) live in /etc/enforcegate/rules.d/ inside the container and follow this naming convention:
- Begin with a two-digit precedence number followed by a hyphen (
05-,40-). Lower numbers are evaluated first. - Include a short, descriptive name (e.g.
allowpackagemirrors). - End with the
.policyextension.
Rules files are loaded in lexicographical order, so the precedence prefix controls evaluation order. The shipped rules.d/ is empty on a fresh appliance — the no-match verdict is engine-synthesised from [policy].default_action, default "permit", so an out-of-the-box deployment passes all traffic until the operator writes their first rule.
The recommended way to author and manage policy files is via the eghost policy verbs:
eghost policy list # list every .policy file in rules.d/
eghost policy show <name> # print a policy's content
eghost policy new [name] # create a new policy file in $EDITOR
eghost policy edit <name> # edit an existing policy in $EDITOR
eghost policy remove <name> # delete a policy (asks for confirmation)
eghost policy edit and eghost policy new open the file in your $EDITOR, save it under /etc/enforcegate/rules.d/, and recompile + load the new policy set into the running engine on save.
Comment syntax
.policy files support comments in three styles: # line comments, // line comments, and /* … */ block comments.
Example 1 — Blocking URL shortening services¶
Block known URL-shortener domains by name. Create the rule file:
This opens your $EDITOR on a new file at /etc/enforcegate/rules.d/90-denyurlshort.policy. Add a policy block that denies requests matching a domain list:
# Deny URL shortening domains
deny-urlshortening-domains: {
match-domain-list: /etc/enforcegate/lists/urlshorts.txt
action: deny
description: Access to URL shortening services is not permitted
}
Explanation:
- The policy is named
deny-urlshortening-domains. match-domain-listreferences a text file containing one domain per line.- When a request matches any domain in the list, the
action: denytriggers and the engine emits a verdict redirect URL. - The
descriptionis shown to the visitor on the captive-portal block page. - Lines beginning with
#are comments — they appear in the source file but are ignored byegpolicy compile.
Example 2 — Allowing Swiss domains¶
Create a higher-precedence rule that explicitly permits requests to .ch domains:
# Allow websites with .ch gTLD
allow-swissdomains: {
match-uri-regex: ^(https?:\/\/)?[^\/]+[.]ch
action: permit
description: Swiss domain names ending with .ch
}
Explanation:
- The
10-precedence ensures this policy is evaluated before the90-rules file. - The regular expression matches any HTTP(S) URI whose hostname ends with
.ch. - Matching requests receive
action: permitand proceed without modification.
Example 3 — Time-scheduled rules¶
Any rule can carry a time-window: attribute and only match during a recurring weekly window — outside it, the rule falls through to the next rule. Useful for "allow during work hours, deny the rest of the time"-shaped policies without a second rule per direction.
# Block social media during business hours only
block-social-business-hours: {
match-domain-list: /etc/enforcegate/rules.d/lists/social-media.txt
action: deny
description: No social media during work hours
time-window: weekdays 08:00-18:00
}
Outside Monday–Friday 08:00–18:00 the rule does not match — the request falls through to whatever rule comes next, or ultimately to the engine's synthesised default_action verdict. The engine re-evaluates every request, so the verdict flips immediately on the next request after the window closes.
The window evaluates against [policy].time_window_tz (local by default, switchable to utc — see engine reference). Confirm what "local" means on the engine with eghost cli → show clock. Full grammar (day specifiers, the daily 22:00-06:00 wrap-past-midnight form, and the worked "allow during these hours, deny otherwise" composition) lives at Time-scheduled rules.
Resulting behaviour¶
With only these two policies and the shipped default_action = "permit":
- Requests to domains listed in
urlshorts.txtare blocked — visitors are redirected to the captive portal with "Access to URL shortening services is not permitted". - Requests to any
.chdomain are explicitly permitted. - Everything else falls through to the synthesised
default-permitverdict and is allowed.
To enforce a strict default-deny posture, set [policy].default_action = "deny" in engine.conf. No catch-all rule is needed — the no-match verdict is engine-synthesised, so it never occupies a rule id and never shadows lower-precedence rules in [policy].shared_path. See the allow-only-listed-internal-services recipe for the full pattern.
Loading policies¶
eghost policy new, eghost policy edit and eghost policy remove recompile and load the policy set automatically when you save and exit the editor (or confirm the removal). No additional step is required.
If you authored or modified .policy files outside the eghost policy workflow — for example through a bind-mount, docker cp, or an external orchestrator — trigger the in-container compile + load manually:
egpolicy load performs the following:
- Scans and parses every
.policyfile under/etc/enforcegate/rules.d/in lexicographical order. - Validates the syntax of each block — any error aborts the load with a precise diagnostic.
- Streams the parsed rules directly into the engine's DuckDB store at
/var/lib/enforcegate/engine.db(the single source of truth for the live policy — no intermediate JSON files). - Calls the engine's Control API to reload the in-memory policy graph.
On success, the engine immediately enforces the updated policies — no container restart required. The streaming loader is bounded in memory (~12 MB of buffer regardless of list size), so multi-million-rule domain lists load in seconds under typical container memory limits. See the reference for egpolicy for the underlying utility and additional options.
Inspecting the live policy¶
The engine's DuckDB at /var/lib/enforcegate/engine.db is the single source of truth for what the engine is enforcing right now. Two operator-facing ways to read it:
- Live engine, online —
eghost cli→request test-policy-uri "<URL>"(or the nicli form,egctl test-policy-uri). Returns the verdict for the URL, the matched rule name, and (Administrator level only) the matched regex pattern. The fastest way to verify a specific rule is doing what you intended. - Offline / forensics — the DB is a standard DuckDB file. Drop a shell into the container with
eghost shelland read it withduckdb /var/lib/enforcegate/engine.dbfor ad-hoc SQL, or copy it out withdocker cpand inspect from the host. Useful for audit, large diff against a backed-up snapshot, or root-cause analysis after a bad reload.
For point-in-time snapshots taken automatically before every successful reload, see policy rollback.
SSL inspection and HTTPS policies¶
URL-based matching on HTTPS traffic depends on the active SSL inspection mode:
off— the engine seesCONNECT host:portonly. URL-rewrite policies cannot match the path or any application-layer content. Use SNI-based or domain-based matching instead.peek— the engine sees the SNI in addition to the CONNECT target, but the body remains opaque. SNI-based matching works, but URL-path-based matching does not.bump— the engine sees fully decrypted HTTPS traffic. All URL-rewrite policies apply. Requires explicit operator acknowledgement — see SSL inspection.