1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
//! The SESSCTL control-plane routes as JSON-RPC methods, and the caller-trust
//! token the #6197 guard travels on (#6288 slice 4).
//!
//! Why: `ctl_run_session` spawns a caller-named executable from a
//! caller-supplied request. #6197 was that it did so with no validation and no
//! proof the caller was local; PR #6205 (`a2c9d0f2e`) closed it with a caller
//! check and three input validators, all written as HTTP-extractor code. Moving
//! this family onto a second transport is exactly the change that could leave
//! those checks behind, so they were moved INTO the shared body first
//! (`control_routes::ctl_run_core`) and the caller check was given a type.
//!
//! What: [`CallerTrust`] is that type — an opaque newtype over a PRIVATE enum,
//! so a trusted verdict has no literal any caller can write. Its only two
//! constructors are `pub(crate)` and each belongs to one transport's guard:
//! `from_loopback_check` for `control_routes::loopback_guard`, and
//! `from_peer_checked_socket` for the socket's accept-time peer-uid check. The
//! `compile_fail` doctests on the type prove an outside caller cannot reach
//! either, nor name a variant, nor build one by hand.
//!
//! | Method | Route |
//! |---|---|
//! | `mpm.control.list` | `GET /api/v1/control/sessions` |
//! | `mpm.control.run` | `POST /api/v1/control/sessions/run` |
//! | `mpm.control.connect` | `POST /api/v1/control/sessions/{id}/connect` (SSE → stream) |
//! | `mpm.control.stop` | `POST /api/v1/control/sessions/{id}/stop` |
//! | `mpm.control.auth` | `GET /api/v1/control/sessions/{id}/auth` |
//!
//! `mpm.control.connect` is the one route whose HTTP form is an SSE stream. It
//! is registered with [`RpcRouter::typed_stream`], so a caller sends
//! `"stream": true` and reads one frame per `SessionEvent` — the same events,
//! in the same order, that the SSE `data:` lines carry.
//!
//! Test: `rpc_ctl_*` in `daemon::rpc::managed_tests`.
use Arc;
use Deserialize;
use mpsc;
use StreamExt;
use BroadcastStream;
use ;
use ;
use crateControlSessionId;
use crate;
use crateDaemonState;
/// Whether the transport proved the caller is entitled to the control plane
/// (#6197, carried across transports by #6288).
///
/// Why: the check is a property of the CONNECTION, not of the request body, so
/// it cannot live inside a route body that both transports share — but its
/// RESULT must, or the body has no way to refuse. This enum is that result, and
/// making it a parameter means a new caller of a control-plane body has to say
/// which transport vouched for it. There is no `Default`, deliberately.
///
/// **The socket's proof is strictly stronger than HTTP's.** `loopback_guard`
/// asks whether the peer address is loopback — whether the caller is on this
/// HOST. `trusty_common::uds::ensure_peer_is_self`, which `serve_until` runs on
/// every accepted connection before a byte is read, asks whether the peer's uid
/// is THIS USER'S. Same-uid is a strict subset of same-host, so every caller the
/// socket admits would also have passed `loopback_guard`, while a different
/// local user — admitted by `loopback_guard` — is refused by the socket. The
/// socket path is additionally `0600` inside a `0700` directory, so a foreign
/// uid cannot reach `connect(2)` at all. Nothing weaker is carried across.
///
/// Test: `caller_trust_refusal_matches_the_http_403`,
/// `rpc_ctl_run_refuses_an_untrusted_caller`.
/// The three `compile_fail` blocks below are the proof, and this one is what
/// keeps them honest: a `compile_fail` test passes for ANY compile error, a
/// mistyped import path included, so one block that MUST compile pins the path
/// they all use. If `CallerTrust` ever stops being reachable at this path, this
/// block fails and the three below stop proving anything silently.
///
/// ```
/// use trusty_mpm::daemon::rpc::managed::control::CallerTrust;
/// assert!(CallerTrust::REFUSAL.contains("loopback-only"));
/// ```
///
/// ```compile_fail
/// // #6288: the verdict has no public variant, so a caller cannot name one.
/// use trusty_mpm::daemon::rpc::managed::control::CallerTrust;
/// let _trusted = CallerTrust::LocalVerified;
/// ```
///
/// ```compile_fail
/// // #6288: nor build one by hand — the inner verdict is private.
/// use trusty_mpm::daemon::rpc::managed::control::CallerTrust;
/// let _trusted = CallerTrust(());
/// ```
///
/// ```compile_fail
/// // #6288: nor call a constructor — both are crate-internal.
/// use trusty_mpm::daemon::rpc::managed::control::CallerTrust;
/// let _trusted = CallerTrust::from_peer_checked_socket();
/// ```
;
/// The verdict [`CallerTrust`] wraps.
///
/// Private on purpose, and the whole mechanism behind the type's guarantee: a
/// newtype over a private type has no expressible literal outside this module,
/// so `CallerTrust`'s only constructors are the two below. Making this `pub`
/// would restore exactly the hole #6347's review found.
/// A control-plane request naming a session by id.
/// `mpm.control.stop` parameters: the id, plus the `?force=` query flag.
/// Register the five control-plane methods on `router`.
/// `mpm.control.connect` — the SSE route's socket form (#6288).
///
/// Why a stream and not a unary method: the HTTP route answers with an
/// open-ended `text/event-stream`, one `data:` line per `SessionEvent`. A unary
/// method could only return a snapshot, which is a different route, so this one
/// is registered with `typed_stream` and answers in many frames.
///
/// What: refuses an untrusted caller and an unknown id up front — before any
/// frame is written — then forwards each broadcast event as one stream item.
/// The write-lock CAS the HTTP route performs is NOT done here: the HTTP
/// handler's `WriteLockGuard` is owned by the SSE stream and released when that
/// stream drops, and this slice has no equivalent drop point on the socket. A
/// socket connect is therefore an OBSERVER, and `writer` is reported false.
/// That is a deliberate narrowing, not a parity gap to paper over — an RPC
/// caller that acquired the lock with no drop path would leak it permanently.
///
/// # Errors
///
/// [`CODE_NOT_FOUND`] for an unknown session; the shared 403 code for an
/// untrusted caller.
///
/// Test: `rpc_ctl_connect_streams_events`,
/// `rpc_ctl_connect_unknown_session_refuses_before_any_frame`.
async