You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ef26df3
Browse filesBrowse the repository at this point in the historyBrowse files
feat(adopt): confirm the installed node's fingerprint before trusting it
After a node installs and reboots during adoption it presents a new
management certificate that nothing links to the maintenance certificate
the operator confirmed. Adoption no longer pins it on first use. It
captures the certificate, streams the awaiting-fingerprint-confirmation
phase with its SHA-256 and the adoption ID, and waits for
ConfirmAdoptionFingerprint.
A matching fingerprint (case, colons and spaces ignored) resumes the
adoption, and only then is the certificate pinned and the node dialed
against it. A mismatch, no confirmation within 15 minutes, or the client
going away fails the adoption with nothing pinned, recorded or
registered. Confirmations and refusals are audited with the operator.
The new RPC is excluded from the MCP catalog.
Closes#144
Refs #135
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@
18
18
19
19
A node is linked to the FM via **mutual consent**: the operator declares the FM's URL + trust anchor in the node's machine config and reboots; the node calls the FM's `EnrollNode` with a TPM EK attestation; the FM operator approves in the UI; the FM issues the node a 90-day peer cert (EKU `clientAuth` only). Either side can revoke or unlink.
20
20
21
-
Adopting a fresh node from its maintenance endpoint is safe to retry. The manager mints a bootstrap admin credential per node name and stores it under `MANAGER_NODE_CREDS_DIR` (default `/var/lib/cryptos-manager/node-creds`). A retry reuses it, because a node that got as far as installing trusts only that credential. When the node reports it has already booted its installed system, the retry skips the install, runs the first-boot ceremony only if a Root has not finished it, and registers the node. If the stored credential is gone, the node refuses the handshake and the adoption fails with a hint to reset the node from its console and adopt it again.
21
+
Adopting a fresh node from its maintenance endpoint is safe to retry. The manager mints a bootstrap admin credential per node name and stores it under `MANAGER_NODE_CREDS_DIR` (default `/var/lib/cryptos-manager/node-creds`). A retry reuses it, because a node that got as far as installing trusts only that credential. When the node reports it has already booted its installed system, the retry skips the install, runs the first-boot ceremony only if a Root has not finished it, and registers the node. If the stored credential is gone, the node refuses the handshake and the adoption fails with a hint to reset the node from its console and adopt it again. After the node reboots into its installed system, the adoption pauses until an admin confirms the certificate fingerprint it presents against the `Mgmt SHA-256` line on its console; see [docs/node-trust.md](docs/node-trust.md#-what-adoption-records).
22
22
23
23
The node always checks the manager's admin credential, and the manager always checks the node's server certificate: against the node's recorded CA chain once the node signs its management certificate with its CA, or against a pinned `server.crt` before that. A node it can't verify is refused. `manager -check-node-trust` lists how each node is verified. See [`docs/node-trust.md`](docs/node-trust.md).
Copy file name to clipboardExpand all lines: docs/node-trust.md
+36-11Lines changed: 36 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -102,28 +102,53 @@ Adoption records what the manager needs to verify the node afterwards:
102
102
103
103
1. The fingerprint you confirm in the adopt wizard pins the node's
104
104
maintenance-mode certificate for the install steps.
105
-
2. The installed node boots with a new management certificate. When it comes
106
-
back, the manager saves that certificate as the node's `server.crt` and
107
-
shows its fingerprint in the adoption progress:
105
+
2. The installed node boots with a new management certificate. Nothing links
106
+
it to the maintenance certificate you confirmed, so adoption pauses on the
107
+
`awaiting-fingerprint-confirmation` phase and shows the fingerprint the
108
+
node presents:
108
109
109
110
```text
110
-
node is back in running mode; pinned its management certificate sha256 5a0e...c3. Check it against the Mgmt SHA-256 line on the node's console
111
+
the node is back in running mode and presents certificate sha256 5a0e...c3. Compare it with the Mgmt SHA-256 line on the node's console and confirm it to continue
111
112
```
112
113
113
114
> [!CAUTION]
114
-
> This pin is trust on first use: nothing links the installed node's new
115
-
> certificate to the maintenance certificate you confirmed. Compare the
116
-
> fingerprint with the `Mgmt SHA-256` line on the node's console. If they
117
-
> differ, something other than your node answered on its address; stop and
118
-
> find out what before you use the node.
115
+
> Compare the fingerprint with the `Mgmt SHA-256` line on the node's
116
+
> console before you confirm it. If they differ, something other than your
117
+
> node answered on its address: choose "Does not match" in the wizard and
118
+
> find out what answered before you adopt again.
119
+
120
+
Confirming (the adopt wizard, or `ConfirmAdoptionFingerprint` with the
121
+
stream's `adoption_id`) resumes the adoption. Only then does the manager
122
+
save that certificate as the node's `server.crt` and dial the node,
123
+
verified against it. The comparison ignores case, colons and spaces.
124
+
125
+
The adoption stops, and nothing is pinned, recorded or registered, when:
126
+
127
+
- the confirmed fingerprint differs from the presented one (the call and
128
+
the adoption fail with `InvalidArgument`);
129
+
- nobody confirms within 15 minutes (`DeadlineExceeded`);
130
+
- the wizard cancels or the adoption stream is closed.
131
+
132
+
The manager admin credential minted for the adoption stays, so adopting
133
+
the node again resumes from here. Both a confirmation and a refused
134
+
fingerprint are audited (`node-adoption-fingerprint-confirmed`,
135
+
`node-adoption-fingerprint-rejected`) with the operator who sent it.
136
+
137
+
> [!WARNING]
138
+
> The waiting adoption lives in the manager replica that runs the adoption
139
+
> stream. With `replicaCount` above 1, a confirmation that reaches another
140
+
> replica returns `NotFound`; send it again so it reaches the replica that
141
+
> holds the stream, or adopt with a single replica.
119
142
120
143
3. A root's ceremony gives it its CA. The manager reads the CA chain from the
121
144
node and saves it as `ca.crt` next to the admin certificate. An intermediate
122
145
or issuing node gets its CA when its subordinate enrollment is approved, and
123
146
the manager saves the signed chain the same way.
124
147
125
-
A retried adoption replaces the node's old `server.crt` with the certificate
126
-
the re-installed node presents.
148
+
A retried adoption asks you to confirm the certificate the node presents
149
+
again, then replaces the node's old `server.crt` with it. A root that has
150
+
already run its ceremony presents a CA-signed certificate by then; confirm that
"node is back in running mode; pinned its management certificate sha256 %s. Check it against the Mgmt SHA-256 line on the node's console", pinnedSHA256), false); err!=nil {
// A lazy gRPC client dial can succeed before the server is up;
394
+
// confirm the node answers a cheap RPC before proceeding.
395
+
if_, serr:=conn.GetStatus(ctx); serr==nil {
396
+
returnconn, nil
397
+
}
398
+
_=conn.Close()
399
+
}
400
+
iftime.Now().After(deadline) {
401
+
returnnil, fmt.Errorf("fleet: node did not come back in running mode on %s within %s of install", node.Endpoint, rebootWait)
402
+
}
403
+
select {
404
+
case<-ctx.Done():
405
+
returnnil, ctx.Err()
406
+
case<-time.After(rebootPollGap):
407
+
}
395
408
}
396
-
sum:=sha256.Sum256(cert.Raw)
397
-
fp:=hex.EncodeToString(sum[:])
398
-
log.Printf("fleet: adopt %s: pinned the running node's management certificate sha256 %s at %s (trust on first use; check it against the node's console)", node.Name, fp, path)
399
-
returnfp, nil
400
409
}
401
410
402
411
// adoptFail streams a terminal error phase and returns the Connect error, so a
@@ -407,6 +416,17 @@ func (s *Service) adoptFail(send phaseSink, code connect.Code, cause error) erro
407
416
returnconnect.NewError(code, cause)
408
417
}
409
418
419
+
// adoptFailErr is adoptFail for an error that already carries its Connect
0 commit comments