Auto: s7-e2 — PLC password / protection-level handling

Closes #303
This commit is contained in:
Joseph Doherty
2026-04-26 10:51:07 -04:00
parent e0f3d1c925
commit 30c3b10c94
9 changed files with 887 additions and 0 deletions

View File

@@ -1156,6 +1156,123 @@ diagnostics tests where you want every read to hit the wire.
}
```
## PLC password / protection levels
PR-S7-E2 (issue #303) adds a connection-level password option for hardened
deployments. The driver emits the password to the PLC immediately after
`OpenAsync` succeeds and before the pre-flight PUT/GET probe runs (the same
pre-flight read that would otherwise be the first operation a hardened CPU
refuses).
### Options
| Option | Default | Purpose |
| ----------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Password` | `null` | Connection-level password. Secret — never logged. `null` or empty = no password is sent. |
| `ProtectionLevel` | `Auto` | Declarative hint about the PLC's protection scheme. One of `Auto`, `None`, `Level1`, `Level2`, `Level3` (S7-300/400 SFC 109/110 levels), or `ConnectionMechanism` (S7-1200/1500 TIA Portal "Protection & Security" pane). |
### S7-300 / S7-400 protection levels (1, 2, 3)
S7-300/400 firmware exposes three CPU-side protection levels:
* **Level 1** — write protection. Reads work without a password; writes
(parameter, DB, M/Q changes) require an unlock.
* **Level 2** — read and write protection. Both kinds of operation require
the password.
* **Level 3** — full protection. Even online presence detection / status
list reads require the password.
Set `ProtectionLevel = Level1` / `Level2` / `Level3` and supply
`Password` to match the level configured in the CPU's HW Config dialog.
The level value is descriptive — the driver doesn't switch behaviour
between Level1/2/3, since the wire-side `SendPassword` is the same call
in all three cases. The hint surfaces in the driver-diagnostics RPC so a
"PLC said Level 3 but config says Level 1" mismatch is spottable from the
Admin UI.
### S7-1200 / S7-1500 connection mechanism
S7-1200/1500 firmware uses a different gate: TIA Portal's "Protection &
Security" pane has a single **Connection Mechanism** dropdown that, when
set to anything stricter than "No access", requires every PG/HMI/SCADA
connection to authenticate after the COTP handshake. The wire-level
exchange is the same `SendPassword` call but the diagnostic flag is
distinct, so set `ProtectionLevel = ConnectionMechanism` for these
families.
### No-log invariant
`Password` is a secret. The driver MUST NOT include the password value in
log lines, exception messages, or diagnostic surfaces. Specifically:
* `S7DriverOptions.ToString()` redacts the field as `***`.
* `S7Driver`'s success log line is `S7 password sent for {Host}`
identifier-only, no value.
* The "S7netplus does not expose SendPassword" warning logs the host name
and driver instance ID only, never the password.
* Authentication-failure exceptions wrap the inner `S7.Net.PlcException`
but their own message says only "S7 password authentication failed for
host '{Host}'" — no password value.
Any new logging surface that flows an `S7DriverOptions` value MUST
continue to redact. See the FOCAS-F4-d
`docs/v2/focas-deployment.md` § "FOCAS password handling" entry for the
sister no-log discipline on the FOCAS driver.
### Library limitation — S7netplus 0.20
**S7netplus 0.20.0 (the pinned dependency) does not expose a public
`SendPassword` method.** The driver discovers the method reflectively
(checking for `SendPasswordAsync(string, CancellationToken)` first, then
`SendPassword(string)`) so a future minor release that ships the API
will be picked up automatically without a code change here.
Until the upstream lands, configuring `Password` on a hardened CPU
produces this Init-time warning:
```
[Warning] S7 password is set on driver '<DriverInstanceId>' against
host '<Host>', but the linked S7netplus library does not expose
SendPassword; password is being ignored at the wire. Hardened-CPU
connect may fail at first read.
```
Init still completes — the COTP/S7comm handshake itself doesn't require
the password — but the first read against a hardened CPU will surface
`BadDeviceFailure` because PUT/GET-disabled and "level-3 protection"
return identical "function not allowed" PDUs at the wire layer.
If your S7-1200/1500 deployment requires `ConnectionMechanism`, the
near-term workarounds are:
1. **Lower the protection setting** in TIA Portal's Protection & Security
pane to "Full access (no protection)" for the duration of the
evaluation.
2. **Configure a separate non-hardened connection** on a CP module that
the driver can target while keeping the production endpoint hardened.
3. **Track upstream S7netplus** for a `SendPassword` PR (the package owner
has discussed adding it; see issue
<https://github.com/S7NetPlus/s7netplus/issues>).
For S7-300/400 CPUs, levels 1 and 2 leave at least *read* access open
without a password, so most monitoring use cases work without
`SendPassword` until the library catches up — only Level 3 and the
S7-1200/1500 ConnectionMechanism require the wire-level unlock.
### JSON config example
```json
{
"DriverConfig": {
"Host": "192.168.10.50",
"Port": 102,
"CpuType": "S71500",
"Password": "tia-portal-set-password",
"ProtectionLevel": "ConnectionMechanism"
}
}
```
## References
1. Siemens Industry Online Support, *Modbus/TCP Communication between SIMATIC S7-1500 / S7-1200 and Modbus/TCP Controllers with Instructions `MB_CLIENT` and `MB_SERVER`*, Entry ID 102020340, V6 (Feb 2021). https://cache.industry.siemens.com/dl/files/340/102020340/att_118119/v6/net_modbus_tcp_s7-1500_s7-1200_en.pdf