Public cluster links - #18197
Conversation
8691581 to
364ff9d
Compare
This TOFU (Trust On First Use) with user verification provides authentication AFAIK. Is the "unauthenticated" bit here meaning there is no mTLS going on between the 2 clusters? |
Yes, exactly. "unauthenticated" means no mTLS; cluster A does not present a client certificate when connecting to cluster B. |
OK good, that's better in terms of security and aligns nicely with the other widely known TOFU model popularized by SSH. That said, I feels like a misnomer to call it "unauthenticated" then. "Unidirectional cluster link"? "One way cluster link"? |
Since we already have unidirectional as another type, how about "public"? @tomponline previously suggested this. |
925eeb0 to
66c2686
Compare
6ad2c54 to
01beba1
Compare
"Public cluster link" sounds good. There is also "anonymous cluster link" where the connecting side doesn't present its TLS cert. |
0bdb8d2 to
dec1f01
Compare
698caad to
e9cee31
Compare
810f2bf to
72e828e
Compare
72e828e to
711e68d
Compare
dd2bab9 to
e7ab8cc
Compare
5d166cb to
ee39952
Compare
9bf95f8 to
a897514
Compare
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
There was a problem hiding this comment.
Pull request overview
Adds a new public cluster link type that allows one cluster to reach another without presenting a client certificate, relying on pinned server TLS certificates and a two-phase (fetch/confirm) creation flow. This extends the existing cluster-links feature set (and builds on unidirectional links) while explicitly preventing use of public links for replication features that require authenticated writes.
Changes:
- Introduces API extension
cluster_links_public, new link typepublic, and a newClusterLinkCertificateresponse for the pending fetch phase. - Implements server-side pending/confirm handling with volatile pending state (
volatile.pending_certificate,volatile.pending_address) and public-link connection args (no client cert). - Updates CLI, docs, and integration tests to support and validate the new public flow (including rejection paths and replication/config-time guards).
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/suites/clustering.sh | Adds an end-to-end integration test covering public cluster link creation/confirmation, canonicalization, rejection paths, and replication restrictions. |
| test/includes/test-groups.sh | Adds the new clustering test to the cluster test group. |
| shared/version/api.go | Registers the new API extension cluster_links_public. |
| shared/api/cluster.go | Adds ClusterLinkTypePublic, RemoteAddress to request payload, and ClusterLinkCertificate response type. |
| lxd/metadata/configuration.json | Documents new volatile config keys for pending public links. |
| lxd/db/cluster/cluster_links.go | Adds DB type code mapping for public cluster links. |
| lxd/cluster/cluster_link.go | Adds server verification of remote being a LXD API, and public-link connection args (no client cert) for link refresh/connect flows. |
| lxd/api_replicators.go | Rejects public cluster links for replicators at config validation time. |
| lxd/api_project.go | Rejects public cluster links for project replication config (replica.cluster). |
| lxd/api_cluster_link.go | Adds pending/confirm public link request modes, validation, handlers, and volatile key validation. |
| lxc/cluster_link.go | Adds --public + --remote-address CLI flow with certificate fingerprint confirmation and cleanup behavior. |
| doc/rest-api.yaml | Documents the new ClusterLinkCertificate type and remote_address field in the REST API schema. |
| doc/metadata.txt | Documents new volatile config keys in generated metadata docs. |
| doc/howto/cluster_links_manage.md | Documents deletion behavior for public links. |
| doc/howto/cluster_links_create.md | Adds “Create a public cluster link” how-to section and updates link-type overview. |
| doc/explanation/clusters.md | Updates cluster-links explanation to include public links and their connection process. |
| doc/api-extensions.md | Documents the cluster_links_public extension and its two-phase behavior and restrictions. |
| client/lxd_cluster.go | Adds client support for pending public link creation and extension gating for public link creation. |
| client/interfaces.go | Extends InstanceServer interface with CreateClusterLinkPendingPublic. |
Add GetPublicClusterLinkConnectionArgs and update RefreshClusterLinkVolatileAddresses to branch on link type so public links present no client certificate. Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
… its certificate Add VerifyClusterLinkServer, which connects with the pinned certificate (no client certificate presented) and queries /1.0, and wire it into CheckClusterLinkCertificate so a matching-but-non-LXD endpoint is rejected. Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Public cluster links follow a two-phase creation lifecycle, matching bidirectional/unidirectional links: a pending-create phase and a confirm phase. A POST with a name and remote_address (no cluster_certificate) fetches the remote cluster's certificate, verifies it's actually serving the LXD API, and creates a pending link. The fetched certificate is held in volatile.pending_certificate config, not yet pinned, and volatile.addresses is not set, so the link stays inert until confirmed. It returns the fingerprint and PEM certificate for user verification. A second POST with the same name/remote_address plus the returned cluster_certificate pins the certificate for the first time and activates the link by setting volatile.addresses, after verifying the resubmitted certificate's fingerprint matches the one fetched during pending creation; a mismatch is rejected. The certificate is not re-fetched from the remote at this stage. Confirming a link that doesn't exist, isn't public, or has already been confirmed is also rejected. Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Public cluster links present no client certificate, so the remote cluster cannot authenticate the connection. Replication requires authenticated writes, so reject public links at config validation time rather than failing mid-operation with an opaque authentication error. Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
CreateClusterLink checks the cluster_links_public extension when the request is a public link, in addition to the base cluster_links check. CreateClusterLinkPendingPublic mirrors CreateIdentityClusterLinkToken's pattern for bidirectional/unidirectional pending links: it creates a pending public cluster link and returns the fetched certificate fingerprint/PEM for the caller to display for user verification before confirming via CreateClusterLink. Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Signed-off-by: Kadin Sayani <kadin.sayani@canonical.com>
Summary
Adds public cluster link support. A public cluster link lets one cluster (A) reach another cluster (B) without presenting a client certificate, relying solely on TLS certificate pinning to authenticate B. B is completely unaware of the link; no identity or link row is created on B's side. This is useful when B exposes resources publicly, or when anonymous-style read access to B is sufficient.
Depends on #18195.
API extension:
cluster_links_publicHow it works
Creating a public link is a two-phase
POST /1.0/cluster/linkson A:POSTwith a name,"type": "public"andremote_address(nocluster_certificate) creates a pending link. A fetches B's TLS certificate, verifies that the address actually serves the LXD API, and returns the certificate's fingerprint and PEM encoding (newClusterLinkCertificateresponse type) for the user to verify. The certificate is held involatile.pending_certificatebut is not yet pinned, so the link stays inert.POSTwith the same name andremote_address, this time including thecluster_certificatefrom step 1, pins the certificate and activates the link. The resubmitted certificate must match the one fetched in step 1; a mismatch is rejected and the link remains pending.The CLI performs both phases, prompting for confirmation of the fingerprint in between.
Example workflow