@push.rocks/smartnftables 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +13 -0
- package/dist_rust/smartnftables_linux_amd64_musl +0 -0
- package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +4 -4
- package/dist_rust/smartnftables_linux_arm64_musl +0 -0
- package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +4 -4
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/managed.egress.types.d.ts +38 -0
- package/package.json +3 -3
- package/readme.md +104 -2
- package/rust/src/egress.compile.rs +20 -0
- package/rust/src/egress.host.rs +73 -0
- package/rust/src/egress.router.rs +148 -3
- package/rust/src/egress.rs +156 -1
- package/rust/src/egress_tests.rs +608 -1
- package/rust/src/owner_egress_traffic_tests.rs +3 -3
- package/rust/src/owner_host_traffic_tests.rs +225 -2
- package/rust/src/owner_identity_tests.rs +82 -0
- package/rust/src/owner_router_traffic_tests.rs +393 -0
- package/rust/src/owner_tests.rs +3 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/managed.egress.types.ts +40 -0
package/rust/src/egress.rs
CHANGED
|
@@ -94,6 +94,15 @@ pub struct Generation {
|
|
|
94
94
|
pub conntrack_label: String,
|
|
95
95
|
pub grants: Vec<Grant>,
|
|
96
96
|
}
|
|
97
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
98
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
99
|
+
pub struct RouterPublishedPort {
|
|
100
|
+
pub protocol: String,
|
|
101
|
+
pub transit_port: u16,
|
|
102
|
+
pub endpoint_port: u16,
|
|
103
|
+
pub endpoint_address: String,
|
|
104
|
+
pub transit_source_address: String,
|
|
105
|
+
}
|
|
97
106
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
98
107
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
99
108
|
pub struct RouterScope {
|
|
@@ -103,6 +112,10 @@ pub struct RouterScope {
|
|
|
103
112
|
pub handoff: LocalLink,
|
|
104
113
|
pub protection: Protection,
|
|
105
114
|
pub generations: Vec<Generation>,
|
|
115
|
+
/// Absent and empty are the same canonical policy, so an unpublished scope
|
|
116
|
+
/// keeps its exact previous digest and compiled graph.
|
|
117
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
118
|
+
pub published_ports: Vec<RouterPublishedPort>,
|
|
106
119
|
}
|
|
107
120
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
108
121
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
@@ -110,6 +123,15 @@ pub struct HostHandoff {
|
|
|
110
123
|
pub link: LocalLink,
|
|
111
124
|
pub allocations: Vec<Allocation>,
|
|
112
125
|
}
|
|
126
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
127
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
128
|
+
pub struct PublishedPort {
|
|
129
|
+
pub protocol: String,
|
|
130
|
+
pub host_port: u16,
|
|
131
|
+
pub target_port: u16,
|
|
132
|
+
pub target_address: String,
|
|
133
|
+
pub host_ip: String,
|
|
134
|
+
}
|
|
113
135
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
114
136
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
115
137
|
pub struct HostScope {
|
|
@@ -117,6 +139,10 @@ pub struct HostScope {
|
|
|
117
139
|
pub handoffs: Vec<HostHandoff>,
|
|
118
140
|
pub uplink: LocalLink,
|
|
119
141
|
pub snat_address: String,
|
|
142
|
+
/// Absent and empty are the same canonical policy, so an unpublished scope
|
|
143
|
+
/// keeps its exact previous digest and compiled graph.
|
|
144
|
+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
145
|
+
pub published_ports: Vec<PublishedPort>,
|
|
120
146
|
}
|
|
121
147
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
122
148
|
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
@@ -367,6 +393,86 @@ pub fn destination(grant: &Grant, protection: &Protection) -> Result<String> {
|
|
|
367
393
|
}
|
|
368
394
|
}
|
|
369
395
|
}
|
|
396
|
+
/// Inbound publication is bounded by the authority this scope already proves: an
|
|
397
|
+
/// exact leased transit source address present on the handoff, an exact current
|
|
398
|
+
/// workload endpoint address behind one of this scope's veth links, and no overlap
|
|
399
|
+
/// with the leased outbound source ports translated to that same transit address.
|
|
400
|
+
fn normalize_router_published(value: &mut RouterScope) -> Result<()> {
|
|
401
|
+
let mut ports = std::mem::take(&mut value.published_ports);
|
|
402
|
+
require(ports.len() <= 64)?;
|
|
403
|
+
ports.sort();
|
|
404
|
+
for (index, port) in ports.iter().enumerate() {
|
|
405
|
+
require(
|
|
406
|
+
protocol(&port.protocol)
|
|
407
|
+
&& port.transit_port > 0
|
|
408
|
+
&& port.endpoint_port > 0
|
|
409
|
+
&& (index == 0
|
|
410
|
+
|| ports[index - 1].protocol != port.protocol
|
|
411
|
+
|| ports[index - 1].transit_port != port.transit_port),
|
|
412
|
+
)?;
|
|
413
|
+
// rtnetlink verifies this exact handoff address at apply, recovery and
|
|
414
|
+
// inspection, and only a current leased transit source address receives a
|
|
415
|
+
// publication; that binding is what the host publication already targets.
|
|
416
|
+
require(
|
|
417
|
+
value
|
|
418
|
+
.handoff
|
|
419
|
+
.required_ipv4_addresses
|
|
420
|
+
.contains(&port.transit_source_address)
|
|
421
|
+
&& value.generations.iter().any(|generation| {
|
|
422
|
+
generation.transit_source_address == port.transit_source_address
|
|
423
|
+
}),
|
|
424
|
+
)?;
|
|
425
|
+
unicast(&port.endpoint_address)?;
|
|
426
|
+
// Endpoint source prefixes are canonical and pairwise disjoint, so at most
|
|
427
|
+
// one endpoint can hold this address. It must be an actual workload veth
|
|
428
|
+
// behind this scope, never a router-local address, a gateway, the transit
|
|
429
|
+
// address or a protected platform endpoint.
|
|
430
|
+
let target = format!("{}/32", port.endpoint_address);
|
|
431
|
+
let endpoint = value
|
|
432
|
+
.endpoints
|
|
433
|
+
.iter()
|
|
434
|
+
.find(|endpoint| {
|
|
435
|
+
endpoint
|
|
436
|
+
.source_prefixes
|
|
437
|
+
.iter()
|
|
438
|
+
.any(|source| covers(source, &target) == Ok(true))
|
|
439
|
+
})
|
|
440
|
+
.ok_or(Error::Invalid)?;
|
|
441
|
+
require(
|
|
442
|
+
endpoint.interface_kind == "veth"
|
|
443
|
+
&& value
|
|
444
|
+
.links
|
|
445
|
+
.iter()
|
|
446
|
+
.chain(std::iter::once(&value.handoff))
|
|
447
|
+
.all(|link| {
|
|
448
|
+
!link
|
|
449
|
+
.required_ipv4_addresses
|
|
450
|
+
.contains(&port.endpoint_address)
|
|
451
|
+
})
|
|
452
|
+
&& value
|
|
453
|
+
.protection
|
|
454
|
+
.platform_endpoints
|
|
455
|
+
.iter()
|
|
456
|
+
.all(|item| item.address != port.endpoint_address),
|
|
457
|
+
)?;
|
|
458
|
+
for generation in &value.generations {
|
|
459
|
+
if generation.transit_source_address != port.transit_source_address {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
for range in &generation.source_port_ranges {
|
|
463
|
+
// Source NAT translates to this same transit address, so a published
|
|
464
|
+
// port inside a leased outbound range is a translation conflict.
|
|
465
|
+
require(
|
|
466
|
+
range.protocol != port.protocol
|
|
467
|
+
|| port.transit_port < range.first
|
|
468
|
+
|| port.transit_port > range.last,
|
|
469
|
+
)?;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
value.published_ports = ports;
|
|
474
|
+
Ok(())
|
|
475
|
+
}
|
|
370
476
|
fn normalize_router(value: &mut RouterScope, revision: u64) -> Result<()> {
|
|
371
477
|
require(value.links.len() <= 32 && value.generations.len() <= 32)?;
|
|
372
478
|
let private = policy::Policy {
|
|
@@ -537,7 +643,8 @@ fn normalize_router(value: &mut RouterScope, revision: u64) -> Result<()> {
|
|
|
537
643
|
item.transit_source_address.as_str(),
|
|
538
644
|
item.source_port_ranges.as_slice(),
|
|
539
645
|
)
|
|
540
|
-
}))
|
|
646
|
+
}))?;
|
|
647
|
+
normalize_router_published(value)
|
|
541
648
|
}
|
|
542
649
|
fn normalize_pool_guard(value: &mut AllocationPoolGuardScope) -> Result<()> {
|
|
543
650
|
require(
|
|
@@ -556,6 +663,53 @@ fn normalize_pool_guard(value: &mut AllocationPoolGuardScope) -> Result<()> {
|
|
|
556
663
|
}
|
|
557
664
|
Ok(())
|
|
558
665
|
}
|
|
666
|
+
/// Inbound publication is bounded by the same authority the scope already proves:
|
|
667
|
+
/// an exact current uplink address, an exact leased transit source address, and no
|
|
668
|
+
/// overlap with the leased outbound source ports translated to that same address.
|
|
669
|
+
fn normalize_published(value: &mut HostScope) -> Result<()> {
|
|
670
|
+
let mut ports = std::mem::take(&mut value.published_ports);
|
|
671
|
+
require(ports.len() <= 64)?;
|
|
672
|
+
ports.sort();
|
|
673
|
+
for (index, port) in ports.iter().enumerate() {
|
|
674
|
+
require(
|
|
675
|
+
protocol(&port.protocol)
|
|
676
|
+
&& port.host_port > 0
|
|
677
|
+
&& port.target_port > 0
|
|
678
|
+
&& (index == 0
|
|
679
|
+
|| ports[index - 1].protocol != port.protocol
|
|
680
|
+
|| ports[index - 1].host_port != port.host_port),
|
|
681
|
+
)?;
|
|
682
|
+
// rtnetlink verifies this exact uplink address at apply, recovery and
|
|
683
|
+
// inspection. No wildcard, secondary-link or default-route inference.
|
|
684
|
+
require(value.uplink.required_ipv4_addresses.contains(&port.host_ip))?;
|
|
685
|
+
// Only a current leased transit source address can be published; that
|
|
686
|
+
// binding is what proves the exact handoff link of the forward admission.
|
|
687
|
+
require(
|
|
688
|
+
value
|
|
689
|
+
.handoffs
|
|
690
|
+
.iter()
|
|
691
|
+
.flat_map(|handoff| handoff.allocations.iter())
|
|
692
|
+
.any(|allocation| allocation.transit_source_address == port.target_address),
|
|
693
|
+
)?;
|
|
694
|
+
if port.host_ip == value.snat_address {
|
|
695
|
+
for handoff in &value.handoffs {
|
|
696
|
+
for allocation in &handoff.allocations {
|
|
697
|
+
for range in &allocation.source_port_ranges {
|
|
698
|
+
// Outer SNAT translates to this same address, so a published
|
|
699
|
+
// port inside a leased outbound range is a translation conflict.
|
|
700
|
+
require(
|
|
701
|
+
range.protocol != port.protocol
|
|
702
|
+
|| port.host_port < range.first
|
|
703
|
+
|| port.host_port > range.last,
|
|
704
|
+
)?;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
value.published_ports = ports;
|
|
711
|
+
Ok(())
|
|
712
|
+
}
|
|
559
713
|
fn normalize_host(value: &mut HostScope) -> Result<()> {
|
|
560
714
|
normalize_protection(&mut value.protection)?;
|
|
561
715
|
normalize_link(&mut value.uplink)?;
|
|
@@ -622,6 +776,7 @@ fn normalize_host(value: &mut HostScope) -> Result<()> {
|
|
|
622
776
|
}
|
|
623
777
|
}
|
|
624
778
|
}
|
|
779
|
+
normalize_published(value)?;
|
|
625
780
|
unique_links(
|
|
626
781
|
value
|
|
627
782
|
.handoffs
|