Skip to main content

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(&region);
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(&region);
176                assert!((1..=2048).contains(&arn.len()), "{arn}");
177                assert!(arn.starts_with("arn:aws:lambda:"), "{arn}");
178            }
179        }
180    }
181}