microvms_core/control/connector.rs
1// SPDX-License-Identifier: Apache-2.0
2//! Network connectors: a closed intent enum, and the ARN each one derives (TRAP-4).
3//!
4//! # Why an enum rather than a string
5//!
6//! Two reasons, and both are measurements.
7//!
8//! The API takes a **fully-qualified ARN** and rejects the bare name with "Malformed
9//! network connector ARN" (measured 2026-08-05). `NetworkConnector` in the service model
10//! is just `{type: string, max: 2048, min: 1}` — no pattern, no enum — so a free-form
11//! parameter passes every check the model states and fails on the wire, and the value
12//! that reads most natural to write (`"ALL_INGRESS"`) is exactly the one that fails.
13//! Deriving the ARN from an intent means the caller states what they want and the
14//! spelling is not theirs to get wrong.
15//!
16//! # TRAP-11: what is deliberately absent
17//!
18//! `SHELL_INGRESS` exists in the API and is **not** a variant here. It gates
19//! `CreateMicrovmShellAuthToken`, whose documented flow is `ctr task exec` through a
20//! console terminal — scoped to debugging, and recommended disabled in production. It is
21//! not a programmatic exec path despite the name, and this client's whole reason to
22//! exist is that no such path exists.
23//!
24//! Leaving it out of the enum is what makes requesting it **unwriteable** rather than
25//! merely discouraged: there is no `ConnectorIntent` value that renders it, and
26//! [`ConnectorIntent::ALL`] is the complete set a test can enumerate. The sibling half
27//! of TRAP-11 — never calling the shell-auth operation — is closed the same way, by the
28//! absence of a method on [`crate::control::ControlPlane`]; see that module's docs.
29
30use crate::region::Region;
31
32/// The Lambda-managed connectors this client will name, and no others.
33///
34/// Named for the *intent* rather than for the wire value, because the intent is what a
35/// caller has: "let the proxy reach the VM" and "let the VM reach the internet". The
36/// wire spellings ([`ConnectorIntent::wire_name`]) are an implementation detail of the
37/// ARN.
38#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
39pub enum ConnectorIntent {
40 /// Lets the endpoint proxy reach the VM. Required for any session to work.
41 AllIngress,
42 /// Lets the VM reach the internet.
43 ///
44 /// Omitted by default, which is how you get a VM with no outbound network — the
45 /// right default for a daemon that needs none.
46 Egress,
47}
48
49impl ConnectorIntent {
50 /// Both intents, so a test can enumerate the complete set.
51 ///
52 /// The TRAP-11 assertion reads this: a `SHELL_INGRESS` variant added later would
53 /// appear here and fail the test that pins the set to two.
54 pub const ALL: [ConnectorIntent; 2] = [ConnectorIntent::AllIngress, ConnectorIntent::Egress];
55
56 /// The connector's name as the ARN spells it.
57 ///
58 /// `INTERNET_EGRESS` rather than `EGRESS`: the wire name is not the intent's name,
59 /// which is the second reason this is a table rather than a `Display` derive.
60 pub fn wire_name(self) -> &'static str {
61 match self {
62 ConnectorIntent::AllIngress => "ALL_INGRESS",
63 ConnectorIntent::Egress => "INTERNET_EGRESS",
64 }
65 }
66
67 /// The fully-qualified ARN for this connector in `region`.
68 ///
69 /// One interpolation for both directions. The Python client's earlier shape derived
70 /// the egress ARN by string-replacing `ALL_INGRESS` inside the ingress one, which
71 /// produced a valid ARN only as long as the two names never became substrings of
72 /// each other (`sandbox.py:391-398`).
73 ///
74 /// The doubled `aws-network-connector` segment is not a typo: the resource type and
75 /// the resource name are both present, and the format is copied from
76 /// `sandbox.py:398` rather than reconstructed from the ARN grammar.
77 pub fn arn(self, region: &Region) -> String {
78 format!(
79 "arn:aws:lambda:{}:aws:network-connector:aws-network-connector:{}",
80 region.as_str(),
81 self.wire_name()
82 )
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 /// The exact ARN, as a literal, for the region the measurements were taken in. The
91 /// format came from a measurement rather than from the ARN grammar, so a
92 /// reconstruction of it in the test would agree with a reconstruction in the code.
93 #[test]
94 fn the_ingress_arn_is_the_measured_literal() {
95 assert_eq!(
96 ConnectorIntent::AllIngress.arn(&Region::UsEast1),
97 "arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:ALL_INGRESS"
98 );
99 }
100
101 /// The egress ARN, likewise — and note the wire name is `INTERNET_EGRESS` while the
102 /// variant is `Egress`, which is the thing a `Display` derive would have got wrong.
103 #[test]
104 fn the_egress_arn_uses_the_internet_egress_wire_name() {
105 assert_eq!(
106 ConnectorIntent::Egress.arn(&Region::UsEast1),
107 "arn:aws:lambda:us-east-1:aws:network-connector:aws-network-connector:INTERNET_EGRESS"
108 );
109 }
110
111 /// The region is interpolated rather than fixed, for every region including the
112 /// escape hatch. TRAP-4 is "for the request region", and a hardcoded `us-east-1`
113 /// would pass both tests above.
114 #[test]
115 fn the_arn_carries_the_request_region_for_every_region() {
116 for region in crate::region::MICROVM_REGIONS {
117 let arn = ConnectorIntent::AllIngress.arn(®ion);
118 assert!(
119 arn.contains(&format!(":lambda:{}:aws:", region.as_str())),
120 "{arn}"
121 );
122 }
123 let unlisted = Region::unlisted("me-south-1");
124 assert!(
125 ConnectorIntent::Egress
126 .arn(&unlisted)
127 .contains(":lambda:me-south-1:aws:"),
128 "the escape hatch still derives an ARN for its own region"
129 );
130 }
131
132 /// TRAP-11, the absence half. Two intents, neither of them shell — and the check is
133 /// on the rendered ARNs as well as on the variant count, because a variant named
134 /// something else could still render `SHELL_INGRESS`.
135 ///
136 /// **Falsification** — add `ConnectorIntent::ShellIngress` with wire name
137 /// `SHELL_INGRESS` and this test fails on both assertions.
138 #[test]
139 fn no_intent_names_shell_ingress() {
140 assert_eq!(
141 ConnectorIntent::ALL.len(),
142 2,
143 "two intents, and shell is not one"
144 );
145 for intent in ConnectorIntent::ALL {
146 let arn = intent.arn(&Region::UsEast1);
147 assert!(
148 !arn.contains("SHELL"),
149 "SHELL_INGRESS gates a debug console, not programmatic exec: {arn}"
150 );
151 assert!(!intent.wire_name().contains("SHELL"));
152 }
153 }
154
155 /// The two intents render different ARNs. The string-replace shape this replaced
156 /// could produce two identical ARNs if the names ever became substrings of one
157 /// another, and that is the failure this asserts against.
158 #[test]
159 fn the_two_intents_do_not_render_the_same_arn() {
160 let ingress = ConnectorIntent::AllIngress.arn(&Region::EuWest1);
161 let egress = ConnectorIntent::Egress.arn(&Region::EuWest1);
162 assert_ne!(ingress, egress);
163 assert!(!ingress.contains("INTERNET"), "{ingress}");
164 }
165
166 /// Every derived ARN clears the model's `NetworkConnector` bounds (min 1, max 2048),
167 /// including for the longest region name. The model states no pattern, so length is
168 /// the only constraint there is to check — and it is checked here rather than
169 /// trusted because a 2048-character ARN would be rejected on the wire with the same
170 /// "malformed" message a bare name gets.
171 #[test]
172 fn every_derived_arn_fits_the_models_connector_bounds() {
173 for region in crate::region::MICROVM_REGIONS {
174 for intent in ConnectorIntent::ALL {
175 let arn = intent.arn(®ion);
176 assert!((1..=2048).contains(&arn.len()), "{arn}");
177 assert!(arn.starts_with("arn:aws:lambda:"), "{arn}");
178 }
179 }
180 }
181}