Quick Setup#
The quick setup gives you a secure server and client with a few lines, and an error message that names the setting
that is wrong. It adds no protocol behaviour: it collects the settings that are otherwise many
Server setters and Client calls into
configuration objects, and it names three ready-made combinations, the profiles. The setters stay available as the
low-level API.
A secure pair#
Server.quickstart() and
Client.quickstart() return a configured object that is not yet
started. The PKI directory is created on await server.init() (the client creates its own at the first
connect()), each with a self-signed application certificate whose ApplicationUri matches the configuration. To
have the certificate earlier, for example to hand it to the other side before anything connects, call
await client.init_pki() or await server.init_pki(): it creates the directory and the certificate, returns the
certificate and can be called again.
Nothing is trusted automatically. Before a secure connection can work, each side has to trust the other’s
certificate; this is the one step that cannot be skipped. The example does it in code; on a command line you use
enginir-opcua pair (below).
async def main() -> None:
with workdir() as directory:
url = f"opc.tcp://127.0.0.1:{free_port()}/enginir-opcua/example/"
server = Server.quickstart(url, pki=directory / "server-pki")
client = Client.quickstart(url, pki=directory / "client-pki")
await server.init()
await client.init_pki()
trust(directory / "server-pki", directory / "client-pki")
trust(directory / "client-pki", directory / "server-pki")
idx = await server.register_namespace("urn:enginir:example:secure-pair")
temperature = await server.nodes.objects.add_variable(idx, "Temperature", 21.5)
async with server, client:
endpoint = client.security_policy.URI.rsplit("#", 1)[-1]
print("secure channel with", endpoint)
print("temperature:", await client.get_node(temperature.nodeid).read_value())
print("OK 01_secure_pair")
The runnable file, with its imports, is 01_secure_pair.py. The two minimal pages that follow, A Minimal OPC-UA Server and A Minimal OPC-UA Client, split it into two programs.
Profiles#
|
|
|
|
|---|---|---|---|
Server security |
Basic256Sha256, Aes128_Sha256_RsaOaep and Aes256_Sha256_RsaPss, all with SignAndEncrypt only |
No security, and Basic256Sha256 with SignAndEncrypt |
The secure policies in Sign and SignAndEncrypt, Basic256 and Basic128Rsa15, and no security |
Client security |
Basic256Sha256, SignAndEncrypt |
No security |
|
Network |
Any address |
Loopback only ( |
Any address |
Certificate checks |
Nothing relaxed |
Revocation and host name errors are accepted |
The same |
Passwords in clear text |
Refused |
Allowed |
Refused |
Start |
Quiet |
Logs a warning for every weaker setting |
The same |
Trust |
Nothing trusted until you say so |
Nothing trusted for the secured endpoint; the unsecured one needs no certificate |
Nothing trusted for the secured endpoints |
Users |
A users file (below); anonymous sessions are read-only |
The same |
The same |
Use development only on your own machine, to try things out, and legacy only for devices that cannot do
better. Both are your decision: nothing is blocked, and every weaker setting is logged when the server starts or the
client connects. Security Profiles has the complete tables, the settings and the log lines. The examples are
15_development_profile.py
and 17_legacy_profile.py.
Settings and configuration objects#
Everything else is a keyword argument of quickstart: Server.quickstart(endpoint, profile="secure",
pki="./pki", users=None, **overrides) and Client.quickstart(url, profile="secure", pki="./pki", username=None,
password=None, **overrides). An override is any field of ServerConfig or
ClientConfig, for example name, application_uri, security,
limits=ServerLimits(max_sessions=10) or auto_reconnect=True. An unknown name is an error.
A configuration is validated as a whole, and every error names the setting as it is written in the TOML file:
ConfigurationError: server.endpoint: the development profile only listens on a loopback address, got
'opc.tcp://0.0.0.0:4840/dev/'; use 127.0.0.1 or localhost, or profile "secure"
The same objects can be built directly and passed to the constructors, Server(config=...) and
Client(config=...). A config replaces the other constructor arguments; mixing them is an error.
Configuration files#
ServerConfig.from_toml(path) and ClientConfig.from_toml(path) read a TOML file with the standard library’s
tomllib. Unknown keys are errors, relative paths in the file are relative to the file, and secrets never live in
it: a password comes from the environment variable named by password_env (default ENGINIR_OPCUA_PASSWORD),
and a users file is found through users_file or ENGINIR_OPCUA_USERS_FILE. config.dump(path) writes the file
with a comment for every setting; enginir-opcua init does it for you (--profile secure|development|legacy), and for a server it also creates an
empty users.toml that users_file names:
[server]
profile = "secure"
endpoint = "opc.tcp://127.0.0.1:4840/enginir-opcua/server/"
application_uri = "urn:enginir:opcua:server"
pki = "pki"
users_file = "users.toml"
[server.limits]
max_sessions = 100
from enginir_opcua import Server
from enginir_opcua.config import ServerConfig
server = Server(config=ServerConfig.from_toml("server.toml"))
await server.init()
enginir-opcua server --config server.toml serves the same file without code. push_management = true makes
init() switch on certificate management over OPC UA (OPC 10000-12), so a SecurityAdmin can replace the certificate
and the trust lists remotely.
Redundant servers and the synchronous API#
HaClient.quickstart(urls, profile="secure", pki="./pki", username=None, password=None, **overrides) gives a client
for a redundant set the same quick setup: all its clients share the PKI directory and the host-derived ApplicationUri,
and await ha.init_pki() creates the certificate before start(). See 09_ha_client.py.
enginir_opcua.sync.Server.quickstart and enginir_opcua.sync.Client.quickstart have the same arguments and one
more, tloop, to run both on one ThreadLoop; the sync client has init_pki() too. See
11_sync.py.
Users and roles#
A server that should accept more than anonymous read access takes a users file: a TOML file with a name, a role and a
salted scrypt hash for each user. Plain-text passwords are never stored.
$ enginir-opcua user add users.toml alice --role user
added user 'alice' (role user) to users.toml
$ enginir-opcua user list users.toml
alice user
The roles are user, admin and security_admin. Anonymous sessions have the read-only role and can read,
browse, subscribe and read history. A user may also write and call methods, an admin may also
change the address space, and a security_admin may also manage certificates and read the security diagnostics. The
file is read again when it changes. In code, write_users, hash_password and UserRecord in
enginir_opcua.crypto.users_file write the same file, and FileUserManager(path, anonymous=True) is the user
manager behind Server.quickstart(users=path). See 02_users_and_roles.py.
The trust commands#
The program enginir-opcua has five commands for setting up the trust between two applications. They work on PKI
directories (see Certificates: the PKI directory).
enginir-opcua init server|client DIRCreate
DIR/pkiwith a self-signed application certificate, and a commentedDIR/server.tomlorDIR/client.toml; for a server also an emptyDIR/users.toml, which the configuration names. Options:--endpoint(--urlfor a client),--profile,--name,--application-uri,--force. An existing certificate or users file is never replaced. A server on every interface (0.0.0.0) gets a certificate that names the host’s IP addresses, so clients can connect by address.enginir-opcua pki list [--pki DIR]Show the folders of a PKI directory with the subject, thumbprint and expiry of every certificate.
enginir-opcua trust THUMBPRINT_OR_FILE [--pki DIR]Trust a certificate: move a rejected one, by thumbprint, from
rejected/certstotrusted/certs, or add a certificate file. A server or client that refuses a peer tells you the command to run, with the thumbprint.enginir-opcua pair SERVER_PKI CLIENT_PKICopy each certificate into the other directory’s
trusted/certs. It prints a warning: nobody checks who holds the key, so it is for development on one machine. On a network, compare the thumbprints yourself and usetrust.enginir-opcua user add|list|remove FILEManage a users file, see above.
addasks for the password;--password-stdinreads one line from standard input.
A complete session on one machine:
$ enginir-opcua init server srv --endpoint opc.tcp://127.0.0.1:4840/enginir-opcua/server/
created srv/pki
created srv/pki/own/certs/application.der thumbprint 584532607f12... expires 2027-10-29
created srv/server.toml
created srv/users.toml
next enginir-opcua user add srv/users.toml <name> --role user
$ enginir-opcua init client cli --url opc.tcp://127.0.0.1:4840/enginir-opcua/server/
$ enginir-opcua pair srv/pki cli/pki
trusted 301d083dbf7e... CN=urn:host:enginir-opcua:client in srv/pki/trusted/certs
trusted 584532607f12... CN=urn:host:enginir-opcua:server in cli/pki/trusted/certs
$ enginir-opcua user add srv/users.toml alice --role user
If a peer’s certificate has not been trusted, both sides say which, and how. The server logs:
192.168.0.20:51234 refused: client certificate 8b66...a1e (CN=urn:host:enginir-opcua:client) rejected: it is not
trusted (BadCertificateUntrusted); trust it with: enginir-opcua trust 8b66...a1e --pki srv/pki
and the client’s error names both commands when neither side trusts the other yet:
the server refused the secure channel, probably because it does not trust this client's certificate 8b66...a1e;
trust it on the server: enginir-opcua trust 8b66...a1e --pki <server pki>. This client does not trust the server
either: server certificate 5845...12f (CN=urn:host:enginir-opcua:server) is not trusted; trust it with:
enginir-opcua trust 5845...12f --pki cli/pki
A SecurityAdmin can do the same over OPC UA with the push model of OPC 10000-12; see 14_certificate_management.py.
Examples#
The examples directory of the repository has one runnable file per task, from this secure pair to alarms,
history, file transfer and reverse connect. Examples describes each of them.