Repository navigation
Conversation
This continues the integration of the [Support Shell](https://github.com/oxidecomputer/sush) ([RFD 620](https://rfd.shared.oxide.computer/rfd/0620)), building on the sled-agent embedding: a new switch zone service, `sush-proxy`, terminates technician-port connections and routes each request to a sled's sush server. The proxy finds sush servers by probing the bootstrap and underlay prefixes given by DDM, and polls MGS for the cubby map so a request may name its target sled by cubby. **For security review:** On real hardware the proxy serves TLS backed by the sled's platform identity. At zone startup, sled-agent generates an ephemeral key and has the RoT sign its certificate with the TQ key (Ed25519 over the SHA3-256 digest of the TBS certificate), and the sush client verifies that the chain ends at a platform identity root. Simulated and emulated environments have no RoT, so their proxies serve plaintext. Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
e7b2c72 to
01f98ba
Compare
0ce2c9d to
7dc9f6d
Compare
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
|
A heads-up: @rmustacc identified a very serious problem in the security story here, which is that we are forbidden by policy (which we're currently ignoring) from signing certificates with the platform id leaf certificate. I will be sketching a work-around tomorrow (Wed, Sep 9), and it will have some knock-on effects (mostly positive, I think). |
|
Heads-up follow-up: I did not get to implement the previously mentioned work-around today (Wed), but I did document it in the transport security section of RFD 620. Feedback would be welcome, and I'll try again to prototype it tomorrow (Thu) based on the sketch there. |
…h-proxy Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com> # Conflicts: # workspace-hack/Cargo.toml
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com> # Conflicts: # workspace-hack/Cargo.toml
|
Follow-up follow-up: the "voucher" workaround is now implemented in oxidecomputer/sush#86. New commits will be pushed here for the Omicron side. |
The proxy's ephemeral cert is now self-signed which carries the RoT's voucher for its key (a Trust Quorum signature over the tagged SPKI digest; see RFD 620 §4.6.2.1), rather than claiming tq-dhe as its issuer (which violates critical constraints). The TLS mode is renamed `Platform` → `Vouched` to match. Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
internet-diglett
left a comment
There was a problem hiding this comment.
This looks good to me from the networking and switch zone perspective!
jmpesp
left a comment
There was a problem hiding this comment.
I haven't had a chance to review sush_server::ProxyServer yet, but here's a first round of feedback
| .add_property( | ||
| "address", | ||
| "astring", | ||
| &format!("[::]:{SUSH_PROXY_PORT}"), |
There was a problem hiding this comment.
Do we want to restrict this at all?
There was a problem hiding this comment.
Assuming "this" is [::], no, because we need to listen on both the tech port and underlay addresses (for the Nexus tunnel) as they become available. Restriction here would mean plumbing addresses up dynamically for no real win that I can see.
| const SUSH_PROXY_CERT_VALIDITY: Duration = | ||
| Duration::from_secs(365 * 24 * 60 * 60); |
There was a problem hiding this comment.
This is regenerated every sled-agent restart?
There was a problem hiding this comment.
Every switch zone startup, which includes sled-agent restarts, yes.
| } | ||
| } | ||
| } | ||
| targets.send_modify(|t| t.cubbies.extend(cubbies)); |
There was a problem hiding this comment.
The same comment from the previous PR applies here, w.r.t clobbering the map.
There was a problem hiding this comment.
This map has different semantics from the one in '52. There, each round is one complete response from MGS; here, each round polls every SP separately, and we don't want to drop a cubby's entry just because it missed a round. Eventually we'll want to have some kind of stale-entry expiration, but a sled not in this map can't accept jobs targeted by cubby, so we'd like to keep the entries even for a flaky sled (or while it's rebooting, or whatever).
There was a problem hiding this comment.
we don't want to drop a cubby's entry just because it missed a round
I think we do, for the same reason as the other PR: moving sleds around in a not-fully-populated rack would mean the sled would appear in multiple different cubbies.
There was a problem hiding this comment.
Quite right, thank you. c2ddf78 takes a middle path: missed rounds still keep their entries (I do think the reboot/flaky case is important here), but moved sleds are evicted from their old cubbies. A removed sled still appears in the map, but is harmless; if it isn't there, jobs will fail at forwarding time, and we can still work out an expiration scheme later.
| warn!(log, "unable to fetch underlay prefixes"; "error" => %err) | ||
| } | ||
| } | ||
| targets.send_modify(|t| t.sleds.extend(sleds)); |
There was a problem hiding this comment.
Same situation as above, I think merging into the map is correct here.
| /// Path inside the switch zone to the sush proxy's TLS private key. | ||
| pub const SUSH_PROXY_KEY_PATH: &str = "/etc/sush-proxy/key.pem"; | ||
|
|
||
| /// Path inside the switch zone to the sush proxy's TLS certificate chain. | ||
| pub const SUSH_PROXY_CERT_CHAIN_PATH: &str = "/etc/sush-proxy/chain.pem"; |
There was a problem hiding this comment.
what, if anything, can or should reasonably done to restrict the accessibility of these paths to to processes other than the sush proxy?
EDIT: oh, cool, I see that we have a write_private function which ensures the key has the correct permissions. maybe worth noting that in the comment?
| .create(true) | ||
| .write(true) | ||
| .truncate(true) | ||
| .mode(0o600) |
There was a problem hiding this comment.
is this sufficient to ensure that only sush-proxy can read the file? what user do other switch zone processes run as? and all SSH sessions presently log in as root, right?
There was a problem hiding this comment.
As far as I can tell, everything in the switch zone currently runs as root, yes. You are correct that this proxy could run as a dedicated non-privileged user, and probably drop other capabilities and permissions. I would be happy to take that as a follow-up if you think it's worth it (it may be, I'm not sure). But stealing this key would only let an attacker impersonate a proxy; it would not grant any access to run or examine jobs.
There was a problem hiding this comment.
Yeah, for what it's worth, I totally agree that not having everything run as root is very much out of scope for this PR. But, it feels like something we should be thinking about separately. My point was more that the fact that everything, including SSH sessions, runs as root means that restricting who can access the file is currently mostly ceremonial, since basically every process will be running as root anyway. I agree that it's better than not doing it though!
| /// A vouched proxy identity: an ephemeral key that sled-agent | ||
| /// generates and the RoT vouches for, served from local files | ||
| /// along with the platform identity chain that validates it. |
| tokio::join!(sleds(log, ddm, &tx_targets), cubbies(log, mgs, &tx_targets),); | ||
| unreachable!("discovery loops never return"); |
There was a problem hiding this comment.
if they never return, why bother joining them? perhaps they should be spawned instead?
There was a problem hiding this comment.
(what i am actually saying here is in part that i am a bit concerned about these running in the same task rather than in true parallelism, especially since they both contend the watch channel sender which is morally equivalent to a mutex)
There was a problem hiding this comment.
in fact, upon thinking through this further, i don't really get why Targets must be a single watch channel, given that there are two distinct senders which both only touch half of it. It seems like Targets should really be internally a watch channel of cubbies and separately a watch channel of sleds, and then consumers would select either of them having changed
There was a problem hiding this comment.
Ok, in order: we join so that panics propagate, kill the proxy, and SMF will restart it. The alternative is to continue serving with stale routes, which could wedge it closed.
On contention: yes, it's morally a mutex, but the write locks are once per 30-second poll round, and the reads are one borrow per request, which consults both maps. I don't think splitting it would really improve much, though we certainly can if you feel strongly about it.
On concurrency: there's no real work happening in either loop, they're just waiting for timeouts and I/O.
There was a problem hiding this comment.
Ok, in order: we
joinso that panics propagate, kill the proxy, and SMF will restart it. The alternative is to continue serving with stale routes, which could wedge it closed.
Are we building with panic = "abort"? If so, this shouldn't be necessary.
On contention: yes, it's morally a mutex, but the write locks are once per 30-second poll round, and the reads are one borrow per request, which consults both maps. I don't think splitting it would really improve much, though we certainly can if you feel strongly about it.
On concurrency: there's no real work happening in either loop, they're just waiting for timeouts and I/O.
My concern here is just that having two separate concurrent futures in the same task which may both try to write lock the same mutex feels sketchy. I agree that there is no risk of deadlock in the current code, but if some future thing comes along and makes one of the poll loops also try to read from the watch channel, it could become a problem. I don't feel that strongly about it, it's just mildly concerning for the future.
There was a problem hiding this comment.
Ok, you've convinced me, thanks for hanging in there, and for the reminder about panic behavior. I believe that d615d83 should do what you're looking for: it spawns two independent tasks, one for each discovery loop, and bails if either ever returns. Depends on sush e96e719.
There was a problem hiding this comment.
Thanks, this feels neater. Again, I wasn't particularly concerned about a bug in the previous code, per se, but that it might be a bit too easy to introduce one if it grew more complex later. The new version makes a lot of those problems impossible.
Co-authored-by: Eliza Weisman <eliza@elizas.website> Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-authored-by: Eliza Weisman <eliza@elizas.website> Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-authored-by: Eliza Weisman <eliza@elizas.website> Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-authored-by: Eliza Weisman <eliza@elizas.website> Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-authored-by: Eliza Weisman <eliza@elizas.website> Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
26edb6a to
ce874f4
Compare
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
33fa5ce to
71a9dc8
Compare
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
d650a9a to
d615d83
Compare
Co-Authored-By: Claude Mythos 5 <noreply@anthropic.com>
Move the proxy onto sush's own sush-proxy crate, renaming this package to omicron-sush-proxy to avoid the clash. Co-authored-by: Claude <noreply@anthropic.com>
Polling all 32 cubbies is slow (minutes), especially when most of them are empty (as on a racklette), because MGS takes seconds to give up on each empty cubby. So instead, use the strategy that other inventory collectors use: ask MGS for the ignition list, and only ask about cubbies where ignition reports a running sled. Merge each round into the cubby map, dropping cubbies that ignition reports empty. sled-agent now looks for MGS at every candidate address concurrently, since requests to an address with no MGS hang until they time out. Also share the polling code between the proxy and sled-agent, which is why the API dependencies changed. And clean up and simplify helpers, and don't panic on HTTP client errors in the proxy. Co-authored-by: Claude <noreply@anthropic.com>
Bring in the production and lab job signing keys, replacing the sandbox keys. Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
Pick up main's move to SP release 1.86.0, which fixes the Cosmo host flash fault. Co-authored-by: Claude <noreply@anthropic.com>
Move sush-proxy to the same sush main commit as the other sush crates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Claude <noreply@anthropic.com>
| ) | ||
| }) | ||
| .collect(); | ||
| let mut polls: FuturesUnordered<_> = mgs_clients |
There was a problem hiding this comment.
I'd use ParallelTaskSet here, FuturesUnordered does not execute in parallel.
There was a problem hiding this comment.
Thanks for the suggestion, I hadn't seen ParallelTaskSet before. But I think FuturesUnordered is actually the right choice here. First, these are just network requests, so concurrency is what matters, not parallelism. Second, ParallelTaskSet seems designed to cap the number of tasks running at once; but launching these all at once is essential, because a request to a sled without a switch zone hangs until it times out, and requests to the actual scrimlets could get stuck behind those. Finally, it would need 'static futures, which I prefer to avoid if possible.
| "no SP state for cubby"; | ||
| "cubby" => cubby, | ||
| "error" => %err, | ||
| ); |
There was a problem hiding this comment.
What about a third state, like SledState::Off ?
There was a problem hiding this comment.
Otherwise this sled would simply be missing
There was a problem hiding this comment.
If the sled is powered off via ignition, its SP can't give us its baseboard ID, so an Off state couldn't add any entries to the cubby map. All it could do is distinguish a sled that ignition has powered off from one whose SP isn't answering (which is usually a transient condition, like an SP reset). Either way, the sled isn't reachable, so the client would report the same error.
Co-authored-by: Claude <noreply@anthropic.com> # Conflicts: # Cargo.toml
Continues the integration of the Support Shell (RFD 620), building on the sled-agent embedding: a new switch zone service,
sush-proxy, terminates technician-port connections and routes each request to a sled's sush server. The proxy finds sush servers by probing the bootstrap and underlay prefixes given by DDM, and polls MGS for the cubby map so a request may name its target sled by cubby.For security review:
On real hardware the proxy serves TLS backed by the sled's platform identity. At zone startup, sled-agent generates an ephemeral key and has the RoT sign its certificate with the TQ key (Ed25519 over the SHA3-256 digest of the TBS certificate), and the sush client verifies that the chain ends at a platform identity root. Simulated and emulated environments have no RoT, so their proxies serve plaintext. This is described in the transport security section of RFD 620.