Reserve an explicit Lazaret network topology
Launch slirp4netns on 10.137.54.0/24, update its gateway and DNS routes, and refuse configured allows or specific denies that collide with the enclosing Lazaret /23 while preserving broad RFC 1918 denial.
This commit is contained in:
parent
73e3e9fdee
commit
1c86e1aa0e
5 changed files with 87 additions and 7 deletions
|
|
@ -384,6 +384,36 @@ pub(super) fn resolve_network(
|
||||||
configuration: &TrustedHostConfigurationV1,
|
configuration: &TrustedHostConfigurationV1,
|
||||||
selected: &ProfileConfigurationV1,
|
selected: &ProfileConfigurationV1,
|
||||||
) -> Result<(Vec<Ipv4Prefix>, Vec<Ipv4Prefix>), ConfigurationError> {
|
) -> Result<(Vec<Ipv4Prefix>, Vec<Ipv4Prefix>), ConfigurationError> {
|
||||||
|
let reserved = parse_ipv4_prefix("10.137.54.0/23")?;
|
||||||
|
for declared in configuration
|
||||||
|
.network()
|
||||||
|
.default_allow
|
||||||
|
.iter()
|
||||||
|
.chain(&selected.allow_egress)
|
||||||
|
{
|
||||||
|
if ipv4_prefixes_overlap(parse_ipv4_prefix(declared)?, reserved) {
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"configured allow overlaps Lazaret's reserved topology",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for declared in configuration
|
||||||
|
.network()
|
||||||
|
.default_deny
|
||||||
|
.iter()
|
||||||
|
.chain(&selected.deny_egress)
|
||||||
|
{
|
||||||
|
let denied = parse_ipv4_prefix(declared)?;
|
||||||
|
if denied.prefix_length() >= reserved.prefix_length()
|
||||||
|
&& ipv4_prefixes_overlap(denied, reserved)
|
||||||
|
{
|
||||||
|
return Err(ConfigurationError::new(
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
"configured deny conflicts with Lazaret's reserved topology",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
let mut allow = BTreeSet::new();
|
let mut allow = BTreeSet::new();
|
||||||
let mut deny = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
|
let mut deny = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
@ -414,6 +444,16 @@ pub(super) fn resolve_network(
|
||||||
Ok((allow.into_iter().collect(), deny.into_iter().collect()))
|
Ok((allow.into_iter().collect(), deny.into_iter().collect()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ipv4_prefixes_overlap(left: Ipv4Prefix, right: Ipv4Prefix) -> bool {
|
||||||
|
let prefix_length = left.prefix_length().min(right.prefix_length());
|
||||||
|
let mask = if prefix_length == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
u32::MAX << (32 - prefix_length)
|
||||||
|
};
|
||||||
|
u32::from_be_bytes(left.address()) & mask == u32::from_be_bytes(right.address()) & mask
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_READINESS_TARGETS: usize = 32;
|
const MAX_READINESS_TARGETS: usize = 32;
|
||||||
pub(super) fn resolve_network_readiness(
|
pub(super) fn resolve_network_readiness(
|
||||||
readiness: &NetworkReadinessConfigurationV1,
|
readiness: &NetworkReadinessConfigurationV1,
|
||||||
|
|
|
||||||
|
|
@ -535,6 +535,20 @@ fn path_network_environment_and_volume_conflicts_refuse_loading() {
|
||||||
),
|
),
|
||||||
ConfigurationErrorKind::InvalidNetwork,
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
base.replace(
|
||||||
|
"allow_egress = [\"10.1.2.3/24\"]",
|
||||||
|
"allow_egress = [\"10.137.55.2/32\"]",
|
||||||
|
),
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
base.replace(
|
||||||
|
"allow_egress = [\"10.1.2.3/24\"]",
|
||||||
|
"allow_egress = [\"10.1.2.3/24\"]\ndeny_egress = [\"10.137.54.3/32\"]",
|
||||||
|
),
|
||||||
|
ConfigurationErrorKind::InvalidNetwork,
|
||||||
|
),
|
||||||
(
|
(
|
||||||
base.replace("/root/.local/state/agent", "/root"),
|
base.replace("/root/.local/state/agent", "/root"),
|
||||||
ConfigurationErrorKind::InvalidVolume,
|
ConfigurationErrorKind::InvalidVolume,
|
||||||
|
|
@ -583,6 +597,19 @@ fn path_network_environment_and_volume_conflicts_refuse_loading() {
|
||||||
assert_eq!(error.kind(), ConfigurationErrorKind::MissingEnvironment);
|
assert_eq!(error.kind(), ConfigurationErrorKind::MissingEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn broad_private_deny_can_contain_the_reserved_topology() {
|
||||||
|
let tree = TestTree::new();
|
||||||
|
let body = valid_configuration(&tree).replace(
|
||||||
|
"default_allow = [\"192.0.2.4/24\", \"198.51.100.53/32\"]",
|
||||||
|
"default_allow = [\"192.0.2.4/24\", \"198.51.100.53/32\"]\ndefault_deny = [\"10.0.0.0/8\"]",
|
||||||
|
);
|
||||||
|
let config = write_configuration(&tree, &body);
|
||||||
|
let mut environment = BTreeMap::new();
|
||||||
|
environment.insert("HOST_TOKEN".into(), "value".into());
|
||||||
|
load_trusted_configuration(request(&tree, config, environment)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn secret_environment_resolution_occurs_after_authority_validation() {
|
fn secret_environment_resolution_occurs_after_authority_validation() {
|
||||||
let volume_tree = TestTree::new();
|
let volume_tree = TestTree::new();
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,7 @@ impl NetworkFixture {
|
||||||
)?;
|
)?;
|
||||||
fs::write(
|
fs::write(
|
||||||
runtime_domain.join("state-anchor/runtime-files/resolv.conf"),
|
runtime_domain.join("state-anchor/runtime-files/resolv.conf"),
|
||||||
b"nameserver 10.0.2.3\n",
|
b"nameserver 10.137.54.3\n",
|
||||||
)?;
|
)?;
|
||||||
let host_loopback = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))?;
|
let host_loopback = TcpListener::bind((Ipv4Addr::LOCALHOST, 0))?;
|
||||||
let host_loopback_port = host_loopback.local_addr()?.port();
|
let host_loopback_port = host_loopback.local_addr()?.port();
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,11 @@ const MAX_POLICY_BYTES: u64 = 64 * 1024;
|
||||||
const HELPER_READY_TIMEOUT: Duration = Duration::from_secs(30);
|
const HELPER_READY_TIMEOUT: Duration = Duration::from_secs(30);
|
||||||
const ROUTE_READY_TIMEOUT: Duration = Duration::from_secs(15);
|
const ROUTE_READY_TIMEOUT: Duration = Duration::from_secs(15);
|
||||||
const SLIRP4NETNS: &str = "/usr/bin/slirp4netns";
|
const SLIRP4NETNS: &str = "/usr/bin/slirp4netns";
|
||||||
|
const SLIRP_CIDR: &str = "10.137.54.0/24";
|
||||||
|
const SLIRP_GATEWAY: &str = "10.137.54.2";
|
||||||
|
const SLIRP_GATEWAY_OCTETS: [u8; 4] = [10, 137, 54, 2];
|
||||||
|
const SLIRP_DNS_CIDR: &str = "10.137.54.3/32";
|
||||||
|
const SLIRP_DNS_OCTETS: [u8; 4] = [10, 137, 54, 3];
|
||||||
const IP: &str = "/usr/bin/ip";
|
const IP: &str = "/usr/bin/ip";
|
||||||
const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
|
const PROBE_TIMEOUT: Duration = Duration::from_secs(3);
|
||||||
const MAX_DNS_PACKET: usize = 4096;
|
const MAX_DNS_PACKET: usize = 4096;
|
||||||
|
|
@ -469,6 +474,7 @@ fn read_sealed_policy(
|
||||||
fn slirp_target(runtime: PidfsProcessIdentityV1, exit_fd: i32, ready_fd: i32) -> TargetCommand {
|
fn slirp_target(runtime: PidfsProcessIdentityV1, exit_fd: i32, ready_fd: i32) -> TargetCommand {
|
||||||
TargetCommand::new(SLIRP4NETNS)
|
TargetCommand::new(SLIRP4NETNS)
|
||||||
.argument("--configure")
|
.argument("--configure")
|
||||||
|
.argument(format!("--cidr={SLIRP_CIDR}"))
|
||||||
.argument("--mtu=65520")
|
.argument("--mtu=65520")
|
||||||
.argument("--disable-host-loopback")
|
.argument("--disable-host-loopback")
|
||||||
.argument("--enable-sandbox")
|
.argument("--enable-sandbox")
|
||||||
|
|
@ -552,17 +558,24 @@ fn install_policy_routes(policy: &CanonicalDerivedNetworkPolicy) -> io::Result<V
|
||||||
let cidr = ipv4_cidr(address, 32);
|
let cidr = ipv4_cidr(address, 32);
|
||||||
install_tap_route(&cidr, address, 32)?;
|
install_tap_route(&cidr, address, 32)?;
|
||||||
}
|
}
|
||||||
install_tap_route("10.0.2.3/32", [10, 0, 2, 3], 32)?;
|
install_tap_route(SLIRP_DNS_CIDR, SLIRP_DNS_OCTETS, 32)?;
|
||||||
verify_loopback_routes(&denied)?;
|
verify_loopback_routes(&denied)?;
|
||||||
Ok(denied)
|
Ok(denied)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_tap_route(cidr: &str, address: [u8; 4], prefix_length: u8) -> io::Result<()> {
|
fn install_tap_route(cidr: &str, address: [u8; 4], prefix_length: u8) -> io::Result<()> {
|
||||||
if ipv4_network_contains(address, prefix_length, [10, 0, 2, 2]) {
|
if ipv4_network_contains(address, prefix_length, SLIRP_GATEWAY_OCTETS) {
|
||||||
run_ip(["-4", "route", "replace", cidr, "dev", "tap0"])
|
run_ip(["-4", "route", "replace", cidr, "dev", "tap0"])
|
||||||
} else {
|
} else {
|
||||||
run_ip([
|
run_ip([
|
||||||
"-4", "route", "replace", cidr, "via", "10.0.2.2", "dev", "tap0",
|
"-4",
|
||||||
|
"route",
|
||||||
|
"replace",
|
||||||
|
cidr,
|
||||||
|
"via",
|
||||||
|
SLIRP_GATEWAY,
|
||||||
|
"dev",
|
||||||
|
"tap0",
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ fn policy() -> std::io::Result<lazaret_core::network::CanonicalDerivedNetworkPol
|
||||||
.collect(),
|
.collect(),
|
||||||
resolvers: vec![Resolver {
|
resolvers: vec![Resolver {
|
||||||
family: AddressFamily::Ipv4,
|
family: AddressFamily::Ipv4,
|
||||||
address: IpAddress::V4([10, 0, 2, 3]),
|
address: IpAddress::V4([10, 137, 54, 3]),
|
||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
.map_err(std::io::Error::other)
|
.map_err(std::io::Error::other)
|
||||||
|
|
@ -254,7 +254,7 @@ fn probe_request(namespace: lazaret_core::identity::NamespaceIdentity) -> Networ
|
||||||
port: 443,
|
port: 443,
|
||||||
}],
|
}],
|
||||||
dns: vec![DnsProbeTarget {
|
dns: vec![DnsProbeTarget {
|
||||||
resolver: IpAddress::V4([10, 0, 2, 3]),
|
resolver: IpAddress::V4([10, 137, 54, 3]),
|
||||||
query_name: "example.com".into(),
|
query_name: "example.com".into(),
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +265,7 @@ fn reachability() -> ReachabilityExpectation {
|
||||||
denied_destinations: vec![IpAddress::V4([10, 1, 2, 3])],
|
denied_destinations: vec![IpAddress::V4([10, 1, 2, 3])],
|
||||||
permitted_destinations: vec![IpAddress::V4([1, 1, 1, 1])],
|
permitted_destinations: vec![IpAddress::V4([1, 1, 1, 1])],
|
||||||
dns_queries: vec![DnsQueryExpectation {
|
dns_queries: vec![DnsQueryExpectation {
|
||||||
resolver: IpAddress::V4([10, 0, 2, 3]),
|
resolver: IpAddress::V4([10, 137, 54, 3]),
|
||||||
query_name: "example.com".into(),
|
query_name: "example.com".into(),
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue