Add explicit `verify_wg_peer()` inspection
Validate a declared tunnel key against one `pyroute2` snapshot containing the iface's own key and configured peers. Deats, - share worker offload across all WireGuard key readers - forward `WGTunnelSpec.iface` and `.netns` to the read - reject malformed declarations before netlink I/O - export the async helper and cover local, peer and absent keys - replace multihost's `wg show` subprocess probe Prompt-IO: ai/prompt-io/opencode/20260822T023226Z_59a8ecfd_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/wg_pyroute2_read
parent
35e496c166
commit
ece3827bdf
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
session: tractor-addr-unpacking
|
||||||
|
timestamp: 2026-08-22T02:32:26Z
|
||||||
|
git_ref: 59a8ecfd
|
||||||
|
scope: code
|
||||||
|
substantive: true
|
||||||
|
raw_file: 20260822T023226Z_59a8ecfd_prompt_io.raw.md
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt
|
||||||
|
|
||||||
|
After committing the read-only pyroute2 helpers and `wgman` design
|
||||||
|
update, the human authorized the next isolated Layer B change:
|
||||||
|
explicit `verify_wg_peer()` composition over WireGuard inspection.
|
||||||
|
|
||||||
|
## Response summary
|
||||||
|
|
||||||
|
Added and exported async `verify_wg_peer()` using one validated
|
||||||
|
WireGuard key snapshot. It recognizes local-interface and configured
|
||||||
|
peer identities without coupling kernel inspection to address
|
||||||
|
parsing. Updated the multihost examples to use the production helper
|
||||||
|
and removed their subprocess-based probe.
|
||||||
|
|
||||||
|
## Files changed
|
||||||
|
|
||||||
|
- `tractor/discovery/_tunnel.py` - shared async snapshot reader and
|
||||||
|
explicit verification helper.
|
||||||
|
- `tractor/discovery/__init__.py` - public verification export.
|
||||||
|
- `tests/discovery/test_wg_inspection.py` - local, peer, absent and
|
||||||
|
malformed-key verification coverage.
|
||||||
|
- `examples/multihost/wg_lan/host_a_srv.py` - async local-key check.
|
||||||
|
- `examples/multihost/wg_lan/host_b_client.py` - async peer-key check.
|
||||||
|
- `examples/multihost/wg_lan/wg_maddr.py` - removed obsolete
|
||||||
|
subprocess probe.
|
||||||
|
- `examples/multihost/wg_lan/README.md` - pyroute2 requirements and
|
||||||
|
verification workflow.
|
||||||
|
- `ai/tpt-backends/03_wg_tunnel_bindspace.md` - async API contract.
|
||||||
|
|
||||||
|
## Human edits
|
||||||
|
|
||||||
|
The human selected this pre-agreed verification layer as the next
|
||||||
|
atomic change after reviewing and committing the preceding read and
|
||||||
|
architecture changes. The agent implemented the source changes; no
|
||||||
|
direct manual edits or follow-up corrections were observed.
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
model: gpt-5.6-sol
|
||||||
|
service: opencode
|
||||||
|
timestamp: 2026-08-22T02:32:26Z
|
||||||
|
git_ref: 59a8ecfd
|
||||||
|
diff_cmd: git diff HEAD~1..HEAD
|
||||||
|
---
|
||||||
|
|
||||||
|
# Raw output - verify declared WireGuard identities
|
||||||
|
|
||||||
|
The human authorized the next incremental Layer B change after
|
||||||
|
committing the read-only pyroute2 helpers and first-child `wgman`
|
||||||
|
design update.
|
||||||
|
|
||||||
|
> `git diff HEAD~1..HEAD -- tractor/discovery/_tunnel.py tractor/discovery/__init__.py tests/discovery/test_wg_inspection.py examples/multihost/wg_lan ai/tpt-backends/03_wg_tunnel_bindspace.md`
|
||||||
|
|
||||||
|
Added async `verify_wg_peer()` over one WireGuard key snapshot. It
|
||||||
|
validates the declared `WGTunnelSpec.peer_pubkey` before I/O, forwards
|
||||||
|
the spec's iface/netns, and accepts either the local interface key for
|
||||||
|
a source/listen declaration or a configured peer key for a
|
||||||
|
destination/dial declaration.
|
||||||
|
|
||||||
|
Refactored worker offload behind one shared async reader so
|
||||||
|
verification cannot compare two different netlink snapshots. Exported
|
||||||
|
the helper, added local/peer/absent/malformed-key coverage, and moved
|
||||||
|
the multihost examples from their local `wg show` subprocess probe to
|
||||||
|
the production API.
|
||||||
|
|
||||||
|
Ruff and lock checks passed. Focused WireGuard/tunnel/multiaddr
|
||||||
|
coverage passed 51 tests; the complete discovery suite passed 92
|
||||||
|
tests with 2 xpasses.
|
||||||
|
|
@ -206,7 +206,7 @@ Observed protocol-name lists, for writing the `match`:
|
||||||
**not** hand-roll a `wg` parser in `tractor` — the whole point
|
**not** hand-roll a `wg` parser in `tractor` — the whole point
|
||||||
of #429 was dropping the NIH parser.
|
of #429 was dropping the NIH parser.
|
||||||
|
|
||||||
### 3.3 verification helper (pure, composable)
|
### 3.3 pure codecs + explicit verification
|
||||||
|
|
||||||
Port #482 §2's pure helpers into
|
Port #482 §2's pure helpers into
|
||||||
`tractor/discovery/_tunnel.py`, keeping the impure probe cleanly
|
`tractor/discovery/_tunnel.py`, keeping the impure probe cleanly
|
||||||
|
|
@ -215,12 +215,12 @@ separated until layer B:
|
||||||
```python
|
```python
|
||||||
def parse_wg_maddr(maddr: str) -> TunnelledAddress: ... # pure
|
def parse_wg_maddr(maddr: str) -> TunnelledAddress: ... # pure
|
||||||
def wg8_pubkey(multibase_key: str) -> str: ... # pure
|
def wg8_pubkey(multibase_key: str) -> str: ... # pure
|
||||||
def verify_wg_peer(spec: WGTunnelSpec) -> bool: ... # layer B
|
async def verify_wg_peer(spec: WGTunnelSpec) -> bool: ... # layer B
|
||||||
```
|
```
|
||||||
|
|
||||||
In layer A `verify_wg_peer()` may shell out (`wg show <if>
|
Layer A's example-local `verify_wg_peer()` may shell out (`wg show
|
||||||
peers`), but it must be a *single* function so layer B swaps
|
<if> peers`), but layer B replaces that probe with one explicit async
|
||||||
only its body. Never call it implicitly from
|
function backed by pyroute2. Never call it implicitly from
|
||||||
`wrap_address()`/`parse_maddr()` — parsing must stay pure and
|
`wrap_address()`/`parse_maddr()` — parsing must stay pure and
|
||||||
side-effect-free; verification is the *caller's* explicit step
|
side-effect-free; verification is the *caller's* explicit step
|
||||||
(and later, the bindspace `@acm`'s).
|
(and later, the bindspace `@acm`'s).
|
||||||
|
|
@ -309,7 +309,8 @@ async def read_wg_peers(
|
||||||
async def read_wg_pubkey(iface: str = 'wg0', ...) -> str: ...
|
async def read_wg_pubkey(iface: str = 'wg0', ...) -> str: ...
|
||||||
```
|
```
|
||||||
|
|
||||||
and `verify_wg_peer()` becomes a thin composition over the two.
|
and `verify_wg_peer()` becomes a thin composition over one shared key
|
||||||
|
snapshot.
|
||||||
Note the pure-getter rule: no `read_wg_peers(..., create=True)`.
|
Note the pure-getter rule: no `read_wg_peers(..., create=True)`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,12 @@ no `wg` codec. So `pyproject.toml` temporarily pins the merge commit
|
||||||
in its PEP 621 dependency metadata, and a plain
|
in its PEP 621 dependency metadata, and a plain
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv sync
|
uv sync --extra wg
|
||||||
```
|
```
|
||||||
|
|
||||||
gets you a `wg`-aware `multiaddr`. That pin goes away once a
|
gets you a `wg`-aware `multiaddr` plus pyroute2's Linux netlink API.
|
||||||
release carries the codec. `py-multibase` is a direct dependency.
|
The multiaddr pin goes away once a release carries the codec.
|
||||||
|
`py-multibase` is a direct dependency.
|
||||||
|
|
||||||
Without the codec `parse_wg_maddr()` raises immediately with an
|
Without the codec `parse_wg_maddr()` raises immediately with an
|
||||||
actionable message — there is deliberately **no** degraded
|
actionable message — there is deliberately **no** degraded
|
||||||
|
|
@ -150,13 +151,12 @@ Four corrections, all from
|
||||||
all. `parse_wg_maddr()` now rejects it with an actionable
|
all. `parse_wg_maddr()` now rejects it with an actionable
|
||||||
error.
|
error.
|
||||||
2. **parsing is pure.** #482's helper had the key-check adjacent
|
2. **parsing is pure.** #482's helper had the key-check adjacent
|
||||||
to the parse; `verify_wg_peer()` is now a separate, explicitly
|
to the parse; async `verify_wg_peer()` is now a separate,
|
||||||
composed step that the caller invokes. A parser that shells
|
explicitly composed step that the caller invokes. Implicit
|
||||||
out is a nasty surprise.
|
kernel inspection from a parser is a nasty surprise.
|
||||||
3. **no `sudo`.** #482 ran `sudo wg show`; a library/example must
|
3. **no `sudo` or subprocess.** #482 ran `sudo wg show`; tractor's
|
||||||
never escalate. `wg show` works unprivileged for read on most
|
helper reads generic netlink through pyroute2 and never attempts
|
||||||
setups; if yours needs root, run the script as root rather
|
privilege escalation or namespace creation.
|
||||||
than embedding `sudo`.
|
|
||||||
4. **no new `Address` proto-type.** The tunnel rides *beside* the
|
4. **no new `Address` proto-type.** The tunnel rides *beside* the
|
||||||
overlay addr in a frozen `TunnelledAddress`, and only `.overlay`
|
overlay addr in a frozen `TunnelledAddress`, and only `.overlay`
|
||||||
crosses into `open_nursery()`. #482 §6 floated a `WGAddress`
|
crosses into `open_nursery()`. #482 §6 floated a `WGAddress`
|
||||||
|
|
@ -166,7 +166,7 @@ Four corrections, all from
|
||||||
|
|
||||||
## next
|
## next
|
||||||
|
|
||||||
Layer A's `TunnelledAddress` and native maddr parser now live in
|
Layer A's `TunnelledAddress` and native maddr parser plus Layer B's
|
||||||
`tractor.discovery`. Next, replace this example's `wg(8)` verification
|
explicit pyroute2 verification now live in `tractor.discovery`. Next,
|
||||||
probe with `pyroute2`, then add `open_bindspace()` `@acm`s which
|
add `open_bindspace()` `@acm`s which create/tear down the iface and
|
||||||
create/tear down the iface and netns.
|
netns.
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,9 @@ from tractor.discovery import (
|
||||||
TunnelledAddress,
|
TunnelledAddress,
|
||||||
mk_maddr,
|
mk_maddr,
|
||||||
parse_wg_maddr,
|
parse_wg_maddr,
|
||||||
|
verify_wg_peer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from wg_maddr import verify_wg_peer
|
|
||||||
|
|
||||||
# bearer = host A's underlay `(ip, wg ListenPort)`
|
# bearer = host A's underlay `(ip, wg ListenPort)`
|
||||||
# key = host A's OWN tunnel pubkey
|
# key = host A's OWN tunnel pubkey
|
||||||
# overlay = the ep `tractor` binds, on the wg iface's addr
|
# overlay = the ep `tractor` binds, on the wg iface's addr
|
||||||
|
|
@ -35,7 +34,7 @@ async def echo(msg: str) -> str:
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
addr: TunnelledAddress = parse_wg_maddr(WG_MADDR)
|
addr: TunnelledAddress = parse_wg_maddr(WG_MADDR)
|
||||||
assert verify_wg_peer(addr), (
|
assert await verify_wg_peer(addr.tunnel), (
|
||||||
f'wg pubkey from maddr not active on wg0 !\n'
|
f'wg pubkey from maddr not active on wg0 !\n'
|
||||||
f'maddr: {WG_MADDR}\n'
|
f'maddr: {WG_MADDR}\n'
|
||||||
f'key: {addr.tunnel.peer_pubkey}\n'
|
f'key: {addr.tunnel.peer_pubkey}\n'
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,10 @@ import trio
|
||||||
from tractor.discovery import (
|
from tractor.discovery import (
|
||||||
TunnelledAddress,
|
TunnelledAddress,
|
||||||
parse_wg_maddr,
|
parse_wg_maddr,
|
||||||
|
verify_wg_peer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from host_a_srv import echo # noqa: F401 (RPC refs it by mod path)
|
from host_a_srv import echo # noqa: F401 (RPC refs it by mod path)
|
||||||
from wg_maddr import verify_wg_peer
|
|
||||||
|
|
||||||
# same maddr as host A: A's bearer, A's key, A's overlay ep
|
# same maddr as host A: A's bearer, A's key, A's overlay ep
|
||||||
WG_MADDR: str = (
|
WG_MADDR: str = (
|
||||||
'/ip4/192.168.1.50/udp/51820'
|
'/ip4/192.168.1.50/udp/51820'
|
||||||
|
|
@ -26,7 +25,7 @@ WG_MADDR: str = (
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
addr: TunnelledAddress = parse_wg_maddr(WG_MADDR)
|
addr: TunnelledAddress = parse_wg_maddr(WG_MADDR)
|
||||||
assert verify_wg_peer(addr), (
|
assert await verify_wg_peer(addr.tunnel), (
|
||||||
f'wg pubkey from maddr not a peer on wg0 !\n'
|
f'wg pubkey from maddr not a peer on wg0 !\n'
|
||||||
f'maddr: {WG_MADDR}\n'
|
f'maddr: {WG_MADDR}\n'
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
# tractor: distributed structured concurrency.
|
|
||||||
r'''
|
|
||||||
Verify `wg` peers declared by tractor's multiaddr parser.
|
|
||||||
|
|
||||||
`tractor.discovery.parse_wg_maddr()` owns pure parsing and delegates
|
|
||||||
all tunnel peeling to `py-multiaddr`. This example keeps only the
|
|
||||||
explicit impure probe used by the two-host demo; parsing never shells
|
|
||||||
out or verifies local interface state implicitly.
|
|
||||||
|
|
||||||
The canonical maddr form is:
|
|
||||||
|
|
||||||
/ip4/10.0.0.1/udp/51820/wg/u<key>/ip4/10.0.11.1/tcp/1616
|
|
||||||
\_______ wg bearer ______/\_ key _/\____ tractor ep _____/
|
|
||||||
|
|
||||||
The kernel owns the bearer socket. A future tractor bindspace may
|
|
||||||
provision it through netlink, but only the overlay is an application
|
|
||||||
`MsgTransport` endpoint.
|
|
||||||
|
|
||||||
'''
|
|
||||||
from __future__ import annotations
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from tractor.discovery import (
|
|
||||||
TunnelledAddress,
|
|
||||||
WGTunnelSpec,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def verify_wg_peer(
|
|
||||||
addr: TunnelledAddress,
|
|
||||||
iface: str|None = None,
|
|
||||||
) -> bool:
|
|
||||||
'''
|
|
||||||
Check the outer tunnel's key against one local `wg` iface.
|
|
||||||
|
|
||||||
IMPURE + explicit by design: neither `parse_wg_maddr()` nor
|
|
||||||
`tractor.discovery.parse_maddr()` calls this probe.
|
|
||||||
|
|
||||||
?TODO, per plan-03 layer B, swap this body for `pyroute2`
|
|
||||||
while retaining the explicit verification boundary.
|
|
||||||
|
|
||||||
'''
|
|
||||||
spec = addr.tunnel
|
|
||||||
if not isinstance(spec, WGTunnelSpec):
|
|
||||||
raise TypeError(
|
|
||||||
f'Unsupported tunnel spec: {type(spec)!r}'
|
|
||||||
)
|
|
||||||
|
|
||||||
iface = iface or spec.iface
|
|
||||||
|
|
||||||
def _wg(*args: str) -> str:
|
|
||||||
return subprocess.run(
|
|
||||||
['wg', 'show', iface, *args],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
).stdout
|
|
||||||
|
|
||||||
return (
|
|
||||||
spec.peer_pubkey in _wg('peers').split()
|
|
||||||
or
|
|
||||||
spec.peer_pubkey == _wg('public-key').strip()
|
|
||||||
)
|
|
||||||
|
|
@ -16,7 +16,10 @@ import trio
|
||||||
from tractor.discovery import (
|
from tractor.discovery import (
|
||||||
read_wg_peers,
|
read_wg_peers,
|
||||||
read_wg_pubkey,
|
read_wg_pubkey,
|
||||||
|
verify_wg_peer,
|
||||||
|
WGTunnelSpec,
|
||||||
)
|
)
|
||||||
|
from tractor.discovery import _tunnel
|
||||||
|
|
||||||
pyroute2: Any = pytest.importorskip('pyroute2')
|
pyroute2: Any = pytest.importorskip('pyroute2')
|
||||||
|
|
||||||
|
|
@ -24,6 +27,7 @@ pyroute2: Any = pytest.importorskip('pyroute2')
|
||||||
_PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc='
|
_PUBKEY: str = 'g3x7z0AdV1rM6UQU22CC7IL3/ivn4DzrE7ikDhCZ/Dc='
|
||||||
_PEER_1: str = '7PClzcj8o1yAjyPJb0zL2Gt0s2J7yZ6c0JXYqNBGr0E='
|
_PEER_1: str = '7PClzcj8o1yAjyPJb0zL2Gt0s2J7yZ6c0JXYqNBGr0E='
|
||||||
_PEER_2: str = 'H7bJbl1bpY7VzDlB5wI3KjA7JsiYoMWGDJd8dYgc5iw='
|
_PEER_2: str = 'H7bJbl1bpY7VzDlB5wI3KjA7JsiYoMWGDJd8dYgc5iw='
|
||||||
|
_MISSING_KEY: str = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
|
||||||
|
|
||||||
|
|
||||||
class Attrs:
|
class Attrs:
|
||||||
|
|
@ -215,3 +219,87 @@ def test_wg_client_closes_when_read_fails(
|
||||||
|
|
||||||
assert instance is not None
|
assert instance is not None
|
||||||
assert instance.closed
|
assert instance.closed
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('declared_key', 'expected'),
|
||||||
|
(
|
||||||
|
(_PUBKEY, True),
|
||||||
|
(_PEER_2, True),
|
||||||
|
(_MISSING_KEY, False),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_verify_wg_peer(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
declared_key: str,
|
||||||
|
expected: bool,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
A tunnel declaration can identify either side of one local iface.
|
||||||
|
|
||||||
|
Return one stable key snapshot from the async reader, then prove
|
||||||
|
a local interface key and configured peer both verify while an
|
||||||
|
absent key does not. Also prove the spec selects the iface/netns
|
||||||
|
supplied to the read instead of silently using process defaults.
|
||||||
|
|
||||||
|
'''
|
||||||
|
reads: list[tuple[str, str|None]] = []
|
||||||
|
|
||||||
|
async def read_keys(
|
||||||
|
iface: str,
|
||||||
|
netns: str|None,
|
||||||
|
) -> tuple[str, tuple[str, ...]]:
|
||||||
|
'''
|
||||||
|
Return one deterministic WireGuard key snapshot.
|
||||||
|
|
||||||
|
'''
|
||||||
|
reads.append((iface, netns))
|
||||||
|
return _PUBKEY, (_PEER_1, _PEER_2)
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_tunnel,
|
||||||
|
'_read_wg_keys',
|
||||||
|
read_keys,
|
||||||
|
)
|
||||||
|
spec: WGTunnelSpec = WGTunnelSpec(
|
||||||
|
peer_pubkey=declared_key,
|
||||||
|
iface='wg-test',
|
||||||
|
netns='actor-net',
|
||||||
|
)
|
||||||
|
|
||||||
|
assert trio.run(verify_wg_peer, spec) is expected
|
||||||
|
assert reads == [('wg-test', 'actor-net')]
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_wg_peer_validates_before_read(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
A directly constructed tunnel spec can contain a malformed key.
|
||||||
|
|
||||||
|
Install a reader which would fail if called, pass malformed
|
||||||
|
base64, and prove validation rejects the declaration before any
|
||||||
|
kernel-state inspection occurs.
|
||||||
|
|
||||||
|
'''
|
||||||
|
async def unexpected_read(
|
||||||
|
iface: str,
|
||||||
|
netns: str|None,
|
||||||
|
) -> NoReturn:
|
||||||
|
'''
|
||||||
|
Fail if malformed-key validation reaches the read boundary.
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise AssertionError('WireGuard read must not run')
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
_tunnel,
|
||||||
|
'_read_wg_keys',
|
||||||
|
unexpected_read,
|
||||||
|
)
|
||||||
|
spec: WGTunnelSpec = WGTunnelSpec(
|
||||||
|
peer_pubkey='not-base64',
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
trio.run(verify_wg_peer, spec)
|
||||||
|
|
|
||||||
|
|
@ -40,5 +40,6 @@ from ._tunnel import (
|
||||||
read_wg_pubkey as read_wg_pubkey,
|
read_wg_pubkey as read_wg_pubkey,
|
||||||
strip_tunnels as strip_tunnels,
|
strip_tunnels as strip_tunnels,
|
||||||
tunnels_of as tunnels_of,
|
tunnels_of as tunnels_of,
|
||||||
|
verify_wg_peer as verify_wg_peer,
|
||||||
wg8_pubkey as wg8_pubkey,
|
wg8_pubkey as wg8_pubkey,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ def _wg8_key_str(
|
||||||
return key
|
return key
|
||||||
|
|
||||||
|
|
||||||
def _read_wg_keys(
|
def _sync_read_wg_keys(
|
||||||
iface: str,
|
iface: str,
|
||||||
netns: str|None,
|
netns: str|None,
|
||||||
) -> tuple[str, tuple[str, ...]]:
|
) -> tuple[str, tuple[str, ...]]:
|
||||||
|
|
@ -275,6 +275,22 @@ def _read_wg_keys(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _read_wg_keys(
|
||||||
|
iface: str,
|
||||||
|
netns: str|None,
|
||||||
|
) -> tuple[str, tuple[str, ...]]:
|
||||||
|
'''
|
||||||
|
Read one WireGuard key snapshot without blocking Trio.
|
||||||
|
|
||||||
|
'''
|
||||||
|
return await trio.to_thread.run_sync(
|
||||||
|
_sync_read_wg_keys,
|
||||||
|
iface,
|
||||||
|
netns,
|
||||||
|
abandon_on_cancel=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def read_wg_pubkey(
|
async def read_wg_pubkey(
|
||||||
iface: str = 'wg0',
|
iface: str = 'wg0',
|
||||||
netns: str|None = None,
|
netns: str|None = None,
|
||||||
|
|
@ -286,11 +302,9 @@ async def read_wg_pubkey(
|
||||||
keys: tuple[
|
keys: tuple[
|
||||||
str,
|
str,
|
||||||
tuple[str, ...],
|
tuple[str, ...],
|
||||||
] = await trio.to_thread.run_sync(
|
] = await _read_wg_keys(
|
||||||
_read_wg_keys,
|
|
||||||
iface,
|
iface,
|
||||||
netns,
|
netns,
|
||||||
abandon_on_cancel=False,
|
|
||||||
)
|
)
|
||||||
return keys[0]
|
return keys[0]
|
||||||
|
|
||||||
|
|
@ -306,15 +320,39 @@ async def read_wg_peers(
|
||||||
keys: tuple[
|
keys: tuple[
|
||||||
str,
|
str,
|
||||||
tuple[str, ...],
|
tuple[str, ...],
|
||||||
] = await trio.to_thread.run_sync(
|
] = await _read_wg_keys(
|
||||||
_read_wg_keys,
|
|
||||||
iface,
|
iface,
|
||||||
netns,
|
netns,
|
||||||
abandon_on_cancel=False,
|
|
||||||
)
|
)
|
||||||
return keys[1]
|
return keys[1]
|
||||||
|
|
||||||
|
|
||||||
|
async def verify_wg_peer(
|
||||||
|
spec: WGTunnelSpec,
|
||||||
|
) -> bool:
|
||||||
|
'''
|
||||||
|
Verify a declared WireGuard identity against local kernel state.
|
||||||
|
|
||||||
|
A source/listen maddr names the local interface key, while a
|
||||||
|
destination/dial maddr names one configured peer. Accept either
|
||||||
|
match without making verification an implicit part of parsing.
|
||||||
|
|
||||||
|
'''
|
||||||
|
declared_key: str = _wg8_key_str(spec.peer_pubkey)
|
||||||
|
keys: tuple[
|
||||||
|
str,
|
||||||
|
tuple[str, ...],
|
||||||
|
] = await _read_wg_keys(
|
||||||
|
spec.iface,
|
||||||
|
spec.netns,
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
declared_key == keys[0]
|
||||||
|
or
|
||||||
|
declared_key in keys[1]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _wg_proto_code() -> int:
|
def _wg_proto_code() -> int:
|
||||||
'''
|
'''
|
||||||
Deliver the installed `py-multiaddr` `/wg/` protocol code.
|
Deliver the installed `py-multiaddr` `/wg/` protocol code.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue