Chapter 7 — Critical infrastructure and sector security: data and telemetry
Lesson map
Lesson 07 published. Map, example, EX025.
flowchart TD
N0["Learning outcomes and connection"]
N1["7.1 Sources and blind spots"]
N2["7.2 Syslog structure"]
N3["7.3 Time"]
N4["7.5 Practical activity"]
N5["Chapter synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Worked example"]
EX --> Q["Exercise EX025"]
Logical drawing
flowchart LR
N0["Learning outcomes and connection"]
N1["7.1 Sources and blind spots"]
N2["7.2 Syslog structure"]
N3["7.3 Time"]
N4["7.5 Practical activity"]
N5["Chapter synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX025"]
[Learning outcomes and connection]
|
v
[7.1 Sources and blind spots]
|
v
[7.2 Syslog structure]
|
v
[7.3 Time]
|
v
[7.5 Practical activity]
|
v
[Chapter synthesis]
|
v
[Worked example] --> [Exercise EX025]
Learning outcomes and connection
After this chapter you should be able to:
- name the telemetry sources a technician uses in a control environment — process data, device logs, firewall and host logs, flow records, passive captures — and what each can and cannot show;
- parse RFC 5424 syslog, compute facility and severity from the priority value, and handle lines that are not RFC 5424;
- measure and correct clock offsets against an NTP reference and build a single UTC timeline;
- explain how an uncorrected clock reverses cause and effect.
Chapter 6 said where things are. This chapter is about what they tell you, and whether you can put their stories in the right order.
7.1 Sources and blind spots
| Source | Shows | Does not show |
|---|---|---|
| Process data (historian) | Values and alarms over time | Who caused a change |
| HMI and SCADA event logs | Operator actions, alarm acknowledgements | Actions taken directly on the controller |
| Controller/RTU logs | Configuration sessions, mode changes (if supported and enabled) | Often nothing; many devices log little |
| Firewall logs | Allowed and denied connections at the boundary | Traffic inside a zone |
| Host logs (jump host, servers) | Logins, commands, service events | Activity on devices without logging |
| Passive capture / flow records | Who talked to whom, with what function | Encrypted content; traffic not passing the sensor |
NIST's log management guide describes collection, retention and protection of these records (SP 800-92). The technician's first job is to know which source answers which question, and which questions no source answers.
7.2 Syslog structure
RFC 5424 defines the modern syslog format: <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID STRUCTURED-DATA MSG (RFC 5424). The priority value is facility × 8 + severity: <38> is facility 4 (security/authorization), severity 6 (informational). The timestamp is RFC 3339 with an offset, and a dash (-) means "nil" — for a timestamp, the sender did not know the time. Many devices still send the older BSD format (<13>Oct 11 23:15:02 host app: ...) with no year and no zone; count such lines and parse them separately rather than dropping them.
7.3 Time
Every timeline is only as good as its clocks. NTP (RFC 5905) lets hosts measure their offset from a reference (RFC 5905). In control environments some devices have no NTP client, drift by minutes, or are deliberately isolated from time sources. Record, for every host that contributes to a timeline, its measured offset at a known moment, and correct its timestamps by subtracting it: corrected = reported − (host clock − reference). Convert everything to UTC before sorting; local offsets such as +01:00 are correct but easy to misorder by eye.
7.4 Worked example: the 23:14 sequence (synthetic)
On the night of 11 October, an operator reported that pump 2 at KW-04 started unexpectedly. You collected six lines and measured clock offsets the next morning (the RTU was 212 s behind the reference).
Lab 7 (save as lab07.py, run with python3 lab07.py):
# Lab 7 - parse RFC 5424 syslog, correct clock offsets and build a UTC timeline (offline, stdlib)
# Synthetic lines from the Kestrel Vale operations centre; offsets measured against the NTP reference.
import re
from datetime import datetime, timedelta, timezone
lines = [
"<38>1 2026-10-11T23:14:05.120Z fw-01 filterlog - - - block tcp 10.30.3.7:51544 -> 10.10.4.2:502",
"<86>1 2026-10-12T00:13:58+01:00 jump-01 sshd 812 - - Accepted publickey for jtan from 10.90.0.40",
"<134>1 2026-10-11T23:16:40Z hmi-02 hmi - - - operator setpoint change KW-04 PUMP2 start",
"<38>1 2026-10-11T23:15:31Z rtu-kw04 auth - - - config session opened user=eng",
"<13>Oct 11 23:15:02 eng-ws-15 app: project download started",
"<38>1 - hist-02 histd - - - archive rollover",
]
offset_s = {"fw-01": 0.0, "jump-01": 0.4, "hmi-02": 0.0, "rtu-kw04": -212.0} # host clock minus reference
pat = re.compile(r"^<(\d{1,3})>1 (\S+) (\S+) (\S+) (\S+) (\S+) (\S+) ?(.*)$")
events, unparsed = [], []
for ln in lines:
m = pat.match(ln)
if not m: unparsed.append(("not RFC 5424", ln)); continue
pri, ts, host, app, _pid, _msgid, _sd, msg = m.groups()
if ts == "-": unparsed.append(("no timestamp", ln)); continue
t = datetime.fromisoformat(ts.replace("Z", "+00:00")).astimezone(timezone.utc)
t_corr = t - timedelta(seconds=offset_s.get(host, 0.0))
fac, sev = divmod(int(pri), 8)
events.append((t_corr, host, f"fac={fac} sev={sev}", msg, offset_s.get(host, 0.0)))
for t, host, fs, msg, off in sorted(events):
note = f" (clock corrected {-off:+.1f} s)" if off else ""
print(f"{t.strftime('%H:%M:%S.%f')[:-3]}Z {host:<9} {fs:<11} {msg}{note}")
for why, ln in unparsed: print("UNPARSED", why, "|", ln)
Expected output (Python 3.13.5):
23:13:57.600Z jump-01 fac=10 sev=6 Accepted publickey for jtan from 10.90.0.40 (clock corrected -0.4 s)
23:14:05.120Z fw-01 fac=4 sev=6 block tcp 10.30.3.7:51544 -> 10.10.4.2:502
23:16:40.000Z hmi-02 fac=16 sev=6 operator setpoint change KW-04 PUMP2 start
23:19:03.000Z rtu-kw04 fac=4 sev=6 config session opened user=eng (clock corrected +212.0 s)
UNPARSED not RFC 5424 | <13>Oct 11 23:15:02 eng-ws-15 app: project download started
UNPARSED no timestamp | <38>1 - hist-02 histd - - - archive rollover
Before correction, the RTU line (23:15:31Z) appeared before the HMI setpoint change (23:16:40Z), suggesting someone configured the RTU and then the operator acted. After correction the RTU session happened at 23:19:03Z — after the operator's start command. The story changes from "someone tampered first" to "an engineering session followed the operator's action", which leads to a different question (who opened a configuration session at 23:19 and why). Two lines could not be placed: the BSD-format line from the engineering workstation has no year or zone, and the historian line has a nil timestamp. They are reported, not discarded; the engineering workstation's "project download started" is potentially the most important line of all, and placing it requires its own clock check and the workstation's local logs.
7.5 Practical activity
Measure the offset of a machine you own against a public or local NTP server (for example chronyc tracking or ntpq -p on Linux, w32tm /stripchart on Windows), record it with the time of measurement, and repeat after 24 hours to see drift.
Record the host, the reference, the offset and the time of measurement. After twenty-four hours, compare the new offset with the old one to see drift. Keep both readings with the timeline work in Lab 7, so a later investigation knows how uncertain a correction made from a single measurement would be.
7.6 Common errors and limits
- Sorting by the text of mixed-offset timestamps.
- Correcting clocks in the wrong direction; write corrected equals reported minus offset every time.
- Dropping lines that do not parse; count and report them.
- Assuming an offset measured today applied last night; drift and resynchronization change it, so state the uncertainty.
- Assuming the absence of a log line means the absence of an event — many devices simply do not log.
- Merging logs from devices whose offsets were measured at different times without recording each measurement time beside its offset.
- Reporting a corrected time to the second when the offset itself is only known to within a few seconds.
Exercises — chapter 7
EX025 — Decode priorities. Objective: compute facility and severity. Inputs: <86>, <134>, <11>, <165>. Steps: compute each. Expected evidence: four pairs with names. Rubric: 3 = 86 → 10/6 (authpriv, informational); 134 → 16/6 (local0, informational); 11 → 1/3 (user, error); 165 → 20/5 (local4, notice); 2 = arithmetic right, names missing; 1 = partial; 0 = none.
EX026 — Correct a clock. Objective: apply the offset formula. Inputs: fep-01 logs an event at 02:10:00Z; its offset measured an hour later was +47 s (host ahead). Steps: give the corrected time and the uncertainty you would state. Expected evidence: corrected time and a sentence on uncertainty. Rubric: 3 = 02:09:13Z; uncertainty because the offset was measured later and drift is unknown, e.g. "±a few seconds; offset measured 03:10Z"; 2 = right time without uncertainty; 1 = wrong direction; 0 = none.
EX027 — Place the BSD line. Objective: handle legacy formats safely. Inputs: the eng-ws-15 line in Lab 7. Steps: list what you need to place it on the timeline and write the parsing rule you would add. Expected evidence: list and rule. Rubric: 3 = year from collection context, the host's time zone setting and its measured offset; parse with explicit zone assignment and mark the result "assumed zone"; 2 = rule without the assumptions recorded; 1 = drop it; 0 = none.
EX028 — Blind-spot question. Objective: match questions to sources. Inputs: "Did anyone write to KW-04 directly from the station's local laptop port?" Steps: say which sources could answer it, which cannot, and what you would need to add. Expected evidence: short analysis. Rubric: 3 = control-centre firewall and capture cannot see local station traffic; RTU logs might, if it logs configuration and writes; add a station-level sensor or RTU logging and physical access records; 2 = partly; 1 = "check the firewall"; 0 = none.
Chapter synthesis
Telemetry is a set of partial witnesses. Know what each source can see, parse formats strictly while counting what you cannot parse, and correct every clock against a reference before you order events. A timeline built on uncorrected clocks can reverse cause and effect; one built on corrected clocks, with its uncertainties stated, is evidence.
Continue studying — chapter 07 of 18
← Chapter 06 · Course chapter index · Chapter 08 →
Continue to the next authored lesson.