The context
A large refinery operates two sensitive units:
- Unit A — Atmospheric distillation: driven by a Siemens S7-1517F-3 PN/DP CPU on subnet
10.20.10.0/24. ATEX zone 1 atmosphere, SIL 2 on the process safety functions. - Unit B — Hydrocracker: driven by a Siemens S7-1518F-4 PN/DP CPU on subnet
10.20.20.0/24. ATEX zone 1 atmosphere, SIL 3 on some ESD loops.
The business requirement is simple: Unit B must continuously read 12 process variables from Unit A (cut temperatures, flow rates, column level) to optimize its own control. Acceptable latency: 200 ms. No round-trip command — flow A → B only.
The cybersecurity requirement is not: the two PLCs cannot sit on the same IP range. A compromise of A must not allow an attacker to pivot to B. Triton/Trisis (2017) demonstrated that, once inside the SIS of Unit A, an attacker could have disabled the explosion protections.
This is the typical situation faced by tens of thousands of industrial sites worldwide.
What you must NOT do
Three classic anti-patterns that solve the business problem but break the OT posture.
| Anti-pattern | Why it “works” | Why it’s dangerous |
|---|---|---|
| Put both CPUs on the same VLAN | ”Simple, they talk directly over S7” | A VLAN is not real segmentation. A compromised PLC sees the other. And Put/Get S7 requires enabling dangerous functions (force outputs, stop CPU). |
| Route the traffic through the head-office IT firewall | ”The central firewall sees everything” | The IT firewall is not qualified for industrial protocols. It lets S7 through without inspection, and it becomes a single point of failure. Worse: a WAN outage cuts the control loop. |
| Site-to-site IPsec VPN between the two units | ”Encrypted tunnel, logical isolation” | Once the tunnel is up, both networks see each other. Ransomware that encrypts A spreads to B in 30 seconds (cf. NotPetya / Maersk 2017). |
The 3 OT-compliant options
┌────────────────────────────────────────────────────────────────────────────────┐
│ Option 1 — PN/PN COUPLER ▶ Physical wiring, no IP routing │
│ │
│ CPU A ── PROFINET ── ┤PN/PN├ ── PROFINET ── CPU B │
│ │
│ + Maximum security (galvanic, absolute isolation) │
│ + No IP/firewall configuration — Plug & Play │
│ + Certified for SIL if you pick the safety variant │
│ − Limited throughput (1,408 bytes / direction) │
│ − Direct physical cable → suited to adjacent units only │
│ − No native audit trail or logs │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Option 2 — OPC UA via SCALANCE SC + INDUSTRIAL DMZ ◀ RECOMMENDED ▶ │
│ │
│ Unit A ┌───────────── DMZ ───────────┐ Unit B │
│ 10.20.10.0/24 │ 10.20.99.0/24 │ 10.20.20.0/24 │
│ │ │ │
│ CPU A ───SC1──┤ FW1 │── OPC UA proxy ───┤ FW2 │── SC2 ─── CPU B │
│ (OPC UA │ │ (OPC UA │
│ server) │ │ client) │
│ │
│ + End-to-end OPC UA Sign + Encrypt encryption │
│ + Mutual authentication via X.509 certificates │
│ + Fine-grained firewall rules (one port, one IP, one certificate) │
│ + Full audit (who, when, which variables) │
│ + Scalable: a 3rd unit can be added without touching A or B │
│ − Longer configuration (certificates, rules) │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Option 3 — UNIDIRECTIONAL DATA DIODE │
│ │
│ CPU A ──TX── ▶▶▶ DIODE ▶▶▶ ──RX── CPU B (optical hardware) │
│ │
│ + Ultimate security: feedback physically impossible │
│ + Ideal for nuclear, defense, SIL 4 functional safety │
│ − High cost (€10–50k per diode) │
│ − No handshake — needs a loss-tolerant protocol │
│ − No acknowledgment → hard to debug │
└────────────────────────────────────────────────────────────────────────────────┘
For our case (continuous A → B flow, 12 variables, 200 ms latency, no nuclear), option 2 is the right trade-off: robust, auditable, scalable. That’s what we deploy.
Selected architecture — OPC UA + industrial DMZ
═════════════════════════════════════════════════════════════════════════════════
UNIT A INDUSTRIAL DMZ UNIT B
10.20.10.0/24 (VLAN 10) 10.20.99.0/24 (VLAN 99) 10.20.20.0/24 (VLAN 20)
═════════════════════════════════════════════════════════════════════════════════
CPU S7-1517F-3 PN/DP IPC547G — OPC UA proxy CPU S7-1518F-4 PN/DP
10.20.10.10 10.20.99.10 10.20.20.10
OPC UA Server, SIMATIC OPC UA OPC UA Client
port 4840 Aggregating Server → 10.20.99.10
│ │ │
│ PROFINET │ Ethernet │ PROFINET
│ │ │
SCALANCE XC208 SCALANCE XC208 SCALANCE XC208
10.20.10.20 10.20.99.20 10.20.20.20
│ │ │
│ Ethernet uplink │ │ Ethernet uplink
│ (fiber) │ (internal DMZ) │ (fiber)
│ │ │
┌────┴───────────┐ │ ┌───────┴─────────┐
│ SCALANCE │ │ │ SCALANCE │
│ SC632-2C │ │ │ SC632-2C │
│ FW1 │ ─.99.1 ────────┼─────────────── .99.2─ │ FW2 │
│ eth0 : .10.1 │ │ │ eth0 : .99.2 │
│ eth1 : .99.1 │ │ eth1 : .20.1 │
└────────────────┘ └─────────────────┘
═════════════════════════════════════════════════════════════════════════════════
Allow FW1 : Proxy 10.20.99.10 ──► CPU A 10.20.10.10 tcp/4840 (OPC UA Sign+Encrypt)
Allow FW2 : CPU B 10.20.20.10 ──► Proxy 10.20.99.10 tcp/4840 (OPC UA Sign+Encrypt)
All other traffic → DENY + SIEM log
═════════════════════════════════════════════════════════════════════════════════
Hardware components used:
| Reference | Role | Quantity |
|---|---|---|
6ES7517-3FP00-0AB0 — S7-1517F-3 PN/DP | Unit A CPU (SIL 2) | 1 |
6ES7518-4FP00-0AB0 — S7-1518F-4 PN/DP | Unit B CPU (SIL 3) | 1 |
6GK5208-0BA00-2AC2 — SCALANCE XC208 | Managed industrial switch (1× per zone: A, DMZ, B) | 3 |
6GK5632-2GS00-2AC2 — SCALANCE SC632-2C | Industrial L3 firewall / OPC UA DPI (FW1 between A↔DMZ, FW2 between DMZ↔B) | 2 |
6AG4112-2HH40-1AS2 — IPC547G | Industrial PC for the OPC UA proxy in the DMZ | 1 |
Architectural principles to keep in mind:
- The industrial DMZ (10.20.99.0/24) is a separate subnet, neither A nor B. It is what hosts the OPC UA proxy — not A, not B.
- No direct A ↔ B flow. All communication goes through the proxy, and each firewall allows only one unidirectional flow.
- The proxy aggregates: it reads the 12 variables from A and exposes them to B. If B makes an abnormal request, the proxy refuses — it does not “tunnel” through to A.
- Two separate firewalls in series — FW1 between Unit A and the DMZ, FW2 between the DMZ and Unit B. This is network defense in depth: compromising one firewall is not enough to reach the other unit.
- PROFINET stays inside each unit (PLC ↔ switch). PROFINET RT (layer 2) NEVER crosses an L3 firewall — it is OPC UA encapsulated over TCP/IP that bridges the zones.
Addressing plan and VLANs
| Subnet | VLAN | Use | Allowed hosts |
|---|---|---|---|
10.20.10.0/24 | 10 | OT Unit A (PROFINET, S7) | CPU A, HMI A, distributed sensors A |
10.20.20.0/24 | 20 | OT Unit B (PROFINET, S7) | CPU B, HMI B, distributed sensors B |
10.20.99.0/24 | 99 | Industrial DMZ | OPC UA proxy, jump server, NDR |
10.20.100.0/24 | 100 | Switch/firewall management | Admin console only |
The VLANs are configured on the SCALANCE XC208 switches with a trunk towards the SCALANCE SC632-2C. No inter-VLAN routing at the switch level — the firewall decides.
Step-by-step configuration
Step 1 — Enable the OPC UA Server on CPU A (TIA Portal V19+)
In the properties of the S7-1517F CPU:
- Protection & Security → Connection mechanisms: uncheck
Permit access with PUT/GET communication. (We disable legacy S7 — we will go ONLY through OPC UA.) - OPC UA → Server → General: enable
Activate OPC UA server. Port4840(standard). - OPC UA → Server → Security:
Security policy:Basic256Sha256 — Sign and Encryptonly. Uncheck all theNoneandSign onlypolicies.User authentication: uncheckAllow guest access. CheckAllow authentication via user name and password+ enable the user list.- Create a dedicated user, e.g.
opcua-proxy-readonlywith a strong password stored in a vault (Vault, CyberArk).
- OPC UA → Server → Server interfaces: create an interface that exposes ONLY the 12 required variables (and nothing else). Create a custom object type containing those 12 variables.
The Allow-list principle: publish only what is explicitly necessary. If you publish the entire CPU memory, the proxy gets far too much visibility.
Step 2 — Install the proxy’s X.509 certificate on CPU A
# Generate the CSR on CPU A (TIA Portal)
TIA Portal → CPU A → Certificate Manager →
Generate certificate signing request (CSR) →
Subject: CN=cpu-A-opcua, O=Raffinerie, C=FR
# The CSR is signed by the internal PKI (Microsoft AD CS, HashiCorp Vault,
# or Siemens SIMATIC Logon Service Center).
# Import the signed certificate onto CPU A and the proxy's certificate
# into the CPU's "Trusted certificates" store.
Always mutual certificates. The proxy authenticates the CPU, the CPU authenticates the proxy. Annual renewal automated via the PKI.
Step 3 — Configure the OPC UA proxy (Siemens SIMATIC OPC UA aggregating server)
On the IPC547G in the DMZ, install the SIMATIC OPC UA Aggregating Server (equivalent alternatives: Kepware KEPServerEX, Matrikon UA Tunneller).
# Aggregator configuration (simplified excerpt)
endpoints:
# Endpoint exposed on the B side (interface 10.20.99.10)
- name: server-for-unit-b
url: opc.tcp://10.20.99.10:4840
security: Basic256Sha256
auth: certificate-only
allowed_clients:
- cn=cpu-B-opcua,o=Raffinerie # ONLY B's certificate is accepted
# Client towards CPU A (egress 10.20.99.10 → 10.20.10.10)
upstream:
- name: cpu-a
url: opc.tcp://10.20.10.10:4840
security: Basic256Sha256
user: opcua-proxy-readonly
password_vault: vault://kv/opc-ua/cpu-a
# Mapping: what is read on the A side is exposed on the B side
variables:
- source: "ns=3;s=UnitA.T_Coupe1"
target: "Variables/TCoupe1"
access: read-only
- source: "ns=3;s=UnitA.T_Coupe2"
target: "Variables/TCoupe2"
access: read-only
# ... 10 more
Three key points:
read-onlyon every exposed variable. Even if B is compromised, it cannot write into A.- certificate-only authentication: no password to steal on the B side.
- Logging enabled: every session, every read, is logged to the SIEM (via syslog to Splunk / Wazuh).
Step 4 — Configure the two SCALANCE SC632-2C
The SCALANCE SC is an industrial L3 firewall that speaks OPC UA natively (stateful protocol inspection). We deploy two separate firewalls in series — a single round-trip per flow, a single allow rule per firewall.
# ═══════════════════════════════════════════════════════════════
# FW1 — between Unit A (eth0 = 10.20.10.1) and DMZ (eth1 = 10.20.99.1)
# ═══════════════════════════════════════════════════════════════
# Only allowed flow: DMZ proxy ──► CPU A (OPC UA read)
allow src 10.20.99.10 dst 10.20.10.10 tcp/4840 proto OPC-UA-secure
allow src 10.20.10.10 dst 10.20.99.10 established # responses
deny src any dst any log
# ═══════════════════════════════════════════════════════════════
# FW2 — between DMZ (eth0 = 10.20.99.2) and Unit B (eth1 = 10.20.20.1)
# ═══════════════════════════════════════════════════════════════
# Only allowed flow: CPU B ──► DMZ proxy (OPC UA client session)
allow src 10.20.20.10 dst 10.20.99.10 tcp/4840 proto OPC-UA-secure
allow src 10.20.99.10 dst 10.20.20.10 established # responses
deny src any dst any log
Two effective allow rules (one per firewall), everything else is denied and logged to the SIEM. This is the principle of network least privilege.
Immediate validation tests:
- From any workstation in Unit A,
ping 10.20.20.10(CPU B) → timeout: FW1 blocks any egress not explicitly authorized. ✓ - From any workstation in Unit B,
ping 10.20.10.10(CPU A) → timeout: double block by FW2 then FW1. ✓ - From the DMZ proxy,
ping 10.20.10.10→ OK: the FW1 rule allows it. - From the DMZ proxy,
ping 10.20.20.10→ timeout: the proxy NEVER needs to initiate towards B (it is B that calls the proxy). FW2 blocks.
If any of these 4 tests does not give the expected result, there is a hole in the deny rule — to be fixed BEFORE going to production.
Step 5 — Enable OPC UA Deep Packet Inspection
The SCALANCE SC offers a DPI option that inspects the OPC UA content on top of the TCP header. Benefits:
- Rejects
Writerequests even when the port and IP are authorized - Rejects requests to unauthorized nodes (NodeID allow-list)
- Detects massive browsing attempts (attacker enumeration)
# OPC UA DPI policy applied on the SC632
methods_allowed: [Read, Browse, TranslateBrowsePath]
methods_denied: [Write, Call, AddNodes, DeleteNodes]
namespace_allow: ["ns=3;s=UnitA.*"] # only our allow-list
rate_limit: "100 req / s" # brute-force browsing detection
Step 6 — Enable the NDR / OT-IDS probe
A Dragos, Nozomi or Claroty CTD is connected on a mirror port of the SCALANCE XC208 on the DMZ side. It:
- Builds the automatic inventory (CPU A, CPU B, proxy)
- Learns the OPC UA traffic baseline over 7 days
- Alerts on any deviation: new client, new variable read, new temporal pattern
The OT IDS is what detects a Triton in time. Indispensable under NIS2 (article 21 § 2 b).
Step 7 — Test and validate
| Test | Method | Expected result |
|---|---|---|
| Read a variable from B | OPC UA Client block in CPU B → reads UnitA.T_Coupe1 | Correct value, latency < 200 ms |
| Direct ping CPU B → CPU A | ping 10.20.10.10 from an IPC in VLAN 20 | Timeout (denied by firewall) |
| Write attempt from B | OPC UA Write call to a variable in A | BadUserAccessDenied |
| Massive browsing attempt | Script that browses 10,000 nodes in 1 s | DPI block beyond 100 req/s, SIEM alert |
| Fake client certificate | Connection with a non-imported self-signed cert | BadCertificateUntrusted |
Cyber checks to perform afterwards
- IDS inventory: CPU A, CPU B and the proxy appear correctly. No unexpected device.
- Centralized OPC UA logs: 100% of sessions are seen on the SIEM side, with timestamp and identity.
- Proxy outage test: if the proxy goes down, B keeps running on its last known value (fallback logic in the CPU B program).
- Certificate rotation: a PKI cron renews the certificates 30 days before expiry.
- Backup: the IPC547G proxy image is backed up air-gapped quarterly.
- Written incident plan: if A is compromised, who isolates the proxy? Procedure ≤ 15 min.
- Vendor audit: Siemens maintenance goes through a PAM jump server (Wallix), not direct access to the proxy.
Classic pitfalls
- “Just for this test” — you add an
allow any anyfirewall rule for 5 minutes to debug. You forget it. It stays for 3 years. Always open a ticket and remove the rule after debugging. - Self-signed certificate on the proxy in production. It works, but the warning is ignored by operators who end up clicking on anything. Always a PKI-signed certificate.
- Shared
opcua-proxy-readonlyaccount across all proxies. If one proxy is compromised, the password allows reading the other CPUs. One account per(proxy, CPU)pair. - No fallback logic on the B side: if the proxy goes down, B’s control loop receives
Badvalues and trips to safety — an unplanned shutdown. Always implementif quality != Good then use_last_known_value. - DMZ “for now” on the same VLAN as IT. The industrial DMZ is a DEDICATED zone — neither IT nor OT. If you share it with IT, NotPetya can jump into it.
- Routed PROFINET: NEVER route PROFINET RT between VLANs. L2 multicast does not survive. PROFINET stays internal to each unit, OPC UA does the bridging.
Going further
- The OT Cybersecurity hub covers the Purdue model and the multi-site interconnection section in detail.
- The PLC hub details the Siemens S7-1500F CPUs and their OPC UA capabilities.
- The Industrial networks hub compares PROFINET, OPC UA and the other protocols.
- The IEC 62443 page gives the full normative framework for this architecture.
One last thing: what we just did looks like over-engineering the first time you see it. Four firewall interfaces, a proxy, certificates, DPI… Compare it to the cost of a Triton on your hydrocracker. The conversation is quick.