Skip to content

Commit c151c56

Browse files
authored
SY-3801: Fix Anonymous Auth on Encrypted OPC UA Connections (#2031)
SY-3801: Fix Anonymous Auth on Encrypted OPC UA Connections
1 parent b291881 commit c151c56

34 files changed

Lines changed: 626 additions & 105 deletions

File tree

.github/PULL_REQUEST_TEMPLATE/rc.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,6 @@ I can successfully:
402402

403403
- [ ] Enable and disable OPC UA integration when starting the server.
404404
- [ ] Connect to and read data from a physical device.
405-
- [ ] Connect to an encrypted OPC UA server (Write and Read).
406405

407406
### Modbus
408407

alamos/py/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "alamos"
3-
version = "0.52.2"
3+
version = "0.52.3"
44
authors = [{ name = "Emiliano Bonilla", email = "ebonilla@synnaxlabs.com" }]
55
requires-python = ">=3.10,<4"
66
dependencies = [

alamos/ts/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@synnaxlabs/alamos",
3-
"version": "0.52.2",
3+
"version": "0.52.3",
44
"type": "module",
55
"description": "Distributed instrumentation for Synnax",
66
"repository": "https://github.com/synnaxlabs/synnax/tree/main/freighter/ts",

client/py/examples/opcua/README.md

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,23 @@ Follow these scripts in order:
2222
If you don't have a real OPC UA server, start the included test server:
2323

2424
```bash
25-
uv run python examples/opcua/server.py
25+
uv run python -m examples.opcua.server
26+
```
27+
28+
To start a TLS-encrypted server (Basic256Sha256, port 4842):
29+
30+
```bash
31+
uv run python -m examples.opcua.server --tls
32+
```
33+
34+
To start a TLS-encrypted server with username/password authentication (port 4843):
35+
36+
```python
37+
from examples.opcua import OPCUATLSAuthSim
38+
import synnax as sy
39+
40+
sim = OPCUATLSAuthSim(rate=50 * sy.Rate.HZ)
41+
sim.start()
2642
```
2743

2844
This server simulates:
@@ -33,8 +49,8 @@ This server simulates:
3349
- **Boolean variables** (my_bool_0, my_bool_1): Square wave patterns
3450
- **Command variables** (command_0, command_1, command_2): Writable float values
3551

36-
The server runs on `opc.tcp://127.0.0.1:4841/` by default and prints node IDs on
37-
startup.
52+
The server runs on `opc.tcp://127.0.0.1:4841/` by default (port 4842 with `--tls`, port
53+
4843 for TLS with username/password) and prints node IDs on startup.
3854

3955
### 2. Connect Your OPC UA Server
4056

@@ -44,24 +60,40 @@ Register your OPC UA server with Synnax:
4460
uv run python examples/opcua/connect_server.py
4561
```
4662

63+
To connect a TLS-encrypted server (Basic256Sha256, port 4842):
64+
65+
```bash
66+
uv run python examples/opcua/connect_server.py --tls
67+
```
68+
69+
To connect a TLS-encrypted server with username/password auth (port 4843):
70+
71+
```bash
72+
uv run python examples/opcua/connect_server.py --tls-auth
73+
```
74+
4775
This script will:
4876

4977
- Check if the server is already registered
5078
- Register the server with the embedded Synnax rack
51-
- Set up the server configuration
79+
- Set up the server configuration (including security settings and credentials)
5280

5381
**Configuration**: Edit the constants at the top of `connect_server.py` to match your
5482
server:
5583

56-
- `DEVICE_NAME`: A friendly name for your OPC UA server
57-
- `ENDPOINT`: OPC UA endpoint URL (e.g., `opc.tcp://127.0.0.1:4841/`)
84+
- `PLAIN_DEVICE_NAME` / `TLS_DEVICE_NAME` / `TLS_AUTH_DEVICE_NAME`: Friendly names for
85+
your OPC UA servers
86+
- `PLAIN_ENDPOINT` / `TLS_ENDPOINT` / `TLS_AUTH_ENDPOINT`: OPC UA endpoint URLs
87+
- `TLS_AUTH_USERNAME` / `TLS_AUTH_PASSWORD`: Credentials for username/password auth
5888

5989
### 3. Read Float Data from OPC UA Nodes
6090

6191
Read scalar float values from the server:
6292

6393
```bash
6494
uv run python examples/opcua/read_task.py
95+
uv run python examples/opcua/read_task.py --tls
96+
uv run python examples/opcua/read_task.py --tls-auth
6597
```
6698

6799
This example:
@@ -74,14 +106,16 @@ This example:
74106
**What you'll see**: Real-time sine wave values from my_float_0 and my_float_1.
75107

76108
**Node IDs**: The example uses node IDs like `NS=2;I=8` to identify OPC UA variables.
77-
These IDs are printed by `server_extended.py` on startup.
109+
These IDs are printed by `server.py` on startup.
78110

79111
### 4. Read Array Data from OPC UA Nodes
80112

81113
Read array data in high-performance array mode:
82114

83115
```bash
84116
uv run python examples/opcua/read_task_array.py
117+
uv run python examples/opcua/read_task_array.py --tls
118+
uv run python examples/opcua/read_task_array.py --tls-auth
85119
```
86120

87121
This example:
@@ -103,6 +137,8 @@ Read boolean (digital) values from the server:
103137

104138
```bash
105139
uv run python examples/opcua/read_task_boolean.py
140+
uv run python examples/opcua/read_task_boolean.py --tls
141+
uv run python examples/opcua/read_task_boolean.py --tls-auth
106142
```
107143

108144
This example:
@@ -122,6 +158,8 @@ Send commands to writable OPC UA nodes:
122158

123159
```bash
124160
uv run python examples/opcua/write_task.py
161+
uv run python examples/opcua/write_task.py --tls
162+
uv run python examples/opcua/write_task.py --tls-auth
125163
```
126164

127165
This example:
@@ -142,6 +180,8 @@ When finished, remove the server registration:
142180

143181
```bash
144182
uv run python examples/opcua/delete_server.py
183+
uv run python examples/opcua/delete_server.py --tls
184+
uv run python examples/opcua/delete_server.py --tls-auth
145185
```
146186

147187
This will remove the server and all associated tasks from Synnax.
@@ -232,8 +272,40 @@ OPC UA supports various security policies:
232272
- **Aes128-Sha256-RsaOaep**: High security
233273
- **Aes256-Sha256-RsaPss**: Highest security
234274

235-
**Note**: The current examples use `SecurityPolicy.None` for simplicity. For production
236-
deployments, configure security in `device_props()`.
275+
### TLS Test Server
276+
277+
The included test server supports TLS encryption via the `OPCUATLSSim` class, which runs
278+
on port 4842 with `Basic256Sha256_SignAndEncrypt`. Self-signed certificates for both
279+
server and client are generated automatically under `examples/opcua/certificates/`.
280+
281+
```python
282+
from examples.opcua import OPCUATLSSim
283+
284+
sim = OPCUATLSSim()
285+
sim.start() # Starts TLS server on opc.tcp://127.0.0.1:4842/
286+
sim.stop()
287+
```
288+
289+
### TLS Test Server with Username/Password
290+
291+
The `OPCUATLSAuthSim` class adds username/password authentication on top of TLS
292+
encryption. It runs on port 4843 with `Basic256Sha256_SignAndEncrypt` and requires
293+
credentials (`testuser` / `testpass`).
294+
295+
```python
296+
from examples.opcua import OPCUATLSAuthSim
297+
298+
sim = OPCUATLSAuthSim()
299+
sim.start() # Starts on opc.tcp://127.0.0.1:4843/
300+
sim.stop()
301+
```
302+
303+
All three server variants expose the same full set of variables (floats, bools, arrays,
304+
commands, timestamps).
305+
306+
**Note**: The default `OPCUASim` uses no encryption for simplicity. For production
307+
deployments, configure security mode, policy, and credentials when registering the
308+
device.
237309

238310
## Sample Rates
239311

client/py/examples/opcua/__init__.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,18 @@
77
# License, use of this software will be governed by the Apache License, Version 2.0,
88
# included in the file licenses/APL.txt.
99

10-
"""OPC UA example package."""
10+
"""OPC UA example package.
1111
12-
from .server import OPCUASim, run_server
12+
Lazy imports avoid the RuntimeWarning when running
13+
``python -m examples.opcua.server``.
14+
"""
1315

14-
__all__ = ["run_server", "OPCUASim"]
16+
__all__ = ["run_server", "OPCUASim", "OPCUATLSSim", "OPCUATLSAuthSim"]
17+
18+
19+
def __getattr__(name: str):
20+
if name in __all__:
21+
from . import server
22+
23+
return getattr(server, name)
24+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

client/py/examples/opcua/connect_server.py

Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,68 @@
1717
1. Start the Synnax Driver (if not already running).
1818
1919
2. Start the OPC UA test server:
20-
uv run python driver/opc/dev/server_extended.py
20+
uv run python -m examples.opcua.server
21+
22+
For a TLS-encrypted server:
23+
uv run python -m examples.opcua.server --tls
2124
2225
3. Login to Synnax (if not already logged in):
2326
uv run sy login
2427
2528
4. Run this script:
26-
uv run python examples/opcua/connect_opc_server.py
29+
uv run python examples/opcua/connect_server.py
30+
31+
For a TLS-encrypted server:
32+
uv run python examples/opcua/connect_server.py --tls
2733
2834
Configuration:
2935
Modify the constants below to match your OPC UA server configuration.
3036
"""
3137

32-
import json
33-
from uuid import uuid4
38+
import argparse
39+
from pathlib import Path
3440

3541
import synnax as sy
3642

37-
# Configuration
38-
DEVICE_NAME = "OPC UA Server"
39-
ENDPOINT = "opc.tcp://127.0.0.1:4841/"
43+
# Certificate directory (auto-generated by the encrypted test server)
44+
CERT_DIR = Path(__file__).parent / "certificates"
45+
46+
# Plain server config
47+
PLAIN_DEVICE_NAME = "OPC UA Server"
48+
PLAIN_ENDPOINT = "opc.tcp://127.0.0.1:4841/"
49+
50+
# TLS-encrypted server config
51+
TLS_DEVICE_NAME = "OPC UA TLS Server"
52+
TLS_ENDPOINT = "opc.tcp://127.0.0.1:4842/"
53+
54+
# TLS-encrypted + username/password server config
55+
TLS_AUTH_DEVICE_NAME = "OPC UA TLS Auth Server"
56+
TLS_AUTH_ENDPOINT = "opc.tcp://127.0.0.1:4843/"
57+
TLS_AUTH_USERNAME = "testuser"
58+
TLS_AUTH_PASSWORD = "testpass"
59+
60+
parser = argparse.ArgumentParser(description="Connect an OPC UA server to Synnax")
61+
parser.add_argument(
62+
"--tls",
63+
action="store_true",
64+
help="Connect to the TLS-encrypted server (port 4842)",
65+
)
66+
parser.add_argument(
67+
"--tls-auth",
68+
action="store_true",
69+
help="Connect to the TLS-encrypted server with username/password (port 4843)",
70+
)
71+
args = parser.parse_args()
72+
73+
if args.tls_auth:
74+
DEVICE_NAME = TLS_AUTH_DEVICE_NAME
75+
ENDPOINT = TLS_AUTH_ENDPOINT
76+
elif args.tls:
77+
DEVICE_NAME = TLS_DEVICE_NAME
78+
ENDPOINT = TLS_ENDPOINT
79+
else:
80+
DEVICE_NAME = PLAIN_DEVICE_NAME
81+
ENDPOINT = PLAIN_ENDPOINT
4082

4183
# Connect to Synnax
4284
client = sy.Synnax()
@@ -46,6 +88,10 @@
4688
print("=" * 70)
4789
print(f"Target Device Name: {DEVICE_NAME}")
4890
print(f"Endpoint: {ENDPOINT}")
91+
if args.tls_auth:
92+
print("Security: Basic256Sha256 SignAndEncrypt + Username/Password")
93+
elif args.tls:
94+
print("Security: Basic256Sha256 SignAndEncrypt")
4995
print()
5096

5197
# Check if device already exists
@@ -81,21 +127,40 @@
81127
rack = client.racks.retrieve_embedded_rack()
82128
print(f"Using rack: {rack.name} (key={rack.key})")
83129

84-
# Create the device with proper connection properties
85-
device = sy.opcua.Device(
86-
endpoint=ENDPOINT,
87-
name=DEVICE_NAME,
88-
location=ENDPOINT,
89-
rack=rack.key,
90-
)
91-
130+
# Build device kwargs
131+
device_kwargs: dict = {
132+
"endpoint": ENDPOINT,
133+
"name": DEVICE_NAME,
134+
"location": ENDPOINT,
135+
"rack": rack.key,
136+
}
137+
138+
if args.tls_auth or args.tls:
139+
device_kwargs.update(
140+
security_mode="SignAndEncrypt",
141+
security_policy="Basic256Sha256",
142+
server_cert=str(CERT_DIR / "server-certificate.der"),
143+
client_cert=str(CERT_DIR / "client-certificate.der"),
144+
client_private_key=str(CERT_DIR / "client-private-key.pem"),
145+
)
146+
if args.tls_auth:
147+
device_kwargs.update(
148+
username=TLS_AUTH_USERNAME,
149+
password=TLS_AUTH_PASSWORD,
150+
)
151+
152+
device = sy.opcua.Device(**device_kwargs)
92153
created_device = client.devices.create(device)
93154

94155
print("✓ Device connected successfully!")
95156
print(f" - Name: {created_device.name}")
96157
print(f" - Key: {created_device.key}")
97158
print(f" - Location: {created_device.location}")
98159
print(f" - Rack: {rack.name}")
160+
if args.tls_auth:
161+
print(" - Security: Basic256Sha256 SignAndEncrypt + Username/Password")
162+
elif args.tls:
163+
print(" - Security: Basic256Sha256 SignAndEncrypt")
99164
print()
100165
print("Device is ready to use.")
101166
print("=" * 70)
@@ -116,5 +181,11 @@
116181
print(f" - Name: {DEVICE_NAME}")
117182
print(f" - Endpoint: {ENDPOINT}")
118183
print(" - Make: opc")
184+
if args.tls:
185+
print(" - Security Mode: SignAndEncrypt")
186+
print(" - Security Policy: Basic256Sha256")
187+
print(f" - Server Certificate: {CERT_DIR / 'server-certificate.der'}")
188+
print(f" - Client Certificate: {CERT_DIR / 'client-certificate.der'}")
189+
print(f" - Client Private Key: {CERT_DIR / 'client-private-key.pem'}")
119190
print("=" * 70)
120191
exit(1)

0 commit comments

Comments
 (0)