Releases: meshpnet/meshp
Release list
v0.2.2
The release where the command line became usable and the page became something to
look at. Nothing about the data plane changed; everything about reaching it did.
Still pre-alpha. Every packet goes through a relay — there are no direct
peer-to-peer paths yet, so throughput and latency are worse than any mature mesh
and a self-hoster pays for that bandwidth. There are no mobile clients. Access
policy is enforced on Linux only.
The command line does things now
v0.2.1 shipped seven commands, all of them about the machine they ran on, and a
help text advertising ten nouns and thirty-seven verbs that did not exist. That
help now lists only what works, and what works is five nouns:
meshp device— list, revoke, forget. Names as well as ids, because an id
is a UUID nobody has memorised.meshp network— list, show, use, create.useremembers which network
commands act on, so--networkis optional after the first time.meshp acl— show, edit, test, apply, versions. The policy is edited as a
document, parsed before it is sent, and shown as a diff against what is live.meshp dns— list, add, remove.meshp token— list, create, revoke, for the API tokensmeshp loginmints.
Every one is a client of a route that already existed. None of them added an
endpoint, which is the point: the API is the contract three clients share
(ADR-0009), and a verb that needed an endpoint shaped for it would be a verb
defining the API.
See what a policy does before publishing it
POST /networks/{id}/acl/test compiles a document for one named device and
returns the packet filter it would actually enforce, without storing anything.
Selectors become the prefixes allocated to real devices, and each device is shown
its own side of a rule.
Reachable as meshp acl test <device> [--file p.json] and from the page. It is
compiled the way the session builder compiles it, so the answer is what the device
would be sent rather than a second implementation's opinion of it.
The page
A diagram of the network, which is a hub rather than a mesh because that is
what the network is — drawing a line between two devices would show a path that
does not exist. Devices carrying a prefix or reporting a fault are drawn and
named; past a couple of dozen the rest are counted rather than crowded.
An access policy editor: the document as text, diffed against what is live,
with the dry-run above as its last step. Refusing to publish a policy identical to
the one in force, because a version history where half the entries changed nothing
cannot be read backwards.
And a layout — the verdict, then the devices, then the things that change a
network. It was eight identical cards in the order they were written.
Devices can be erased, not only revoked
Since v0.2.1: DELETE /organizations/{id}/devices/{id} removes a device, its
memberships and its keys from every network at once, leaving the audit trail
behind. Revoking cuts a device out and leaves it visible; this leaves nothing.
Upgrading from v0.2.1
Nothing invalidates an existing deployment and no migration is needed — the schema
is unchanged at 17. meshp --help will list fewer commands than it used to, which
is the correction rather than a regression: the ones it stops advertising never
worked.
What's Changed
- docs: ADR-0032, the UI and the CLI are clients of the API by @aswinsam in #220
- refactor(web): move the reconciler into its own module by @aswinsam in #221
- test(web): the reconciler is checked by a machine by @aswinsam in #222
- fix(cli): the help offers only what the binary does by @aswinsam in #224
- test(web): the page's own controls are checked by a machine by @aswinsam in #225
- feat(cli): meshp device, the first noun by @aswinsam in #226
- feat(cli): meshp network, and a choice that sticks by @aswinsam in #227
- feat(cli): meshp acl, a policy edited as a document by @aswinsam in #228
- feat(acl): see what a device would enforce before publishing by @aswinsam in #229
- feat(web): the policy is edited on the page, as a document by @aswinsam in #230
- fix(web): lay the page out around what somebody came for by @aswinsam in #231
- feat(web): the network, as a picture by @aswinsam in #232
- fix(web): sign in down a column, not across a screen by @aswinsam in #233
- feat(cli): meshp dns and meshp token by @aswinsam in #234
Full Changelog: v0.2.1...v0.2.2
v0.2.1
Three changes on top of v0.2.0. One of them is a security fix that matters if you ran the
compose stack anywhere other than your own laptop.
If you used docker-compose.yml, read this (#217)
docker-compose.yml published three things on every interface rather than on loopback:
- PostgreSQL on
5432, holding enrolment tokens, access policies and password hashes,
with the credentials written three lines above it in the same file. - The control plane API on
8080, which mints enrolment tokens, in plaintext. - The relay's admin endpoint on
9090, which is health and statistics and is not
authenticated. The binary defaults it to127.0.0.1:9090and says why; the compose file
overrode that and published it, undoing the decision without recording one.
On a laptop that means anybody on the same café wifi. On a VPS it means the internet.
All three are now bound to 127.0.0.1, and the relay's admin address is left unset so the
binary's own default applies. Read the relay's stats with
docker compose exec relay wget -qO- http://127.0.0.1:9090/stats.
This file is how somebody tries meshp. A real deployment is docs/self-hosting.md, which
serves TLS and does not use it.
A device can be erased, not only revoked (#218)
Nothing could delete a device. DELETE /networks/{id}/devices/{id} revokes, the listing
keeps revoked memberships on purpose so an administrator can see a device is out, and no
query anywhere issued DELETE FROM devices. "Take this laptop off the system" had no answer.
DELETE /api/v1/organizations/{organizationID}/devices/{deviceID} erases a device, its
memberships, its keys and its route advertisements in every network at once, behind a new
organization.devices.forget permission held by administrators and owners. The page grows a
Forget button, which appears once a device is revoked.
Organisation-scoped rather than network-scoped, because a device holds memberships in several
networks (ADR-0004) and no one of them owns it.
The audit trail survives the erasure — it records labels rather than foreign keys — so what
was removed, by whom, and which keys were withdrawn is still answerable afterwards. That is
what makes this a real delete rather than a tombstone.
Migration 0017 changes state_changes.membership_id to ON DELETE CASCADE. It was
SET NULL, which combined with the table's own CHECK made deleting a device impossible: a
referential SET NULL is an UPDATE, CHECK constraints run on UPDATE, and the delete failed on
the device's own enrolment row. Applies automatically on start.
The README says which platforms carry traffic (#215)
It claimed too much in one place and too little in three. The header offered "Linux, Windows,
macOS, Android and iOS" as a feature list when two of those have no data plane; the status
section opened by describing a Linux-only agent four paragraphs above the text correctly
saying three platforms carry traffic; and "full-tunnel egress works on Linux" had been wrong
since #166 and #180.
Upgrading from v0.2.0
Nothing invalidates an existing deployment. The schema migrates forward on start. If you run
the compose stack and had reached it from another machine, that will stop working — which is
the point.
What's Changed
- docs: the README says which platforms carry traffic, in both directions by @aswinsam in #215
- fix(compose): the database and the relay's admin endpoint stop listening to the network by @aswinsam in #217
- feat(devices): a device can be erased, not only revoked by @aswinsam in #218
Full Changelog: v0.2.0...v0.2.1
v0.2.0
The first release with a data plane on more than one operating system, and the first
with people in it rather than a shared secret.
Still pre-alpha. Every packet goes through a relay — there are no direct peer-to-peer
paths yet, so throughput and latency are worse than any mature mesh and a self-hoster pays
for that bandwidth. There are no mobile clients. Access policy is enforced on Linux only.
macOS is a real data plane
A tunnel (#152), names that resolve (#153), a full-tunnel default route (#166), egress that
is refused unless it goes through the tunnel (#169), recovery from a change of network or a
killed agent (#171), and a launchd daemon so none of it needs a terminal (#197).
Five bugs in the macOS egress claim were found by running it on a laptop rather than in a
test, and fixed in #201, #203, #209 and #210 — cloned routes counted as the plan's, a
route belonging to another tunnel counted as a local network, and a claim could not survive
losing DNS.
Windows is a real data plane
A tunnel over WinTun (#175), names through NRPT (#178), a full-tunnel default route (#180),
egress refused through meshp's own WFP layer (#182), and a service that answers the service
control manager properly (#198).
Each of the three platforms is exercised on a real runner of that operating system in CI,
not cross-compiled.
People, not a shared secret
Local accounts with passwords and sessions (#140), permissions and roles scoped to a
network (#142), API tokens that can never grant more than their owner has (#143), and an
audit trail that names who did it (#141). Sign-in slows an attacker down without ever
locking a real person out (#173).
meshp-control --bootstrap stands a deployment up with no shared secret at all (#163), and
an organisation is created over the API rather than with psql (#132). meshp login mints a
token and keeps it, so the CLI stops asking for the admin credential (#214).
MESHP_ADMIN_TOKEN still works. It is now bootstrap and break-glass rather than the
recommendation — see ADR-0024.
More than one of everything
A change on one control plane reaches agents connected to another (#155), the overview stops
reporting other replicas' devices as disconnected (#157), and a relay can be drained without
a restart (#162). Redundancy is not a paid feature.
Operations
The agent rotates its own log, because on macOS nothing else can (#199). The control plane
has somewhere to keep the certificates it obtains (#192) — the shipped systemd unit could
not previously serve TLS at all. Devices notice a change of network instead of waiting for
the next tick (#184).
Upgrading from v0.1.0
Nothing invalidates an existing deployment. The admin token still works, and the schema
migrates forward on start. Migration 0016 replaces a unique constraint on token names with a
partial index so a revoked token's name becomes free again; its down-migration deliberately
fails rather than silently discarding rows.
What's Changed
- fix(install): a pre-release is an answer, not a missing version by @aswinsam in #111
- feat(api): one endpoint answers whether anything in a network is broken by @aswinsam in #113
- feat(api): a browser gets a cookie, and the cookie can only read by @aswinsam in #114
- feat(web): a page that answers whether anything is broken by @aswinsam in #115
- feat(paths): a device says what its tunnel is actually doing by @aswinsam in #118
- feat(api): the overview says what is wrong, not only what is true by @aswinsam in #119
- test(install): cover the branch that runs when nobody names a version by @aswinsam in #120
- ci(reachability): fail on a mechanism nothing reaches by @aswinsam in #122
- feat(routes): record which candidate a device says it is using by @aswinsam in #123
- feat(routes): a device moving between candidates is auditable by @aswinsam in #124
- feat(api): the audit trail has a reader by @aswinsam in #125
- feat(web): the page shows why something moved by @aswinsam in #126
- feat(dns): the names an administrator writes down by @aswinsam in #127
- docs(reachability): say why the relays table is unread by @aswinsam in #129
- fix(prefixmap): a mapped range must not land on a reachable network by @aswinsam in #130
- feat(cli): meshp down, and the decision it turns on by @aswinsam in #131
- feat(api): an organisation is created over the API, not with psql by @aswinsam in #132
- docs(adr): local user accounts, and what becomes of the admin token by @aswinsam in #133
- feat(auth): people, passwords and sessions by @aswinsam in #140
- feat(audit): the trail names who did it by @aswinsam in #141
- feat(authz): permissions, roles, and the networks they reach by @aswinsam in #142
- feat(authz): API tokens, and what they may never do by @aswinsam in #143
- feat(web): the page stops being read-only by @aswinsam in #147
- docs: stop teaching the admin token by @aswinsam in #149
- feat(dataplane): a tunnel on macOS by @aswinsam in #152
- feat(dns): names resolve on macOS by @aswinsam in #153
- feat(replicas): a change on one control plane reaches agents on another by @aswinsam in #155
- ci: Bump the codeql group across 1 directory with 3 updates by @dependabot[bot] in #150
- feat(replicas): the overview stops calling other replicas' devices disconnected by @aswinsam in #157
- chore: ignore the four binaries when built at the repository root by @aswinsam in #158
- feat(relays): a relay can be drained without a restart by @aswinsam in #162
- feat(control): meshp-control --bootstrap, and a deployment that needs no shared secret by @aswinsam in #163
- ci: every platform-sensitive package runs on macOS, and a test says which by @aswinsam in #164
- feat(dataplane): macOS can take a full-tunnel default route by @aswinsam in #166
- refactor(tunnel): the egress lock is its own collaborator by @aswinsam in #167
- docs(adr): fail-closed egress on macOS lives in a pf anchor under com.apple by @aswinsam in #168
- feat(dataplane): macOS refuses egress that does not go through the tunnel by @aswinsam in #169
- test(meshpd): the daemon's wiring is tested, and its nil checks are reachable by @aswinsam in #170
- fix(dataplane): a macOS laptop that changes networks or is killed recovers by @aswinsam in #171
- feat(api): a guessed-at account slows down, and is never locked out by @aswinsam in #173
- docs(adr): the Windows data plane is WinTun, shipped beside the binary by @aswinsam in #174
- feat(dataplane): Windows brings up a tunnel, and CI proves it by @aswinsam in #175
- docs: say what Windows can do, which is a tunnel and nothing above it by @aswinsam in #176
- fix: meshp doctor start hint is platform-aware by @VedantMadane in #156
- docs(adr): Windows split DNS is NRPT, and the resolver moves to port 53 there by @aswinsam in #177
- feat(dataplane): names resolve on Windows, through NRPT by @aswinsam in #178
- docs: Windows resolves names now, and its resolver is on 53 by @aswinsam in #179
- feat(dataplane): Windows can take a full-tunnel default route by @aswinsam in #180
- docs(adr): fail-closed egress on Windows is meshp's own WFP layer by @aswinsam in #181
- feat(dataplane): Windows refuses egress that does not go through the tunnel by @aswinsam in #182
- docs: Windows is complete, and its lock has no command to remove it by @aswinsam in #183
- feat(dataplane): notice a change of network instead of waiting for the tick by @aswinsam in #184
- ci: lint the platforms this runner is not by @aswinsam in #187
- docs: state what each platform can do once, and check it against the code by @aswinsam in #188
- fix(web): update the page in place instead of rebuilding it every poll by @aswinsam in #189
- fix(deploy): give the control plane somewhere to keep the certificates it obtains by @aswinsam in #192
- fix(dataplane): stop withdrawing the routes the kernel installed itself by @aswinsam in https://github.com/m...
v0.1.0
What's Changed
- docs: say which sibling repos exist, and how to land a change by @aswinsam in #3
- feat(store): PostgreSQL access, self-applying schema, honest readiness by @aswinsam in #4
- feat(enroll): device identity, one-time tokens, proof of possession by @aswinsam in #5
- feat(api): enrolment over HTTP, and
meshp joinby @aswinsam in #6 - feat(session): the device control channel by @aswinsam in #7
- feat(agentapi): a local socket, and join behind the daemon by @aswinsam in #8
- feat(session): state deltas, a push that happens, and a bounded change log by @aswinsam in #9
- ci: build the container image, and prove it starts by @aswinsam in #10
- deps: Bump alpine from 3.21 to 3.24 in /deploy/docker by @dependabot[bot] in #1
- feat(wgplan): decide what a WireGuard interface should look like by @aswinsam in #11
- feat(wglink): create real WireGuard interfaces, and move a packet by @aswinsam in #12
- feat(meshpd): the daemon brings up a real tunnel, and keeps it up by @aswinsam in #13
- fix(wgplan): a peer's endpoint is evidence, not drift by @aswinsam in #14
- feat(meshpd): remember the port an interface settled on by @aswinsam in #15
- docs: decide how relayed traffic reaches a relay (ADR-0016) by @aswinsam in #16
- feat(relayproto): the framing a relay forwards by by @aswinsam in #17
- feat(relay): the forwarding core, and what it refuses to do by @aswinsam in #18
- feat(relaytoken): the capability a relay checks, which it cannot mint by @aswinsam in #19
- feat(meshp-relay): a relay that listens, and does so on several ports by @aswinsam in #20
- fix(relay): report an IPv4 peer as IPv4 by @aswinsam in #21
- feat(relayclient): the agent's side of the relay protocol by @aswinsam in #22
- feat(session): agents ask for relay credentials (ADR-0017) by @aswinsam in #23
- feat(wgplan): a relayed peer, and why it cannot be inferred by @aswinsam in #24
- feat(relayforward): carry packets between the kernel and a relay by @aswinsam in #25
- feat(session): tell agents which relay to use, and lower the MTU for it by @aswinsam in #26
- feat(relay): carry a peer's traffic through a relay, end to end by @aswinsam in #27
- feat(revoke): take a device out of a network, and mean it by @aswinsam in #28
- feat(acl): the policy language, and compiling it to a device's filter by @aswinsam in #29
- feat(acl): store a policy, and deliver the filter it compiles to by @aswinsam in #30
- feat(acl): enforce policy with nftables by @aswinsam in #31
- feat(tls): serve the control plane over TLS, and refuse plaintext to anywhere else by @aswinsam in #32
- feat(routes): route groups, their advertisers, and networks without psql by @aswinsam in #33
- feat(routes): select advertisers and tell devices who carries a prefix by @aswinsam in #34
- feat(routes): carry an assigned prefix on the peer that advertises it by @aswinsam in #35
- feat(routes): tell an advertiser what it carries by @aswinsam in #36
- feat(routes): forward into a carried prefix, and rewrite the source by @aswinsam in #37
- test(routes): prove a packet crosses into a carried LAN by @aswinsam in #38
- feat(routes): let client reports drive advertiser health by @aswinsam in #39
- feat(routes): decide when to leave an advertiser and when to come back by @aswinsam in #40
- feat(routes): fail over from an advertiser that has gone silent by @aswinsam in #41
- feat(routes): tell the control plane what this device observed by @aswinsam in #42
- test(e2e): stand up a route group against a real kernel by @aswinsam in #43
- test(e2e): two live agents, one per network namespace by @aswinsam in #44
- chore: add NOTICE and canonicalise authorship by @aswinsam in #45
- chore: move to Go 1.26.6, from one file by @aswinsam in #46
- fix(health): a client's verdict is authoritative on arrival by @aswinsam in #47
- test(e2e): a client fails over from a dead gateway on its own by @aswinsam in #48
- docs(adr-0009): the commercial layer is operated, never distributed by @aswinsam in #49
- docs(adr-0018): a mechanism is not done until something running reaches it by @aswinsam in #50
- feat(egress): the fail-closed kill switch, before anything claims a route by @aswinsam in #51
- feat(egress): reclaim a lock left behind by a dead agent by @aswinsam in #52
- feat(egress): work out what must never go through the tunnel by @aswinsam in #53
- docs(adr-0019): egress is routing, overlapping prefixes are addressing by @aswinsam in #54
- feat(egress): route a full tunnel without capturing its own packets by @aswinsam in #55
- feat(egress): claim the default route, behind the lock by @aswinsam in #56
- feat(egress): fail closed unless somebody says otherwise by @aswinsam in #57
- test(egress): kill the agent and prove the traffic stays blocked by @aswinsam in #58
- feat(egress): refuse plaintext DNS outside the tunnel by @aswinsam in #59
- feat(cli): meshp doctor explains a machine that is refusing traffic by @aswinsam in #60
- feat(release): an install path that is not "build it yourself" by @aswinsam in #61
- docs: stop pointing at a deployment guide that does not exist by @aswinsam in #69
- feat(routes): refuse a prefix two customer networks both claim by @aswinsam in #70
- feat(api): somewhere for an administrator to opt out of fail-closed by @aswinsam in #71
- feat(routes): the failover policy an administrator writes now reaches the agent by @aswinsam in #72
- feat(routes): probe through the advertiser, not just its handshake by @aswinsam in #73
- docs: guides for self-hosters, and the bugs that writing them found by @aswinsam in #74
- docs: ADR-0021, names resolve on the device, from desired state by @aswinsam in #79
- docs: ADR-0020 allocates mapped ranges server-side, and both ADRs are accepted by @aswinsam in #80
- feat(dns): a device resolves its networks' names, from state it already holds by @aswinsam in #81
- feat(dns): a device gets a name that is unique in the network it answers from by @aswinsam in #82
- fix(dns): the resolver binds before the daemon carries on by @aswinsam in #83
- feat(dns): point this machine's resolver at meshp for the names meshp owns by @aswinsam in #84
- fix(dns): a port free in one protocol is not free in the other by @aswinsam in #85
- chore: ignore what an assistant derives from this repository by @aswinsam in #86
- test(dns): a name resolves on the machine holding it by @aswinsam in #87
- feat(nftables): two customers on the same prefix, told apart by @aswinsam in #88
- fix(forwarding): an advertiser that carries nothing stops looking healthy by @aswinsam in #90
- feat(prefixmap): the control plane can choose where a colliding prefix is reached by @aswinsam in #91
- feat(routes): a colliding prefix reaches the device with somewhere to reach it by @aswinsam in #92
- feat(tunnel): a technician reaches both customers on 192.168.1.0/24 by @aswinsam in #93
- test(e2e): a lock that does not block is not a lock that went away by @aswinsam in https://github.com/meshpnet/meshp/pu...