Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions components.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ A Control Coordinator is concerned with routing control channel messages from/to
Every network needs at least one Control Coordinator.
Every Node MUST have exactly one Control Coordinator, all other Components within one Node directly exchange messages only with their Node's Control Coordinator.

A Control Coordinator is responsible for enforcing the Network's security mode as defined in {doc}`security`:
it SHALL reject connections that do not match the configured security mode,
and in `CURVE` mode, it SHALL validate client public keys via ZAP before accepting a connection.

:::{admonition} Note
Multiple Control Coordinator instances MAY be necessary for large deployments, but a single coordinator instance SHOULD be sufficient for operation.
:::
Expand Down
7 changes: 7 additions & 0 deletions control_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ The {ref}`control_protocol.md#message-layer` is the common language to understan

The transport layer ensures that a message arrives at its destination.

### Security

The control protocol supports optional authentication and encryption as defined in {doc}`security`.

When the `CURVE` security mode is enabled, all control channel connections (Component-to-Coordinator and Coordinator-to-Coordinator) are authenticated and encrypted using CurveZMQ before any LECO messages are exchanged.
The LECO message format and protocol flows remain unchanged; security operates entirely at the ZMQ transport layer.

### Protocol basics

#### Socket Configuration
Expand Down
6 changes: 6 additions & 0 deletions glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ Node
Network
The web of Components communicating with each other in a LECO deployment.

CURVE
A security mode using CurveZMQ (RFC 50) for authentication and encryption based on Curve25519 key pairs, see {doc}`security`.

Security mode
The security configuration of a LECO Network, either `NONE` (no security) or `CURVE` (authenticated and encrypted), see {ref}`security.md#security-modes`.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has to be changed, as #80 has been merged. Does {doc} still work?


Observer
A Component that receives data from other Components, e.g. for logging, storage, or plotting, see {ref}`components.md#observer`.

Expand Down
1 change: 1 addition & 0 deletions index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
components
messages
network-structure
security
control_protocol
glossary
Hello_world
Expand Down
4 changes: 4 additions & 0 deletions network-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ The Message Layer is the communication layer that concerns itself with (de)compo
The Transport Layer is the communication layer that transports LECO Messages between Components, making use of routing information in the Message header.
This uses zeromq or simpler localised methods, see the section "Message Transport Mode".

The Transport Layer MAY provide authentication and encryption as defined in {doc}`security`.

## Node

A Node is a local context in which (part of) a LECO deployment runs.
Expand Down Expand Up @@ -66,6 +68,8 @@ For example, the message `||Second frame|Third frame||Fifth frame` consists of 5

For some useful information see our {ref}`appendix.md#zmq`.

When using DMT, implementations SHOULD enable the `CURVE` security mode as defined in {doc}`security` to protect against unauthorized access and eavesdropping.

### Local Message Transport (LMT)

The Local Message Transport only works within a Node _and_ within a process.
Expand Down
207 changes: 207 additions & 0 deletions security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Security

LECO controls physical laboratory hardware, where unauthorized or tampered commands can damage equipment or cause safety hazards.
This section defines the security mechanisms available to protect LECO networks.

## Security modes

A LECO Network operates in one of the following security modes:

| Mode | Authentication | Encryption | Use case |
|---------|------------------|-------------------|------------------------------------------|
| `NONE` | No | No | Local development, trusted networks only |
| `CURVE` | Yes (Curve25519) | Yes (per-session) | Production deployments (RECOMMENDED) |

All Nodes in a Network MUST use the same security mode.
A Coordinator SHALL reject connections that use a different security mode than its own.

Each Component determines its security mode from its local configuration (e.g., a configuration file, command-line argument, or environment variable), alongside other connection parameters such as the Coordinator's host and port.
A Component SHALL NOT attempt to autodetect the security mode by trying `CURVE` and falling back to `NONE`, as this is vulnerable to downgrade attacks.

Implementations MUST support the `NONE` mode and SHOULD support the `CURVE` mode.

:::{warning}
The `NONE` mode provides no security.
It MUST NOT be used on networks accessible to untrusted parties.
Any ZeroMQ client can connect, sign in with any name, and send commands to any Component.
:::

## CURVE security mode

