Chapter 10 — Critical infrastructure and sector security: control configuration
Lesson map
Lesson 10 published. Map, example, EX037.
flowchart TD
N0["Learning outcomes and connection"]
N1["10.1 Rule sets as programs"]
N2["10.2 Patterns that erode a policy"]
N3["10.3 Remote and supplier access"]
N4["10.5 Change planning"]
N5["10.6 Practical activity"]
N6["Chapter synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Worked example"]
EX --> Q["Exercise EX037"]
Logical drawing
flowchart LR
N0["Learning outcomes and connection"]
N1["10.1 Rule sets as programs"]
N2["10.2 Patterns that erode a policy"]
N3["10.3 Remote and supplier access"]
N4["10.5 Change planning"]
N5["10.6 Practical activity"]
N6["Chapter synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX037"]
[Learning outcomes and connection]
|
v
[10.1 Rule sets as programs]
|
v
[10.2 Patterns that erode a policy]
|
v
[10.3 Remote and supplier access]
|
v
[10.5 Change planning]
|
v
[10.6 Practical activity]
|
v
[Chapter synthesis]
|
v
[Worked example] --> [Exercise EX037]
Learning outcomes and connection
After this chapter you should be able to:
- translate declared conduits (Chapter 6) into a first-match firewall policy with a default deny;
- evaluate a rule set against test flows in code, and detect shadowed and redundant rules;
- recognize the configuration patterns that silently widen access — "temporary" broad rules, overlapping "duplicates", any-port allows;
- configure supplier and remote access as time-bound, logged and routed through the management zone;
- plan a configuration change so that it can be verified (Chapter 11) and rolled back.
Chapters 6–9 described and observed the system. This chapter changes it, starting with the control that most directly limits who can reach an unauthenticated protocol: the conduit firewall.
10.1 Rule sets as programs
Most network firewalls evaluate rules top to bottom and apply the first match. NIST's firewall guide describes policy design, default deny and rule-set management (SP 800-41 Rev. 1). Thinking of a rule set as a small program clarifies three defects:
- Shadowed rule: a later rule whose traffic is entirely matched by an earlier rule with the opposite action. It never takes effect, though its comment says it does.
- Redundant rule: a later rule entirely matched by an earlier rule with the same action. Harmless today; confusing tomorrow, when someone deletes the earlier one.
- Broadening "duplicate": a rule that looks like a copy of another but has a wider source, destination or port. It is not redundant; it is a new permission.
Order matters, so every change must be reviewed in context, not as a single line.
10.2 Patterns that erode a policy
- Temporary rules that outlive their purpose ("allow corporate to control centre for the migration").
- Any-port allows (
tcp any) where a single port was needed. - Subnet sources where one host was needed.
- Rules without owner, ticket or expiry.
- Management access from user zones instead of through the jump host.
CISA's primary mitigations for operational technology emphasize removing internet exposure, changing default passwords, securing remote access and segmenting IT from OT (CISA primary mitigations for OT).
10.3 Remote and supplier access
Configure supplier access so that it exists only when needed: the account or path is disabled by default; enabled for a ticketed job and a fixed window; authenticated with multi-factor authentication at the jump host; the session is logged; and an alert fires on any use outside an enabled window. CISA's advisory on PRC state-sponsored actors describes actors maintaining long-term access to critical infrastructure environments, including through valid accounts and remote access (CISA AA24-038A) — the reason standing access is a finding even when nobody is misusing it today.
10.4 Worked example: fw-01 (synthetic)
The policy on fw-01 between the KVROC zones has seven rules. Lab 10 checks it for shadowing and against five test flows, each with the outcome the conduit list expects.
Lab 10 (save as lab10.py, run with python3 lab10.py):
# Lab 10 - evaluate a first-match firewall policy and find shadowed rules (offline, stdlib)
# Synthetic conduit policy for fw-01 between Kestrel Vale zones; addresses are RFC 1918.
from ipaddress import ip_address, ip_network
rules = [ # id, action, source, destination, protocol, port (None = any)
("R1", "allow", "10.20.0.10/32", "10.10.0.0/16", "tcp", 502), # SCADA server polls stations
("R2", "allow", "10.20.0.15/32", "10.10.0.0/16", "tcp", 502), # engineering workstation
("R3", "allow", "10.90.0.20/32", "10.20.0.0/24", "tcp", 22), # jump host to control centre
("R4", "allow", "10.30.0.0/16", "10.20.0.0/24", "tcp", None), # "temporary" corporate access
("R5", "deny", "10.30.3.0/24", "10.20.0.0/24", "tcp", 3389), # intended: block RDP from desks
("R6", "allow", "10.20.0.0/24", "10.10.4.0/24", "tcp", 502), # duplicate of R1 for KW-04
("R7", "deny", "0.0.0.0/0", "0.0.0.0/0", "any", None), # default deny
]
def matches(rule, src, dst, proto, port):
_, _, s, d, p, pt = rule
return (ip_address(src) in ip_network(s) and ip_address(dst) in ip_network(d)
and p in ("any", proto) and pt in (None, port))
def covers(a, b): # does earlier rule a match everything rule b matches?
return (ip_network(b[2]).subnet_of(ip_network(a[2])) and ip_network(b[3]).subnet_of(ip_network(a[3]))
and a[4] in ("any", b[4]) and a[5] in (None, b[5]))
for i, r in enumerate(rules):
for earlier in rules[:i]:
if covers(earlier, r):
kind = "redundant" if earlier[1] == r[1] else "SHADOWED (never takes effect)"
print(f"{r[0]} {kind}: fully covered by {earlier[0]}"); break
tests = [("10.20.0.10", "10.10.4.2", "tcp", 502, "allow"), ("10.30.3.7", "10.20.0.5", "tcp", 3389, "deny"),
("10.30.3.7", "10.10.4.2", "tcp", 502, "deny"), ("10.30.8.2", "10.20.0.5", "tcp", 445, "deny"),
("10.20.0.12", "10.10.4.2", "tcp", 502, "deny")]
for src, dst, proto, port, expect in tests:
hit = next(r for r in rules if matches(r, src, dst, proto, port))
status = "PASS" if hit[1] == expect else "FAIL"
print(f"{status} {src}->{dst} {proto}/{port}: {hit[1]} by {hit[0]} (expected {expect})")
Expected output (Python 3.13.5):
R5 SHADOWED (never takes effect): fully covered by R4
PASS 10.20.0.10->10.10.4.2 tcp/502: allow by R1 (expected allow)
FAIL 10.30.3.7->10.20.0.5 tcp/3389: allow by R4 (expected deny)
PASS 10.30.3.7->10.10.4.2 tcp/502: deny by R7 (expected deny)
FAIL 10.30.8.2->10.20.0.5 tcp/445: allow by R4 (expected deny)
FAIL 10.20.0.12->10.10.4.2 tcp/502: allow by R6 (expected deny)
Three findings:
- R5 is shadowed by R4. The comment says RDP from corporate desks is blocked, but R4 already allowed all TCP from 10.30.0.0/16, so R5 never applies — the "block" has never worked.
- R4 is the real problem. A "temporary" rule allows every TCP port from the whole corporate zone into the control centre. Its tests fail (RDP and SMB are allowed). Note what the tests do not show: corporate desk to station port 502 is denied by R7, so fw-01 does not explain the corporate-desk writes captured at KW-04 in Chapter 2. That path must exist somewhere else; record it as open (Chapter 13 finds it).
- R6 is not a duplicate of R1. It allows the whole control-centre subnet to write to KW-04, including 10.20.0.12, which has no business there.
A sound change: delete R4 (after confirming with the owner what still uses it — the firewall logs will show); move the RDP block above any allow if it is still needed, or rely on the default deny; replace R6 with nothing (R1 and R2 already cover the required paths). Re-run the evaluator with the new rule set before the change and the live tests afterwards (Chapter 11).
10.5 Change planning
Every configuration change in a control environment needs: the exact before and after; the reason (finding or requirement); the expected effect, written as tests; a rollback plan; the window agreed with operations; and who verifies. NIST's guide to security-focused configuration management describes baselines and change control (SP 800-128). The firewall export before and after is evidence; hash it (Chapter 5).
Export and hash the rule set before and after the change, and keep both digests in the change record. Write the expected effect as the same tests Lab 10 runs, so verification is a re-run rather than a new invention. Agree the window with operations, and name who will confirm that SCADA polling still meets the freshness requirement after the change.
10.6 Practical activity
Rewrite the fw-01 policy from the conduit list in Chapter 6: one rule per declared conduit, each with ticket and owner in its comment, then the default deny. Run Lab 10 on your version until all five tests pass and no rule is shadowed or redundant. Add two tests of your own for conduit C3.
Put each allow above the default deny, with ticket and owner in the comment. Remove R4 and R6 rather than trying to patch them. Add two tests for conduit C3: jump host to control centre on tcp/22 allowed, and a corporate desk to the same port denied. Re-run until every test passes and no rule is shadowed or redundant.
10.7 Common errors and limits
- Reviewing a new rule without reading the rules above it that may already allow or deny the same traffic.
- Deleting a broad rule without checking its current users in the logs.
- Treating "looks like a duplicate" as "safe to keep".
- Configuring supplier access as permanently enabled "for emergencies".
-
Lab 10 models a stateless first-match policy with exact containment; real firewalls add state, objects, NAT and zones, and their own analysis tools.
-
Narrowing R4 to fix the tcp/3389 and tcp/445 failures without first checking the logs for corporate hosts that rely on it today.
- Enabling a supplier account for a ticketed job and forgetting to disable it when the fixed window closes.
Exercises — chapter 10
EX037 — Explain the shadow. Objective: reason about order. Inputs: Lab 10. Steps: (1) move R5 above R4 and predict the test results; (2) run and compare. Expected evidence: prediction and output. Rubric: 3 = no shadowing reported; test 2 (RDP) passes (deny by R5); test 4 (SMB) still fails (allow by R4); test 5 still fails (R6); 2 = mostly; 1 = guesses; 0 = none.
EX038 — Fix the policy. Objective: produce a correct rule set. Inputs: Lab 10. Steps: remove R4 and R6 (keep R5 or remove it with justification); run. Expected evidence: new rule list and output with five PASS lines. Rubric: 3 = five PASS lines; justification for R5 (redundant with the default deny once R4 goes, so remove or keep for explicit logging); notes the need to check R4's users in logs first; 2 = passes without justification; 1 = partial; 0 = none.
EX039 — Supplier access configuration. Objective: configure time-bound access. Inputs: the SS-3 relay vendor needs remote access about twice a year. Steps: specify the account state, enablement steps, path, authentication, logging, alerting and expiry. Expected evidence: a configuration specification. Rubric: 3 = disabled by default; enabled by KVROC on-call against a ticket approved by Kestrel Grid; path via jump-01 with MFA and session recording; never directly to the relay network; alert on use outside a window; automatic expiry; next-day review; 2 = one element missing; 1 = "VPN for vendor"; 0 = none.
EX040 — Detect broadening. Objective: extend the evaluator. Inputs: Lab 10's covers. Steps: add a check that reports when a later rule with the same action partially overlaps an earlier one and is broader in source or destination. Expected evidence: code and output. Rubric: 3 = reports R6 as broadening R1 (source /24 contains R1's /32; destination /24 inside /16; same port), explains why this deserves review; 2 = detects without explanation; 1 = attempted; 0 = none.
Chapter synthesis
A firewall rule set is a first-match program, and its defects are program defects: shadowed rules that never act, redundant rules that confuse, and look-alike rules that widen access. Configure conduits one rule per declared purpose with a default deny, keep supplier access disabled until needed, and plan every change with its tests and rollback. Then verify, which is the next chapter.
Continue studying — chapter 10 of 18
← Chapter 09 · Course chapter index · Chapter 11 →
Continue to the next authored lesson.