Lift certificate pinning into the connector contract
fluksio.flow.connector_tls: trust the certificate a device presented on first contact, kept in ConnectorNode.store, and name the new fingerprint when another one is refused. Additive, so no contract version bump; hue and wfrac use it.
This commit is contained in:
@@ -240,6 +240,34 @@ curl -X DELETE $FLUKSIO/connectors/hue/state -H "Authorization: Bearer $TOKEN"
|
||||
So a connector has to cope with a key disappearing: clearing is how somebody
|
||||
tells it to start over.
|
||||
|
||||
### Pinning a device's certificate
|
||||
|
||||
A device on the local network presents a certificate no system trusts and that
|
||||
names a serial or a MAC rather than its address, so `verify=True` fails and
|
||||
`verify=False` trusts anyone. `fluksio.flow.connector_tls` trusts the
|
||||
certificate the device presented on first contact instead, kept in `self.store`
|
||||
under `cert:<address>`:
|
||||
|
||||
```python
|
||||
from fluksio.flow.connector_tls import context_for, pinned_certificate, raise_if_changed
|
||||
|
||||
address = f"{self.config.host}:{self.config.port}"
|
||||
pem = pinned_certificate(self.store, address, self.config.timeout) # blocking
|
||||
try:
|
||||
with httpx.Client(verify=context_for(pem)) as client:
|
||||
...
|
||||
except httpx.ConnectError as exc:
|
||||
raise_if_changed(exc, pem, address, self.config.timeout, "my_node_type")
|
||||
raise
|
||||
```
|
||||
|
||||
- First contact sends a TLS hello and nothing else; a device that does not
|
||||
complete one raises `OSError` and nothing is pinned.
|
||||
- Any other certificate is refused in the handshake, before a key or a command
|
||||
is sent. `raise_if_changed` turns that refusal into `CertificateChanged`,
|
||||
which names the new SHA-256 and the key to clear, under the node type given.
|
||||
- Clearing the key accepts whatever the device presents next.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
```python
|
||||
|
||||
Reference in New Issue
Block a user