Skip to content

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 .policy extension.

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:

eghost policy new 90-denyurlshort

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:

/etc/enforcegate/rules.d/90-denyurlshort.policy
# 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-list references a text file containing one domain per line.
  • When a request matches any domain in the list, the action: deny triggers and the engine emits a verdict redirect URL.
  • The description is shown to the visitor on the captive-portal block page.
  • Lines beginning with # are comments — they appear in the source file but are ignored by egpolicy compile.

Example 2 — Allowing Swiss domains

Create a higher-precedence rule that explicitly permits requests to .ch domains:

eghost policy new 10-allowswissdomains
/etc/enforcegate/rules.d/10-allowswissdomains.policy
# 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 the 90- rules file.
  • The regular expression matches any HTTP(S) URI whose hostname ends with .ch.
  • Matching requests receive action: permit and 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.

eghost policy new 40-block-social-media
/etc/enforcegate/rules.d/40-block-social-media.policy
# 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 clishow 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.txt are blocked — visitors are redirected to the captive portal with "Access to URL shortening services is not permitted".
  • Requests to any .ch domain are explicitly permitted.
  • Everything else falls through to the synthesised default-permit verdict 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:

docker exec enforcegate egpolicy load

egpolicy load performs the following:

  1. Scans and parses every .policy file under /etc/enforcegate/rules.d/ in lexicographical order.
  2. Validates the syntax of each block — any error aborts the load with a precise diagnostic.
  3. 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).
  4. 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, onlineeghost clirequest 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 shell and read it with duckdb /var/lib/enforcegate/engine.db for ad-hoc SQL, or copy it out with docker cp and 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 sees CONNECT host:port only. 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.