◇ OpenCyber Academy
Technical · Chapter 14 · English

Chapter 14 — Critical infrastructure and sector security: automation

Open, free learning for anyone who wants to protect people and technology. No account is required to read this text or its worked answers.

Chapter 14 — Critical infrastructure and sector security: automation

Lesson map

Lesson 14 published. Map, example, EX053.

flowchart TD
  N0["Learning outcomes and connection"]
  N1["14.1 Why hashes"]
  N2["14.2 Four outcomes, four actions"]
  N3["14.4 Safe automation in control environments"]
  N4["14.5 Protecting the automation"]
  N5["14.6 Practical activity"]
  N6["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
  N6 --> EX["Worked example"]
  EX --> Q["Exercise EX053"]

Logical drawing

flowchart LR
  N0["Learning outcomes and connection"]
  N1["14.1 Why hashes"]
  N2["14.2 Four outcomes, four actions"]
  N3["14.4 Safe automation in control environments"]
  N4["14.5 Protecting the automation"]
  N5["14.6 Practical activity"]
  N6["Chapter synthesis"]
  N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX053"]
[Learning outcomes and connection]
    |
    v
[14.1 Why hashes]
    |
    v
[14.2 Four outcomes, four actions]
    |
    v
[14.4 Safe automation in control environments]
    |
    v
[14.5 Protecting the automation]
    |
    v
[14.6 Practical activity]
    |
    v
[Chapter synthesis]
    |
    v
[Worked example] --> [Exercise EX053]

Learning outcomes and connection

After this chapter you should be able to:

Chapters 10–13 changed, verified and troubleshot by hand. This chapter makes the repeatable checks automatic, so that a technician's attention goes to the differences that matter.

14.1 Why hashes

A SHA-256 digest (FIPS 180-4) of a file changes if any byte changes, and it is infeasible to construct a different file with the same digest. Comparing digests against a manifest recorded at a known-good moment tells you which files changed, cheaply and without storing copies. It does not tell you how they changed — for that you need the approved copy, kept in version control — and it does not tell you whether the change was legitimate — for that you need the change records. Python's hashlib provides SHA-256 in the standard library (Python hashlib).

14.2 Four outcomes, four actions

Outcome Meaning Action
Unchanged Matches the manifest Nothing
Changed, approved A change record covers it Verify (Chapter 11), then update the manifest
Changed, unapproved No record Preserve, investigate, then revert under a change
New or missing File appeared or vanished Investigate before deleting or restoring

The most dangerous automation mistake is "revert all drift automatically". It destroys the evidence of an unapproved change and may undo an emergency fix made at 03:00 that has not yet been written up. Automated detection is cheap and safe; automated correction needs a short, explicit list of what may be auto-fixed, and nothing that touches process control.

14.3 Worked example: drift on hist-02 (synthetic)

A nightly job compares hist-02's key files with the manifest approved after CHG-2207. Lab 14 simulates one night, using a temporary directory.

Lab 14 (save as lab14.py, run with python3 lab14.py):

# Lab 14 - detect configuration drift against an approved manifest (offline, standard library only)
# Files are created in a temporary directory that is removed at the end.
import hashlib, json, pathlib, tempfile
def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest()
with tempfile.TemporaryDirectory() as tmp:
    root = pathlib.Path(tmp)
    files = {"sshd_config": "ListenAddress 10.90.0.21\nPermitRootLogin no\nPasswordAuthentication no\n",
             "ntp.conf": "server 10.90.0.5 iburst\n",
             "fw-01.rules": "R1 allow 10.20.0.10 10.10.0.0/16 tcp 502\nR7 deny any any\n"}
    for n, t in files.items(): (root / n).write_text(t)
    manifest = {n: sha(root / n) for n in files}                       # approved baseline
    approved_changes = {"ntp.conf": "CHG-2210"}
    (root / "ntp.conf").write_text("server 10.90.0.5 iburst\nserver 10.90.0.6 iburst\n")   # approved
    (root / "sshd_config").write_text(files["sshd_config"].replace("PermitRootLogin no", "PermitRootLogin yes"))
    (root / "debug.sh").write_text("nc -l -p 9000\n")                  # new, unexpected file
    current = {p.name: sha(p) for p in sorted(root.iterdir())}
    for name in sorted(set(manifest) | set(current)):
        if name not in current: print(f"MISSING   {name}")
        elif name not in manifest: print(f"NEW       {name}  -> investigate before removing")
        elif current[name] != manifest[name]:
            tag = f"approved ({approved_changes[name]}) -> update baseline" if name in approved_changes else "UNAPPROVED -> preserve, then revert"
            print(f"CHANGED   {name}  {tag}")
        else: print(f"unchanged {name}")
    print("manifest digest", hashlib.sha256(json.dumps(manifest, sort_keys=True).encode()).hexdigest()[:16])

Expected output (Python 3.13.5):

NEW       debug.sh  -> investigate before removing
unchanged fw-01.rules
CHANGED   ntp.conf  approved (CHG-2210) -> update baseline
CHANGED   sshd_config  UNAPPROVED -> preserve, then revert
manifest digest fdea74f1d09e359c

fw-01.rules is unchanged. ntp.conf changed and CHG-2210 covers it (a second NTP server was added): verify that time is synchronized, then update the manifest. sshd_config changed with no record — someone re-enabled root login. debug.sh is new and contains a listener command. Both are preserved (copy, hash, timestamps, the account that last wrote them if the host records it) before anything is reverted, and the second is very likely to become an incident. The manifest's own digest is printed so that the manifest itself can be checked; store that digest somewhere the host's administrators cannot change (Chapter 18).

14.4 Safe automation in control environments

14.5 Protecting the automation

The automation account can read or change every host it manages, so: keep its credentials in a vault and rotate them; restrict where it may connect from; require review of changes to its desired state; alert on runs outside the schedule. The CISA advisory on long-term access to critical infrastructure describes actors using legitimate administration tools and valid accounts (CISA AA24-038A); an over-privileged automation account is exactly such a tool.

A tool that can change every host is as attractive to an adversary as an engineering workstation. Review changes to its desired state the same way you review firewall changes.

14.6 Practical activity

Extend Lab 14 so that, for an unapproved change, it writes a preservation record — file name, old digest, new digest, size and modification time — into the temporary directory before printing the finding, and prints the preservation record's own SHA-256.

Write the preservation record before printing the finding, and include the record's own digest in the output. That way a later investigator can show that the copy taken at detection time matches what the job claimed to save. Keep the temporary directory's cleanup after the hashes are recorded.

14.7 Common errors and limits

Exercises — chapter 14

EX053 — Classify and act. Objective: apply the four outcomes. Inputs: Lab 14 output. Steps: write the action and owner for each line, in order of execution. Expected evidence: ordered list. Rubric: 3 = preserve sshd_config and debug.sh first (copy, hash, metadata), notify security lead; investigate debug.sh before removal; revert sshd_config under a change after preservation; verify NTP and update manifest for ntp.conf; nothing for fw-01.rules; 2 = order wrong; 1 = revert all; 0 = none.

EX054 — Auto-fix list. Objective: limit automation. Inputs: candidate auto-fixes: re-enable log forwarding, reset sshd_config, restart historian service, correct NTP server list, push firewall rules, re-set PLC setpoint. Steps: mark each as auto-fix, ticket, or never-automate, with reasons. Expected evidence: six rows. Rubric: 3 = log forwarding and NTP list auto-fix (restore visibility and time, low process risk); sshd_config and firewall push ticketed (preservation and lock-out risk); historian restart ticketed with operations; PLC setpoint never automated by security tooling; 2 = one wrong; 1 = all auto; 0 = none.

EX055 — Halt guard. Objective: reduce blast radius. Inputs: a fleet of 14 hosts; on one night 9 show ntp.conf changed identically. Steps: propose a guard condition and explain what the pattern likely means. Expected evidence: guard and explanation. Rubric: 3 = halt when more than a set share (for example 25 %) of hosts show the same change; most likely an unrecorded but deliberate fleet change or a wrong manifest — confirm with the change calendar before any action; still treat as a finding until explained; 2 = guard without interpretation; 1 = revert all nine; 0 = none.

EX056 — Preservation record. Objective: extend the lab. Inputs: activity 14.6. Steps: implement and show output. Expected evidence: code and output. Rubric: 3 = record written before the finding line, contains the required fields, its digest printed; explains why the record's digest must be stored off-host; 2 = record without digest; 1 = attempted; 0 = none.

Chapter synthesis

Automate the checks that never change: collect, hash, compare with an approved manifest and classify differences. Act differently on approved, unapproved, new and missing items — preserve before you revert, and keep automatic correction to a short list that never touches the process. Protect the automation as the powerful tool it is.

Continue studying — chapter 14 of 18

← Chapter 13 · Course chapter index · Chapter 15 →

Continue to the next authored lesson.

Worked answers

EX053 — Classify and act. (1) Preserve sshd_config and debug.sh: copies, digests, size, mtime, owner, and relevant auth logs; inform L. Ferreira. (2) Investigate debug.sh (who created it, whether it ran, whether a listener is active) before removal — likely incident. (3) Revert sshd_config to the approved version under a change, after preservation; verify with sshd -T. (4) For ntp.conf: verify offset against both servers, then update the manifest with reference CHG-2210. (5) fw-01.rules: nothing.

EX054 — Auto-fix list. Re-enable log forwarding: auto-fix (restores visibility, no process effect). Correct NTP server list: auto-fix (time integrity, low risk). Reset sshd_config: ticket (preserve first; lock-out risk). Restart historian: ticket with operations (data gap). Push firewall rules: ticket with tests (can cut polling). Re-set PLC setpoint: never automated by security tooling — process control belongs to operations.

EX055 — Halt guard. Halt if the same key changes identically on more than 25 % of hosts (here 9 of 14 = 64 %). Most likely an unrecorded fleet change by the platform team or a stale manifest; check the change calendar and the desired-state repository. Until explained it remains a finding — an attacker with the automation account could produce the same pattern.

EX056 — Preservation record. Before printing the unapproved-change line: rec = {"file": name, "old": manifest[name], "new": current[name], "size": p.stat().st_size, "mtime": p.stat().st_mtime}; write it as JSON to root / f"preserve-{name}.json" and print its SHA-256. The record's digest must be stored off-host (case record or collector), because an administrator of the host could otherwise rewrite both the file and the record.