The `CURVE` mode uses [CurveZMQ](https://rfc.zeromq.org/spec/50/) (RFC 50) to provide mutual authentication and encryption at the ZeroMQ transport layer.
It requires [libsodium](https://doc.libsodium.org/) and libzmq >= 4.0 (bundled with pyzmq >= 14.0).

CurveZMQ authenticates connections before any LECO message is exchanged.
The LECO message format and protocol flows remain unchanged.

### Cryptographic keys

Each entity in a `CURVE`-secured Network has a long-term Curve25519 key pair:

- **Server key pair**: Each Coordinator has a server key pair (`server_secret_key`, `server_public_key`).
The ROUTER socket uses the server secret key.
- **Client key pair**: Each Component (including Coordinators connecting as clients to other Coordinators) has a client key pair (`client_secret_key`, `client_public_key`).
The DEALER/PUBLISHER/SUBSCRIBER socket uses the client secret key.

Key pairs SHOULD be generated using the ZMQ curve key generation utilities (e.g. `zmq.curve_keypair()` in pyzmq).

### Key distribution

A Coordinator SHALL maintain a list of authorized client public keys.

The mechanism for populating this list is implementation-defined.
RECOMMENDED approaches include:

1. **Key directory**: The Coordinator reads authorized public keys from a directory on the filesystem (one key per file, similar to SSH `authorized_keys`).
File names SHOULD correspond to the intended Component name for traceability.
2. **Configuration file**: The Coordinator reads authorized public keys from a configuration file mapping Component names to public keys.
3. **Any-authenticated mode**: The Coordinator accepts any client with a valid CurveZMQ handshake, without checking the public key against a list.
This provides encryption and prevents unauthenticated outsiders from connecting, but does not restrict which authenticated Components may join.

Implementations SHOULD support option 1 or 2 for production use, and MAY support option 3 for simplified setups.

Client public keys MUST NOT be transmitted over the network as part of the LECO protocol.
They MUST be distributed out-of-band (e.g., copied via secure file transfer, shared on a USB drive, or provisioned by a deployment tool).

### Control protocol setup

In `CURVE` mode, the control channel is secured as follows:

**Coordinator (server side):**
1. Set `CURVE_SERVER = 1` on the ROUTER socket.
2. Set `CURVE_SECRETKEY` to the server's secret key.
3. Optionally configure a ZAP handler to validate client public keys against the authorized list.

**Component (client side):**
1. Set `CURVE_SERVERKEY` to the Coordinator's server public key.
2. Set `CURVE_PUBLICKEY` and `CURVE_SECRETKEY` to the Component's own key pair.
3. Connect the DEALER socket to the Coordinator's ROUTER socket.

Both the Coordinator's security mode and the Component's security mode are determined by their respective local configurations.
They MUST agree; if they do not, the ZMQ connection will fail:
- A `CURVE` Component connecting to a `NONE` Coordinator will fail the CurveZMQ handshake (the Coordinator does not speak the Curve protocol).
- A `NONE` Component connecting to a `CURVE` Coordinator will be rejected because it does not complete the CurveZMQ handshake.

In either case, the connection is simply never established — no LECO messages are exchanged and no ambiguous error arises.

After the CurveZMQ handshake completes, the normal LECO `sign_in` flow proceeds unchanged.

If the CurveZMQ handshake fails (wrong server key, unauthorized client key), the connection is rejected at the ZMQ transport layer.
The Coordinator SHALL log the rejection reason (from ZAP) to aid debugging.
No LECO-level error message is sent because the connection is never established.

:::{mermaid}
sequenceDiagram
participant CA as Component A
participant Co as N1.COORDINATOR
Note over CA,Co: CURVE mode handshake
CA ->> Co: ZMQ CURVE handshake (client_secret + server_public)
Note right of Co: ZAP validates client public key
alt Handshake succeeds
Co -->> CA: Handshake OK
CA ->> Co: V|COORDINATOR|CA|H|sign_in
Co ->> CA: V|N1.CA|N1.COORDINATOR|H|result
else Unauthorized key
Co -->> CA: Connection rejected (ZAP)
Note right of Co: Log: "CURVE auth failed for [key hash]"
else Wrong server key
CA -->> Co: Handshake fails
Note left of CA: Log: "CURVE handshake failed"
end
:::
Comment on lines +93 to +111
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:::{mermaid}
sequenceDiagram
participant CA as Component A
participant Co as N1.COORDINATOR
Note over CA,Co: CURVE mode handshake
CA ->> Co: ZMQ CURVE handshake (client_secret + server_public)
Note right of Co: ZAP validates client public key
alt Handshake succeeds
Co -->> CA: Handshake OK
CA ->> Co: V|COORDINATOR|CA|H|sign_in
Co ->> CA: V|N1.CA|N1.COORDINATOR|H|result
else Unauthorized key
Co -->> CA: Connection rejected (ZAP)
Note right of Co: Log: "CURVE auth failed for [key hash]"
else Wrong server key
CA -->> Co: Handshake fails
Note left of CA: Log: "CURVE handshake failed"
end
:::
```mermaid
sequenceDiagram
participant CA as Component A
participant Co as N1.COORDINATOR
Note over CA,Co: CURVE mode handshake
CA ->> Co: ZMQ CURVE handshake (client_secret + server_public)
Note right of Co: ZAP validates client public key
alt Handshake succeeds
Co -->> CA: Handshake OK
CA ->> Co: V|COORDINATOR|CA|H|sign_in
Co ->> CA: V|N1.CA|N1.COORDINATOR|H|result
else Unauthorized key
Co -->> CA: Connection rejected (ZAP)
Note right of Co: Log: "CURVE auth failed for [key hash]"
else Wrong server key
CA -->> Co: Handshake fails
Note left of CA: Log: "CURVE handshake failed"
end
```

see #80


### Coordinator-to-Coordinator setup

When two Coordinators connect in `CURVE` mode:

- The connecting Coordinator acts as a client (DEALER socket), using its own client key pair and the remote Coordinator's server public key.
- The receiving Coordinator acts as a server (ROUTER socket), as described above.
- Both directions of the bidirectional link are secured independently.

### Data protocol setup

The data protocol (XPUB/XSUB proxy) uses CurveZMQ in a relay configuration:

**Proxy server (Data Coordinator):**

1. Set `CURVE_SERVER = 1` on the XPUB socket.
2. Set `CURVE_SECRETKEY` to the proxy's server secret key.
3. Set `CURVE_SERVER = 1` on the XSUB socket.
4. Set `CURVE_SECRETKEY` to the same (or a different) server secret key.

**Publisher (client side):**

1. Set `CURVE_SERVERKEY` to the proxy's XSUB server public key.
2. Set `CURVE_PUBLICKEY` and `CURVE_SECRETKEY` to the Publisher's own key pair.
3. Connect the PUB socket to the proxy's XSUB socket.

**Subscriber (client side):**

1. Set `CURVE_SERVERKEY` to the proxy's XPUB server public key.
2. Set `CURVE_PUBLICKEY` and `CURVE_SECRETKEY` to the Subscriber's own key pair.
3. Connect the SUB socket to the proxy's XPUB socket.

For the logging coordinator, the same pattern applies.

:::{note}
In CurveZMQ's PUB/SUB pattern, only the SUB/PUB client authenticates to the server.
The server does not authenticate to the client by default.
To achieve mutual authentication for the data channel, implementations SHOULD use a ZAP handler on the proxy to validate client public keys.
:::

## Upgrade considerations

### From NONE to CURVE

Existing deployments operating in `NONE` mode can upgrade to `CURVE` mode with the following steps:

1. **Generate key pairs**: Generate a server key pair for each Coordinator, and a client key pair for each Component.
2. **Distribute server public keys**: Each Component needs the server public key of its Coordinator.
3. **Distribute authorized client public keys**: Each Coordinator needs the public keys of all Components that should be allowed to connect.
4. **Enable CURVE mode on all entities**: Update the configuration of each Coordinator and Component.
5. **Restart the entire Network**: Since security mode must be uniform, all Nodes must switch simultaneously.

:::{warning}
A Network with mixed security modes will fail:
a Component in `NONE` mode cannot connect to a Coordinator in `CURVE` mode (and vice versa).
Plan a coordinated upgrade.
:::

### Protocol version

The LECO protocol version field (frame 1 of every message) remains unchanged when security mode changes.
Security negotiation happens entirely at the ZMQ transport layer and does not affect the LECO message format.

### Implementation compatibility

Implementations that do not support `CURVE` mode will not be able to connect to `CURVE`-secured Networks.
Implementations SHOULD:

- Clearly indicate whether they support `CURVE` mode in their documentation.
- Provide a meaningful error message if a `CURVE` handshake fails (e.g., "CURVE authentication failed: ensure the server key and client key are correctly configured").
- Default to `NONE` mode for backward compatibility, but emit a warning if `NONE` mode is used on a non-loopback network interface.

## Threat model

The security mechanisms in this section address the following threats:

| Threat | Mitigation | Mode |
|-----------------------------------------------------------------|------------------------------------------------------------------------|---------|
| Unauthorized Component connecting to a Coordinator | Client public key authentication | `CURVE` |
| Network eavesdropping on control or data messages | Per-session encryption | `CURVE` |
| Message tampering or injection in transit | Encryption with integrity checks (built into CurveZMQ) | `CURVE` |
| Name spoofing (Component claiming another's name) | Coordinator maps authenticated public key to authorized Component name | `CURVE` |
| Accidental misconnection (wrong Component to wrong Coordinator) | Server key ensures Components connect to the intended Coordinator | `CURVE` |

### Out of scope

The following threats are NOT addressed by the current security mechanisms:

- **Authorization / access control**: Once authenticated, any Component can send any RPC method to any other Component.
Future versions MAY define an access control mechanism.
- **Compromised keys**: If a Component's secret key is leaked, an attacker can impersonate it.
Key rotation is the responsibility of the deployment.
- **Denial of service**: An attacker with network access can flood a Coordinator with connection attempts.
Rate limiting and network-level protections are out of scope.
- **Local privilege escalation**: Components running on the same machine may be able to read each other's key files.
File permissions and OS-level security are out of scope.