microsandbox_network/
config.rs1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
7
8use ipnetwork::{Ipv4Network, Ipv6Network};
9use serde::{Deserialize, Serialize};
10
11use crate::dns::Nameserver;
12
13use crate::policy::NetworkPolicy;
14use crate::secrets::config::SecretsConfig;
15use crate::tls::TlsConfig;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct NetworkConfig {
29 #[serde(default = "default_true")]
31 pub enabled: bool,
32
33 #[serde(default)]
35 pub interface: InterfaceOverrides,
36
37 #[serde(default)]
39 pub ports: Vec<PublishedPort>,
40
41 #[serde(default)]
43 pub policy: NetworkPolicy,
44
45 #[serde(default)]
47 pub dns: DnsConfig,
48
49 #[serde(default)]
51 pub tls: TlsConfig,
52
53 #[serde(default)]
55 pub secrets: SecretsConfig,
56
57 #[serde(default)]
59 pub max_connections: Option<usize>,
60
61 #[serde(default)]
68 pub trust_host_cas: bool,
69}
70
71#[derive(Debug, Clone, Default, Serialize, Deserialize)]
75pub struct InterfaceOverrides {
76 #[serde(default)]
78 pub mac: Option<[u8; 6]>,
79
80 #[serde(default)]
82 pub mtu: Option<u16>,
83
84 #[serde(default)]
86 pub ipv4_address: Option<Ipv4Addr>,
87
88 #[serde(default)]
90 pub ipv4_pool: Option<Ipv4Network>,
91
92 #[serde(default)]
94 pub ipv6_address: Option<Ipv6Addr>,
95
96 #[serde(default)]
98 pub ipv6_pool: Option<Ipv6Network>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct DnsConfig {
104 #[serde(default = "default_true")]
106 pub rebind_protection: bool,
107
108 #[serde(default)]
115 pub nameservers: Vec<Nameserver>,
116
117 #[serde(default = "default_query_timeout_ms")]
119 pub query_timeout_ms: u64,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PublishedPort {
125 pub host_port: u16,
127
128 pub guest_port: u16,
130
131 #[serde(default)]
133 pub protocol: PortProtocol,
134
135 #[serde(default = "default_host_bind")]
137 pub host_bind: IpAddr,
138}
139
140#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
142pub enum PortProtocol {
143 #[default]
145 #[serde(rename = "tcp", alias = "Tcp")]
146 Tcp,
147
148 #[serde(rename = "udp", alias = "Udp")]
150 Udp,
151}
152
153impl Default for NetworkConfig {
158 fn default() -> Self {
159 Self {
160 enabled: true,
161 interface: InterfaceOverrides::default(),
162 ports: Vec::new(),
163 policy: NetworkPolicy::default(),
164 dns: DnsConfig::default(),
165 tls: TlsConfig::default(),
166 secrets: SecretsConfig::default(),
167 max_connections: None,
168 trust_host_cas: false,
169 }
170 }
171}
172
173impl Default for DnsConfig {
174 fn default() -> Self {
175 Self {
176 rebind_protection: true,
177 nameservers: Vec::new(),
178 query_timeout_ms: default_query_timeout_ms(),
179 }
180 }
181}
182
183fn default_true() -> bool {
188 true
189}
190
191fn default_host_bind() -> IpAddr {
192 IpAddr::V4(Ipv4Addr::LOCALHOST)
193}
194
195fn default_query_timeout_ms() -> u64 {
196 5000
197}
198
199#[cfg(test)]
204mod tests {
205 use super::{InterfaceOverrides, NetworkConfig, PortProtocol};
206 use crate::dns::Nameserver;
207 use crate::policy::{Destination, NetworkPolicy, Rule};
208
209 #[test]
214 fn engine_network_subdocs_round_trip_through_wire_types() {
215 let mut config = NetworkConfig::default();
216 let mut policy = NetworkPolicy::public_only()
220 .allow_domain("example.com")
221 .expect("valid domain")
222 .allow_domain_suffix("staging.example.com")
223 .expect("valid suffix");
224 policy.rules.push(Rule::allow_egress(Destination::Cidr(
225 "151.101.0.0/16".parse().unwrap(),
226 )));
227 config.policy = policy;
228 config.dns.nameservers = vec![
229 "1.1.1.1:53".parse::<Nameserver>().unwrap(),
230 "dns.google".parse::<Nameserver>().unwrap(),
231 ];
232 config.interface.ipv4_address = Some("172.16.0.2".parse().unwrap());
233 config.interface.ipv4_pool = Some("172.16.0.0/12".parse().unwrap());
234 config.interface.mac = Some([0x02, 0, 0, 0, 0, 0x01]);
235
236 let policy_json = serde_json::to_value(&config.policy).unwrap();
238 let dns_json = serde_json::to_value(&config.dns).unwrap();
239 let iface_json = serde_json::to_value(&config.interface).unwrap();
240
241 let wire_policy: microsandbox_types::NetworkPolicy =
244 serde_json::from_value(policy_json.clone()).unwrap();
245 let wire_dns: microsandbox_types::DnsConfig =
246 serde_json::from_value(dns_json.clone()).unwrap();
247 assert_eq!(policy_json, serde_json::to_value(&wire_policy).unwrap());
248 assert_eq!(dns_json, serde_json::to_value(&wire_dns).unwrap());
249
250 let wire_iface: microsandbox_types::InterfaceOverrides =
253 serde_json::from_value(iface_json.clone()).unwrap();
254 let back: InterfaceOverrides =
255 serde_json::from_value(serde_json::to_value(&wire_iface).unwrap()).unwrap();
256 assert_eq!(iface_json, serde_json::to_value(&back).unwrap());
257
258 assert_eq!(
261 serde_json::to_string(&Destination::DomainSuffix(
262 "staging.example.com".parse().unwrap()
263 ))
264 .unwrap(),
265 r#"{"domain_suffix":"staging.example.com"}"#
266 );
267 let legacy: microsandbox_types::Destination =
268 serde_json::from_str(r#"{"domain-suffix":"old.example.com"}"#).unwrap();
269 assert!(matches!(
270 legacy,
271 microsandbox_types::Destination::DomainSuffix(_)
272 ));
273 let legacy_group: microsandbox_types::DestinationGroup =
274 serde_json::from_str(r#""link-local""#).unwrap();
275 assert_eq!(
276 legacy_group,
277 microsandbox_types::DestinationGroup::LinkLocal
278 );
279 }
280
281 #[test]
282 fn port_protocol_serializes_lowercase_and_accepts_legacy_case() {
283 assert_eq!(
284 serde_json::to_string(&PortProtocol::Tcp).unwrap(),
285 "\"tcp\""
286 );
287 assert_eq!(
288 serde_json::to_string(&PortProtocol::Udp).unwrap(),
289 "\"udp\""
290 );
291 assert_eq!(
292 serde_json::from_str::<PortProtocol>("\"Tcp\"").unwrap(),
293 PortProtocol::Tcp
294 );
295 assert_eq!(
296 serde_json::from_str::<PortProtocol>("\"Udp\"").unwrap(),
297 PortProtocol::Udp
298 );
299 }
300}