Security model
taktwerk has four surfaces a network can reach: the OPC UA server, the http connector,
taktwerk serve and SSH to devices. Client connectors reach out to a PLC or a plant data
system with credentials from the environment or a file. The project file holds no secret:
inline passwords are refused; a rest login body takes them as ${NAME} from the
environment.
| surface | listens on | who may read | who may write | transport security |
|---|---|---|---|---|
| OPC UA server | 0.0.0.0:4840 by default | every accepted identity | identities with write access; outputs never | OPC UA sign, sign-encrypt |
http connector | 127.0.0.1:8480 by default | anyone who reaches it | bearer token, with writes = true | none: loopback or a trusted network |
taktwerk serve | loopback only | the local user’s browser | bearer token | none: loopback only |
| devices | the device’s SSH server | the login user | the login user; root for units | SSH |
OPC UA server
Open by default: anonymous, writable, no encryption, all interfaces, so any client on a closed machine network is a UI. On any other network:
- bind
endpointto one interface; anonymous = false, oranonymous_write = falsefor read-only anonymous sessions;userswith passwords frompassword_envorpassword_file,write = falsefor viewers;security = ["sign-encrypt"]and thepoliciesto offer; client certificates are trusted only from<pki_dir>/trusted/unlesstrust_client_certs.
A user’s password on a "none" endpoint is encrypted with the server certificate, the values
are not. Inputs and tunables are writable to identities with write access, through
UserAccessLevel; outputs and system signals are read-only for everyone. Every write passes
the image’s type, length and range checks; a refused tunable write is logged in events.csv.
Keys in OPC UA server.
http connector
- Reading needs no credentials. Writes and stop need
writes = trueandAuthorization: Bearer <token>:TAKTWERK_HTTP_TOKEN, else a fresh token written totoken_file(default$XDG_RUNTIME_DIR/taktwerk/http-<port>.token, mode 0600) and removed at shutdown. - No TLS. It listens on loopback by default; keep it there or on a network you trust.
- On loopback a request is refused (403) unless its
Hostis a loopback address orlocalhostand no browser marks it cross-site (Sec-Fetch-Site), so a web page the operator opens cannot reach the engine through DNS rebinding.
On a device the bundle’s unit serves it on a loopback port with the token in the bundle’s env
file; a host reaches it through an SSH port forward and reads the token over SSH.
taktwerk serve
- Listens on loopback only; another address is refused.
- A token (
TAKTWERK_SERVE_TOKEN, else generated and printed in the URL’s fragment) is needed to check a project (it loads model libraries), start and stop runs, write values, shut down, and for every request that changes a device. Requests that change a device are refused whenCIis set. - The same
HostandSec-Fetch-Sitechecks as thehttpconnector. - Runs start without the project’s connectors unless asked, so the workbench talks to no plant by accident.
- Passwords for SSH logins,
sudoorsustay in memory untilserveexits or the device’s session is forgotten; nothing is written to disk.
Devices
- Every control call is an SSH exec, every file an SFTP transfer, the live view an SSH port forward: SSH authenticates and encrypts.
- Host keys are pinned at the first verify and checked on every connection; a changed key is
refused until it is pinned again (
device verify --accept-host-key). - Logins use an SSH agent, key files, or a password asked for and never stored.
devices.tomlholds no secret. - The login user owns the install root; only unit changes and creating a missing root run as
root, through the device’s
privilege(none,sudo,su). - Secrets for the engine (
password_env,token_env,TAKTWERK_HTTP_TOKEN) live in the bundle’senvfile on the device, mode 600, which a deploy never rewrites. - Every mutating command is a dry run without
--yes, and--yesis refused whenCIis set.
See Deploying to devices.
Client connectors
opcua-client:security = "sign"or"sign-encrypt"(Basic256Sha256),credentialsfrompassword_envorpassword_file, server certificates trusted only from<pki_dir>/trusted/unlesstrust_server_certs.rest: plain HTTP only;https://is refused. For a server that requires TLS, put a TLS proxy beside the engine. Credentials from the environment (Auth).- Both write to a plant only when the project says
writes = "on", or for one runrun --write <id>;"dry-run"logs what they would write (Dry run).
What taktwerk does not do
- Models run in the engine’s process, with its privileges; a package is trusted code.
- The engine is not a safety controller: the PLC owns the safe state (the safety contract).
- No TLS on the
httpconnector,serveorrest.