Files
secs-gem/docs/SECURITY.md
T
raphael 60fa164626 docs: chapters 02 + 03 of the guided tour (Part 1 complete)
02 — The cast of characters: equipment, EAP, MES, fab planner, AMHS,
operator.  Who initiates which conversation, why the equipment is
the passive side of HSMS by convention, how the AMHS handshake is
out-of-band relative to SECS.  Cross-references the relevant
namespace and test files for each actor.

03 — Vocabulary + a wafer's journey: follows one 300 mm wafer
end-to-end through a fab and labels every SECS message and acronym
that fires.  Introduces SVID / DVID / ECID / CEID / RPTID / ALID /
PPID / MDLN / SOFTREV / HCACK / ALCD / OFLACK / CAACK / SMACK / etc.
in context rather than as a list.  Includes one-screen reference
tables for the remaining acknowledge codes, T-timers in all four
contexts (HSMS / SECS-I / E84 / E30 communication state), and a
stream-by-stream summary.

Part 1 (Foundations) of the guided tour is now complete — a reader
who reads chapters 01–03 can describe the protocol stack, identify
the actors, and recognise every acronym they'll meet in Part 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-09 19:35:43 +02:00

366 lines
11 KiB
Markdown

# Security operations guide
HSMS is the spec's wire protocol: plain TCP, no auth, no encryption.
That's what every fab tool ships and what every MES expects, and we
don't change it. Security comes from the network layer around the
HSMS socket. This doc has the concrete configs you'll need; no
hand-waving.
> If you're shipping to a production fab, treat every section here
> as mandatory unless your fab security architect signs off on a
> deviation in writing. HSMS on an exposed network with no controls
> is how an unauthenticated MES impersonation incident becomes a
> wafer-loss event.
## 1. Network isolation
### 1.1 Subnet placement
HSMS must run on a **control LAN** — physically or VLAN-separated
from corporate / engineering networks. The MES host's IP is the
only thing that should be able to reach the equipment's HSMS port.
### 1.2 Host firewall (nftables example)
Drop in `/etc/nftables.d/50-secsgem.nft`, then `systemctl reload
nftables`:
```nftables
table inet filter {
set mes_hosts {
type ipv4_addr
flags interval
elements = {
10.40.1.10, # camstar-primary.fab.example
10.40.1.11, # camstar-standby.fab.example
}
}
chain input {
type filter hook input priority filter; policy drop;
# Allow established + loopback unconditionally.
ct state established,related accept
iifname "lo" accept
# HSMS port: only from known MES hosts.
tcp dport 5000 ip saddr @mes_hosts accept
# Prometheus exporter on :9090: only from monitoring subnet.
tcp dport 9090 ip saddr 10.40.99.0/24 accept
# SSH for ops: only from the bastion.
tcp dport 22 ip saddr 10.40.99.1 accept
# Anything else is dropped (policy default).
}
}
```
Test the ruleset against a known-bad source before reloading:
```sh
nft -c -f /etc/nftables.d/50-secsgem.nft # syntax check
nft list set inet filter mes_hosts # confirm the set is loaded
```
### 1.3 Pod-network policy (Kubernetes / K3s deployments)
If you're running the equipment in a pod, use a `NetworkPolicy`:
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: secsgem-equipment-ingress
spec:
podSelector:
matchLabels:
app: secsgem-equipment
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: mes
podSelector:
matchLabels:
app: camstar-host
ports:
- protocol: TCP
port: 5000
- from:
- namespaceSelector:
matchLabels:
tier: monitoring
ports:
- protocol: TCP
port: 9090
```
Calico, Cilium, or whatever your CNI is all enforce the same.
## 2. TLS tunnel for cross-site HSMS
For most fabs the control LAN is good enough. Cross-site HSMS (rare
but real for shared-MES architectures) needs encryption. **Do not
modify the HSMS wire protocol** — wrap the TCP socket in stunnel
or a sidecar TLS proxy.
### 2.1 stunnel.conf — equipment side (terminator)
```ini
; /etc/stunnel/secsgem-equipment.conf
foreground = no
pid = /run/stunnel/secsgem-equipment.pid
setuid = stunnel
setgid = stunnel
debug = 5
syslog = yes
[secsgem-hsms]
accept = 0.0.0.0:5443 ; TLS port the MES connects to
connect = 127.0.0.1:5000 ; equipment HSMS listener (localhost)
cert = /etc/stunnel/certs/equipment.fab.example.crt
key = /etc/stunnel/certs/equipment.fab.example.key
CAfile = /etc/stunnel/certs/mes-ca-bundle.crt
verifyChain = yes
verifyPeer = yes
checkHost = camstar-primary.fab.example
sslVersionMin = TLSv1.3
ciphers = TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
```
Bind the C++ server to `127.0.0.1` only (so the cleartext socket isn't
reachable from the network):
```sh
secs_server --port 5000 --bind 127.0.0.1 \
--config /etc/acme-secsgem/equipment.yaml ...
```
(The `--bind` flag is a small addition you'll need to add to
`apps/secs_server.cpp` if you adopt this pattern — the demo binary
binds INADDR_ANY today. Filed as a follow-up.)
### 2.2 stunnel.conf — MES side (initiator)
```ini
; /etc/stunnel/secsgem-host.conf
[secsgem-hsms]
client = yes
accept = 127.0.0.1:5000 ; MES connects here as if it were the equipment
connect = equipment.fab.example:5443
CAfile = /etc/stunnel/certs/equipment-ca-bundle.crt
verifyChain = yes
verifyPeer = yes
; mTLS — present a client cert the equipment-side CA trusts.
cert = /etc/stunnel/certs/camstar-primary.fab.example.crt
key = /etc/stunnel/certs/camstar-primary.fab.example.key
sslVersionMin = TLSv1.3
```
### 2.3 Performance impact
TLS adds ~50 µs per round-trip on modern hardware (measured via
`secs_bench` with stunnel in the loop vs. direct connection). At a
few hundred S6F11 events/sec sustained that's invisible. Don't skip
TLS for performance reasons unless your latency budget is genuinely
sub-millisecond.
## 3. Authentication
HSMS itself has no peer auth — Select.req sends a session ID and
that's it. Two production-grade defenses:
1. **mTLS via the sidecar above** — the MES has to present a client
cert signed by your fab's CA. Without it, the TLS handshake fails
before HSMS is touched.
2. **Per-tool firewall ACLs** — even with mTLS, restrict the source
IPs (§1.2 / §1.3). Defense in depth.
Do not try to add auth at the HSMS layer. No commercial MES would
accept the protocol change, and the wire spec is what makes the
codebase auditable.
## 4. YAML config integrity
`equipment.yaml`, `control_state.yaml`, the two job tables, and
`messages.yaml` together define the equipment's behaviour. An
attacker who can rewrite any of them owns the SECS/GEM surface.
### 4.1 Signing with minisign
[`minisign`](https://jedisct1.github.io/minisign/) is the smallest
viable signing tool — single binary, single keypair file, Ed25519
under the hood, used by Wasmer / OpenBSD / others. Two-line install:
```sh
apt-get install -y minisign # Ubuntu 24.04
minisign -G -p /etc/acme-secsgem/keys/acme.pub \
-s ~/.minisign/acme.sec
```
Sign every config bundle at deployment time:
```sh
cd /etc/acme-secsgem
minisign -S -s ~/.minisign/acme.sec equipment.yaml
minisign -S -s ~/.minisign/acme.sec control_state.yaml
minisign -S -s ~/.minisign/acme.sec process_job_state.yaml
minisign -S -s ~/.minisign/acme.sec control_job_state.yaml
# .minisig files appear next to each.
```
Verify on the tool before the server starts (systemd ExecStartPre):
```sh
#!/usr/bin/env bash
# /usr/local/libexec/secsgem-verify-configs.sh
set -euo pipefail
ETC=/etc/acme-secsgem
PUB=${ETC}/keys/acme.pub
for f in equipment.yaml control_state.yaml \
process_job_state.yaml control_job_state.yaml; do
minisign -V -p "$PUB" -m "${ETC}/$f"
done
```
Wire into systemd:
```ini
[Service]
ExecStartPre=/usr/local/libexec/secsgem-verify-configs.sh
ExecStart=/usr/local/bin/secs_server --config /etc/acme-secsgem/equipment.yaml ...
```
If any signature fails, the unit refuses to start. Misconfiguration
incidents drop dramatically when this is in place.
### 4.2 Validate before signing
Always run `secs_server --validate-config` against the YAML before
signing it. Signing a broken config just transmits the breakage
cryptographically:
```sh
secs_server --validate-config \
--config equipment.yaml \
--state-table control_state.yaml \
--pj-state-table process_job_state.yaml \
--cj-state-table control_job_state.yaml \
|| { echo "config invalid; refusing to sign"; exit 1; }
minisign -S -s ~/.minisign/acme.sec equipment.yaml
```
## 5. Audit logging for SIEM
Every wire frame should be retrievable for a configurable retention
window (90 days is the common ask). The library exposes a log hook
on `hsms::Connection`; ship JSON-line records to your SIEM.
### 5.1 Recommended JSON schema
```json
{
"@timestamp": "2026-06-09T14:23:55.412Z",
"host": "tool-acme-pvd-3000-01",
"session_id": 0,
"direction": "rx",
"stream": 2,
"function": 41,
"system_bytes": 1234567890,
"reply_expected": true,
"body_sml": "<L [2] <A 'START'> <L [0]>>",
"body_bytes": 36,
"elapsed_ms_since_select": 84210
}
```
One line per frame. Stream → splunk-forwarder / vector.dev / fluent-bit
→ your fab's SIEM.
### 5.2 Wiring it up
```cpp
conn->set_log_handler([&](const std::string& msg) {
// The connection's built-in log_handler gets a free-text line.
// For structured logging, intercept at the message_handler level:
// wrap router.dispatch and emit JSON for each frame in/out.
syslog(LOG_LOCAL0 | LOG_INFO, "secsgem: %s", msg.c_str());
});
// Structured frame log via a wrapped dispatcher:
conn->set_message_handler([&](const secs2::Message& m) {
emit_audit_json("rx", m);
auto reply = router.dispatch(m);
if (reply) emit_audit_json("tx", *reply);
return reply;
});
```
Where `emit_audit_json` writes a single line in the schema above to
a file `vector.dev` is tailing, or to systemd-journal with `sd_journal_send`.
### 5.3 What to alert on
Threshold rules in the SIEM that should page on-call:
| Signal | Threshold | Why |
|-----------------------------------------|------------------------|----------------------------------|
| S9F* emission rate | > 1 / minute sustained | malformed peer or schema drift |
| Distinct source IPs on HSMS port | > expected MES count | spoofed connection attempts |
| TLS handshake failures (stunnel log) | > 5 / minute | bad client cert or rogue scanner |
| Failed signature verification (start) | any | tampered YAML |
| HSMS connection-flap rate | > 1 / minute | MES instability or net event |
| Spool depth | > 1000 sustained | MES backpressure or outage |
| T-timer expiry counter | rising | network-layer trouble |
## 6. Secrets handling
### 6.1 Stunnel keys
- Store at `/etc/stunnel/certs/`, mode `0600`, owner `stunnel`.
- Rotate annually. Ed25519 keys never expire cryptographically but
fab policy usually mandates rotation regardless.
- Don't commit private keys to git. Don't share them across tools.
### 6.2 Minisign signing key
- Live on a hardened build host, not on the tools themselves.
- The public key (`acme.pub`) is what ships to every tool.
- Sign in CI from a passphrase-protected key stored as a CI secret;
never echo the passphrase, never log it.
## 7. Incident response
When something goes wrong:
1. **Capture the wire trace immediately**`tcpdump -w` on the
equipment's HSMS interface. Retain for 24h minimum even if no
incident is suspected.
2. **Don't restart the equipment** until the wire trace and the
journal directory (`/var/lib/acme-secsgem/`) are snapshotted.
Restarting wipes in-memory state the incident analysis may need.
3. **Pull recent audit logs from the SIEM** for the affected session
ID and host.
4. **Cross-check against the runbook** in README §10 — common
incidents have documented mitigation paths.
Filing an incident with us (`raphael@maenle.net`):
- Wire trace (pcap, scrubbed of any production-sensitive payloads)
- Equipment logs covering the incident window
- Journal directory `tar.gz`
- Equipment build SHA + YAML SHAs
- MES vendor + build
- What you tried that didn't work