@push.rocks/smartnftables 1.4.0 → 1.6.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 +26 -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/classes.manageddockerforwarding.d.ts +17 -0
- package/dist_ts/classes.manageddockerforwarding.js +128 -0
- package/dist_ts/classes.managednftables.d.ts +9 -7
- package/dist_ts/classes.managednftables.js +2 -2
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/managed.docker.types.d.ts +78 -0
- package/dist_ts/managed.docker.types.js +2 -0
- package/dist_ts/managed.egress.types.d.ts +91 -0
- package/dist_ts/managed.egress.types.js +2 -0
- package/dist_ts/managed.types.d.ts +20 -18
- package/package.json +2 -2
- package/readme.md +143 -6
- package/rust/src/docker.frontend.rs +203 -0
- package/rust/src/docker.graph.rs +129 -0
- package/rust/src/docker.policy.rs +140 -0
- package/rust/src/docker.rs +191 -0
- package/rust/src/docker_process_tests.rs +29 -0
- package/rust/src/docker_tests.rs +148 -0
- package/rust/src/egress.compile.rs +288 -0
- package/rust/src/egress.host.rs +103 -0
- package/rust/src/egress.router.rs +231 -0
- package/rust/src/egress.rs +682 -0
- package/rust/src/egress_tests.rs +332 -0
- package/rust/src/main.rs +13 -6
- package/rust/src/managed.rs +105 -0
- package/rust/src/owner.rs +115 -7
- package/rust/src/owner_coexistence_tests.rs +77 -0
- package/rust/src/owner_egress_identity_tests.rs +155 -0
- package/rust/src/owner_egress_tests.rs +153 -0
- package/rust/src/owner_egress_traffic_tests.rs +470 -0
- package/rust/src/owner_host_traffic_tests.rs +305 -0
- package/rust/src/owner_identity_tests.rs +169 -0
- package/rust/src/owner_link_tests.rs +119 -0
- package/rust/src/owner_packet_fixture.rs +200 -0
- package/rust/src/owner_tests.rs +33 -7
- package/rust/src/policy.rs +120 -81
- package/rust/src/tests.rs +22 -0
- package/rust/src/wire.links.rs +279 -0
- package/rust/src/wire.rs +16 -66
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.manageddockerforwarding.ts +113 -0
- package/ts/classes.managednftables.ts +13 -13
- package/ts/index.ts +3 -0
- package/ts/managed.docker.types.ts +49 -0
- package/ts/managed.egress.types.ts +85 -0
- package/ts/managed.types.ts +20 -16
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
use crate::{policy, Error, Result};
|
|
2
|
+
use serde::{Deserialize, Serialize};
|
|
3
|
+
use std::collections::BTreeSet;
|
|
4
|
+
use std::net::Ipv4Addr;
|
|
5
|
+
|
|
6
|
+
#[path = "egress.compile.rs"]
|
|
7
|
+
mod compile;
|
|
8
|
+
|
|
9
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
10
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
11
|
+
pub struct LocalLink {
|
|
12
|
+
pub interface_index: u32,
|
|
13
|
+
pub interface_name: String,
|
|
14
|
+
pub interface_kind: String,
|
|
15
|
+
#[serde(deserialize_with = "policy::required_nullable")]
|
|
16
|
+
pub mac_address: Option<String>,
|
|
17
|
+
pub interface_link_index: u32,
|
|
18
|
+
pub required_ipv4_addresses: Vec<String>,
|
|
19
|
+
}
|
|
20
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
21
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
22
|
+
pub struct PlatformEndpoint {
|
|
23
|
+
pub id: String,
|
|
24
|
+
pub address: String,
|
|
25
|
+
pub protocol: String,
|
|
26
|
+
pub port: u16,
|
|
27
|
+
}
|
|
28
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
29
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
30
|
+
pub struct Protection {
|
|
31
|
+
pub authority_digest: String,
|
|
32
|
+
pub prefixes: Vec<String>,
|
|
33
|
+
pub platform_endpoints: Vec<PlatformEndpoint>,
|
|
34
|
+
}
|
|
35
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
36
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
37
|
+
pub struct PortRange {
|
|
38
|
+
pub protocol: String,
|
|
39
|
+
pub first: u16,
|
|
40
|
+
pub last: u16,
|
|
41
|
+
}
|
|
42
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
43
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
44
|
+
pub struct Lease {
|
|
45
|
+
pub id: String,
|
|
46
|
+
pub generation: u64,
|
|
47
|
+
pub digest: String,
|
|
48
|
+
}
|
|
49
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
50
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
51
|
+
pub struct Allocation {
|
|
52
|
+
pub lease: Lease,
|
|
53
|
+
pub transit_source_address: String,
|
|
54
|
+
pub source_port_ranges: Vec<PortRange>,
|
|
55
|
+
}
|
|
56
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
57
|
+
#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
|
|
58
|
+
pub enum Destination {
|
|
59
|
+
Public {
|
|
60
|
+
prefix: String,
|
|
61
|
+
},
|
|
62
|
+
Platform {
|
|
63
|
+
#[serde(rename = "endpointId")]
|
|
64
|
+
endpoint_id: String,
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
68
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
69
|
+
pub struct SelectedPortRange {
|
|
70
|
+
pub first: u16,
|
|
71
|
+
pub last: u16,
|
|
72
|
+
}
|
|
73
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
|
74
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
75
|
+
pub struct Grant {
|
|
76
|
+
#[serde(deserialize_with = "policy::required_nullable")]
|
|
77
|
+
pub source_endpoint: Option<String>,
|
|
78
|
+
pub source_prefix: String,
|
|
79
|
+
pub destination: Destination,
|
|
80
|
+
pub protocol: String,
|
|
81
|
+
#[serde(deserialize_with = "policy::required_nullable")]
|
|
82
|
+
pub source_port: Option<u16>,
|
|
83
|
+
#[serde(deserialize_with = "policy::required_nullable")]
|
|
84
|
+
pub destination_port: Option<u16>,
|
|
85
|
+
pub source_port_range: SelectedPortRange,
|
|
86
|
+
}
|
|
87
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
88
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
89
|
+
pub struct Generation {
|
|
90
|
+
pub lease: Lease,
|
|
91
|
+
pub transit_source_address: String,
|
|
92
|
+
pub source_port_ranges: Vec<PortRange>,
|
|
93
|
+
pub conntrack_zone: u16,
|
|
94
|
+
pub conntrack_label: String,
|
|
95
|
+
pub grants: Vec<Grant>,
|
|
96
|
+
}
|
|
97
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
98
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
99
|
+
pub struct RouterScope {
|
|
100
|
+
pub endpoints: Vec<policy::Endpoint>,
|
|
101
|
+
pub rules: Vec<policy::Rule>,
|
|
102
|
+
pub links: Vec<LocalLink>,
|
|
103
|
+
pub handoff: LocalLink,
|
|
104
|
+
pub protection: Protection,
|
|
105
|
+
pub generations: Vec<Generation>,
|
|
106
|
+
}
|
|
107
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
108
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
109
|
+
pub struct HostHandoff {
|
|
110
|
+
pub link: LocalLink,
|
|
111
|
+
pub allocations: Vec<Allocation>,
|
|
112
|
+
}
|
|
113
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
114
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
115
|
+
pub struct HostScope {
|
|
116
|
+
pub protection: Protection,
|
|
117
|
+
pub handoffs: Vec<HostHandoff>,
|
|
118
|
+
pub uplink: LocalLink,
|
|
119
|
+
pub snat_address: String,
|
|
120
|
+
}
|
|
121
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
122
|
+
#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
|
|
123
|
+
pub enum Scope {
|
|
124
|
+
RouterEgress(RouterScope),
|
|
125
|
+
HostTransit(HostScope),
|
|
126
|
+
}
|
|
127
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
128
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
129
|
+
pub struct Policy {
|
|
130
|
+
pub schema_version: u8,
|
|
131
|
+
pub revision: u64,
|
|
132
|
+
pub scope: Scope,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
|
136
|
+
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
|
137
|
+
pub struct Prepared {
|
|
138
|
+
pub policy: Policy,
|
|
139
|
+
pub digest: String,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn require(value: bool) -> Result<()> {
|
|
143
|
+
if value {
|
|
144
|
+
Ok(())
|
|
145
|
+
} else {
|
|
146
|
+
Err(Error::Invalid)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
fn identifier(value: &str) -> bool {
|
|
150
|
+
!value.is_empty()
|
|
151
|
+
&& value.len() <= 200
|
|
152
|
+
&& value.as_bytes()[0].is_ascii_alphanumeric()
|
|
153
|
+
&& value
|
|
154
|
+
.bytes()
|
|
155
|
+
.all(|item| item.is_ascii_alphanumeric() || b":._-".contains(&item))
|
|
156
|
+
}
|
|
157
|
+
pub fn digest(value: &str) -> bool {
|
|
158
|
+
value.len() == 71
|
|
159
|
+
&& value.starts_with("sha256:")
|
|
160
|
+
&& value.as_bytes()[7..]
|
|
161
|
+
.iter()
|
|
162
|
+
.all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(item))
|
|
163
|
+
}
|
|
164
|
+
fn integer(value: u64) -> bool {
|
|
165
|
+
value > 0 && value <= 9_007_199_254_740_991
|
|
166
|
+
}
|
|
167
|
+
fn protocol(value: &str) -> bool {
|
|
168
|
+
["tcp", "udp"].contains(&value)
|
|
169
|
+
}
|
|
170
|
+
pub fn address(value: &str) -> Result<u32> {
|
|
171
|
+
let ip: Ipv4Addr = value.parse().map_err(|_| Error::Invalid)?;
|
|
172
|
+
require(ip.to_string() == value)?;
|
|
173
|
+
Ok(u32::from(ip))
|
|
174
|
+
}
|
|
175
|
+
fn unicast(value: &str) -> Result<u32> {
|
|
176
|
+
let ip = address(value)?;
|
|
177
|
+
let first = ip >> 24;
|
|
178
|
+
require(first > 0 && first < 224 && first != 127)?;
|
|
179
|
+
Ok(ip)
|
|
180
|
+
}
|
|
181
|
+
pub fn prefix(value: &str) -> Result<(u32, u32)> {
|
|
182
|
+
let (ip, width) = value.split_once('/').ok_or(Error::Invalid)?;
|
|
183
|
+
let width: u32 = width.parse().map_err(|_| Error::Invalid)?;
|
|
184
|
+
require(width <= 32 && value == format!("{ip}/{width}"))?;
|
|
185
|
+
let base = address(ip)?;
|
|
186
|
+
let mask = if width == 0 {
|
|
187
|
+
0
|
|
188
|
+
} else {
|
|
189
|
+
u32::MAX << (32 - width)
|
|
190
|
+
};
|
|
191
|
+
require(base & mask == base)?;
|
|
192
|
+
Ok((base, mask))
|
|
193
|
+
}
|
|
194
|
+
pub fn covers(outer: &str, inner: &str) -> Result<bool> {
|
|
195
|
+
let (a, am) = prefix(outer)?;
|
|
196
|
+
let (b, bm) = prefix(inner)?;
|
|
197
|
+
Ok(bm & am == am && b & am == a)
|
|
198
|
+
}
|
|
199
|
+
fn overlaps(left: &str, right: &str) -> Result<bool> {
|
|
200
|
+
Ok(covers(left, right)? || covers(right, left)?)
|
|
201
|
+
}
|
|
202
|
+
fn covers_address(prefixes: &[String], ip: &str) -> Result<bool> {
|
|
203
|
+
for item in prefixes {
|
|
204
|
+
if covers(item, &format!("{ip}/32"))? {
|
|
205
|
+
return Ok(true);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
Ok(false)
|
|
209
|
+
}
|
|
210
|
+
fn normalize_link(link: &mut LocalLink) -> Result<()> {
|
|
211
|
+
require(
|
|
212
|
+
link.interface_index > 0
|
|
213
|
+
&& link.interface_index <= i32::MAX as u32
|
|
214
|
+
&& link.interface_link_index > 0
|
|
215
|
+
&& link.interface_link_index <= i32::MAX as u32
|
|
216
|
+
&& policy::identifier(&link.interface_name)
|
|
217
|
+
&& link.interface_name.len() <= 15
|
|
218
|
+
&& link.interface_name != "lo"
|
|
219
|
+
&& ["veth", "tun", "ethernet"].contains(&link.interface_kind.as_str())
|
|
220
|
+
&& link.required_ipv4_addresses.len() <= 32,
|
|
221
|
+
)?;
|
|
222
|
+
if link.interface_kind == "tun" {
|
|
223
|
+
require(link.mac_address.is_none())?;
|
|
224
|
+
} else {
|
|
225
|
+
let mac = link.mac_address.as_ref().ok_or(Error::Invalid)?;
|
|
226
|
+
let parts: Vec<_> = mac.split(':').collect();
|
|
227
|
+
require(
|
|
228
|
+
parts.len() == 6
|
|
229
|
+
&& parts.iter().all(|part| {
|
|
230
|
+
part.len() == 2
|
|
231
|
+
&& part
|
|
232
|
+
.bytes()
|
|
233
|
+
.all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(&item))
|
|
234
|
+
}),
|
|
235
|
+
)?;
|
|
236
|
+
let bytes: Vec<u8> = parts
|
|
237
|
+
.iter()
|
|
238
|
+
.map(|part| u8::from_str_radix(part, 16).unwrap())
|
|
239
|
+
.collect();
|
|
240
|
+
require(bytes[0] & 1 == 0 && bytes.iter().any(|item| *item != 0))?;
|
|
241
|
+
}
|
|
242
|
+
link.required_ipv4_addresses.sort();
|
|
243
|
+
for (index, ip) in link.required_ipv4_addresses.iter().enumerate() {
|
|
244
|
+
unicast(ip)?;
|
|
245
|
+
require(index == 0 || *ip != link.required_ipv4_addresses[index - 1])?;
|
|
246
|
+
}
|
|
247
|
+
Ok(())
|
|
248
|
+
}
|
|
249
|
+
fn unique_links<'a>(links: impl Iterator<Item = &'a LocalLink>) -> Result<()> {
|
|
250
|
+
let mut indices = BTreeSet::new();
|
|
251
|
+
let mut names = BTreeSet::new();
|
|
252
|
+
for link in links {
|
|
253
|
+
require(indices.insert(link.interface_index) && names.insert(&link.interface_name))?;
|
|
254
|
+
}
|
|
255
|
+
Ok(())
|
|
256
|
+
}
|
|
257
|
+
fn normalize_protection(value: &mut Protection) -> Result<()> {
|
|
258
|
+
require(
|
|
259
|
+
digest(&value.authority_digest)
|
|
260
|
+
&& !value.prefixes.is_empty()
|
|
261
|
+
&& value.prefixes.len() <= 128
|
|
262
|
+
&& value.platform_endpoints.len() <= 96,
|
|
263
|
+
)?;
|
|
264
|
+
value.prefixes.sort();
|
|
265
|
+
for (index, item) in value.prefixes.iter().enumerate() {
|
|
266
|
+
prefix(item)?;
|
|
267
|
+
for previous in &value.prefixes[..index] {
|
|
268
|
+
require(!overlaps(item, previous)?)?;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
value.platform_endpoints.sort_by(|a, b| a.id.cmp(&b.id));
|
|
272
|
+
let mut tuples = BTreeSet::new();
|
|
273
|
+
for (index, endpoint) in value.platform_endpoints.iter().enumerate() {
|
|
274
|
+
require(
|
|
275
|
+
identifier(&endpoint.id)
|
|
276
|
+
&& protocol(&endpoint.protocol)
|
|
277
|
+
&& endpoint.port > 0
|
|
278
|
+
&& (index == 0 || endpoint.id != value.platform_endpoints[index - 1].id),
|
|
279
|
+
)?;
|
|
280
|
+
unicast(&endpoint.address)?;
|
|
281
|
+
require(
|
|
282
|
+
covers_address(&value.prefixes, &endpoint.address)?
|
|
283
|
+
&& tuples.insert((&endpoint.address, &endpoint.protocol, endpoint.port)),
|
|
284
|
+
)?;
|
|
285
|
+
}
|
|
286
|
+
Ok(())
|
|
287
|
+
}
|
|
288
|
+
fn normalize_allocation(
|
|
289
|
+
lease: &Lease,
|
|
290
|
+
source: &str,
|
|
291
|
+
ranges: &mut [PortRange],
|
|
292
|
+
protection: &Protection,
|
|
293
|
+
) -> Result<()> {
|
|
294
|
+
require(
|
|
295
|
+
identifier(&lease.id)
|
|
296
|
+
&& integer(lease.generation)
|
|
297
|
+
&& digest(&lease.digest)
|
|
298
|
+
&& !ranges.is_empty()
|
|
299
|
+
&& ranges.len() <= 16,
|
|
300
|
+
)?;
|
|
301
|
+
unicast(source)?;
|
|
302
|
+
require(covers_address(&protection.prefixes, source)?)?;
|
|
303
|
+
require(
|
|
304
|
+
protection
|
|
305
|
+
.platform_endpoints
|
|
306
|
+
.iter()
|
|
307
|
+
.all(|endpoint| endpoint.address != source),
|
|
308
|
+
)?;
|
|
309
|
+
ranges.sort();
|
|
310
|
+
for (index, range) in ranges.iter().enumerate() {
|
|
311
|
+
require(protocol(&range.protocol) && range.first > 0 && range.first <= range.last)?;
|
|
312
|
+
if index > 0 {
|
|
313
|
+
let previous = &ranges[index - 1];
|
|
314
|
+
require(previous.protocol != range.protocol || previous.last < range.first)?;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
Ok(())
|
|
318
|
+
}
|
|
319
|
+
fn unique_allocations<'a>(
|
|
320
|
+
items: impl Iterator<Item = (&'a Lease, &'a str, &'a [PortRange])>,
|
|
321
|
+
) -> Result<()> {
|
|
322
|
+
let mut leases = BTreeSet::new();
|
|
323
|
+
let mut prior: Vec<(&str, &[PortRange])> = Vec::new();
|
|
324
|
+
for (lease, source, ranges) in items {
|
|
325
|
+
require(leases.insert((&lease.id, lease.generation)))?;
|
|
326
|
+
for (other_source, other_ranges) in &prior {
|
|
327
|
+
if *other_source == source {
|
|
328
|
+
for left in ranges {
|
|
329
|
+
for right in *other_ranges {
|
|
330
|
+
require(
|
|
331
|
+
left.protocol != right.protocol
|
|
332
|
+
|| left.last < right.first
|
|
333
|
+
|| right.last < left.first,
|
|
334
|
+
)?;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
prior.push((source, ranges));
|
|
340
|
+
}
|
|
341
|
+
Ok(())
|
|
342
|
+
}
|
|
343
|
+
pub fn destination(grant: &Grant, protection: &Protection) -> Result<String> {
|
|
344
|
+
match &grant.destination {
|
|
345
|
+
Destination::Public { prefix: value } => {
|
|
346
|
+
prefix(value)?;
|
|
347
|
+
Ok(value.clone())
|
|
348
|
+
}
|
|
349
|
+
Destination::Platform { endpoint_id } => {
|
|
350
|
+
let endpoint = protection
|
|
351
|
+
.platform_endpoints
|
|
352
|
+
.iter()
|
|
353
|
+
.find(|item| item.id == *endpoint_id)
|
|
354
|
+
.ok_or(Error::Invalid)?;
|
|
355
|
+
require(
|
|
356
|
+
grant.protocol == endpoint.protocol
|
|
357
|
+
&& grant.destination_port == Some(endpoint.port),
|
|
358
|
+
)?;
|
|
359
|
+
Ok(format!("{}/32", endpoint.address))
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
fn normalize_router(value: &mut RouterScope, revision: u64) -> Result<()> {
|
|
364
|
+
require(value.links.len() <= 32 && value.generations.len() <= 32)?;
|
|
365
|
+
let private = policy::Policy {
|
|
366
|
+
schema_version: 1,
|
|
367
|
+
revision,
|
|
368
|
+
endpoints: value.endpoints.clone(),
|
|
369
|
+
rules: value.rules.clone(),
|
|
370
|
+
}
|
|
371
|
+
.prepare()?;
|
|
372
|
+
value.endpoints = private.policy.endpoints;
|
|
373
|
+
value.rules = private.policy.rules;
|
|
374
|
+
normalize_protection(&mut value.protection)?;
|
|
375
|
+
normalize_link(&mut value.handoff)?;
|
|
376
|
+
require(
|
|
377
|
+
value.handoff.interface_kind == "veth"
|
|
378
|
+
&& !value.handoff.required_ipv4_addresses.is_empty()
|
|
379
|
+
&& value.links.len() == value.endpoints.len(),
|
|
380
|
+
)?;
|
|
381
|
+
for link in &mut value.links {
|
|
382
|
+
normalize_link(link)?;
|
|
383
|
+
}
|
|
384
|
+
value.links.sort_by_key(|link| link.interface_index);
|
|
385
|
+
unique_links(value.links.iter().chain(std::iter::once(&value.handoff)))?;
|
|
386
|
+
for endpoint in &value.endpoints {
|
|
387
|
+
require(value.links.iter().any(|link| {
|
|
388
|
+
link.interface_index == endpoint.interface_index
|
|
389
|
+
&& link.interface_name == endpoint.interface_name
|
|
390
|
+
&& link.interface_kind == endpoint.interface_kind
|
|
391
|
+
}))?;
|
|
392
|
+
for source in &endpoint.source_prefixes {
|
|
393
|
+
require(
|
|
394
|
+
value
|
|
395
|
+
.protection
|
|
396
|
+
.prefixes
|
|
397
|
+
.iter()
|
|
398
|
+
.any(|protected| covers(protected, source) == Ok(true)),
|
|
399
|
+
)?;
|
|
400
|
+
for exception in &value.protection.platform_endpoints {
|
|
401
|
+
require(!covers(source, &format!("{}/32", exception.address))?)?;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
for link in value.links.iter().chain(std::iter::once(&value.handoff)) {
|
|
406
|
+
for ip in &link.required_ipv4_addresses {
|
|
407
|
+
require(
|
|
408
|
+
covers_address(&value.protection.prefixes, ip)?
|
|
409
|
+
&& value
|
|
410
|
+
.protection
|
|
411
|
+
.platform_endpoints
|
|
412
|
+
.iter()
|
|
413
|
+
.all(|endpoint| endpoint.address != *ip),
|
|
414
|
+
)?;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for rule in &value.rules {
|
|
418
|
+
for item in [&rule.source_prefix, &rule.destination_prefix] {
|
|
419
|
+
require(
|
|
420
|
+
value
|
|
421
|
+
.protection
|
|
422
|
+
.prefixes
|
|
423
|
+
.iter()
|
|
424
|
+
.any(|protected| covers(protected, item) == Ok(true)),
|
|
425
|
+
)?;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
let mut zones = BTreeSet::new();
|
|
429
|
+
let mut labels = BTreeSet::new();
|
|
430
|
+
let mut classifiers: Vec<(&Grant, String)> = Vec::new();
|
|
431
|
+
let mut count = 0;
|
|
432
|
+
value
|
|
433
|
+
.generations
|
|
434
|
+
.sort_by(|a, b| (&a.lease.id, a.lease.generation).cmp(&(&b.lease.id, b.lease.generation)));
|
|
435
|
+
for generation in &mut value.generations {
|
|
436
|
+
normalize_allocation(
|
|
437
|
+
&generation.lease,
|
|
438
|
+
&generation.transit_source_address,
|
|
439
|
+
&mut generation.source_port_ranges,
|
|
440
|
+
&value.protection,
|
|
441
|
+
)?;
|
|
442
|
+
require(
|
|
443
|
+
value
|
|
444
|
+
.handoff
|
|
445
|
+
.required_ipv4_addresses
|
|
446
|
+
.contains(&generation.transit_source_address)
|
|
447
|
+
&& generation.conntrack_zone > 0
|
|
448
|
+
&& zones.insert(generation.conntrack_zone)
|
|
449
|
+
&& generation.conntrack_label.len() == 32
|
|
450
|
+
&& generation
|
|
451
|
+
.conntrack_label
|
|
452
|
+
.bytes()
|
|
453
|
+
.all(|item| item.is_ascii_digit() || (b'a'..=b'f').contains(&item))
|
|
454
|
+
&& generation.conntrack_label.bytes().any(|item| item != b'0')
|
|
455
|
+
&& labels.insert(generation.conntrack_label.clone()),
|
|
456
|
+
)?;
|
|
457
|
+
count += generation.grants.len();
|
|
458
|
+
require(count <= 128)?;
|
|
459
|
+
generation.grants.sort();
|
|
460
|
+
for grant in &generation.grants {
|
|
461
|
+
require(
|
|
462
|
+
protocol(&grant.protocol)
|
|
463
|
+
&& grant.source_port != Some(0)
|
|
464
|
+
&& grant.destination_port != Some(0),
|
|
465
|
+
)?;
|
|
466
|
+
prefix(&grant.source_prefix)?;
|
|
467
|
+
if let Some(endpoint_id) = &grant.source_endpoint {
|
|
468
|
+
let endpoint = value
|
|
469
|
+
.endpoints
|
|
470
|
+
.iter()
|
|
471
|
+
.find(|item| item.id == *endpoint_id)
|
|
472
|
+
.ok_or(Error::Invalid)?;
|
|
473
|
+
require(
|
|
474
|
+
endpoint.interface_kind == "veth"
|
|
475
|
+
&& endpoint
|
|
476
|
+
.source_prefixes
|
|
477
|
+
.iter()
|
|
478
|
+
.any(|source| covers(source, &grant.source_prefix) == Ok(true)),
|
|
479
|
+
)?;
|
|
480
|
+
} else {
|
|
481
|
+
let (ip, mask) = prefix(&grant.source_prefix)?;
|
|
482
|
+
require(
|
|
483
|
+
mask == u32::MAX
|
|
484
|
+
&& value
|
|
485
|
+
.links
|
|
486
|
+
.iter()
|
|
487
|
+
.chain(std::iter::once(&value.handoff))
|
|
488
|
+
.any(|link| {
|
|
489
|
+
link.required_ipv4_addresses
|
|
490
|
+
.contains(&Ipv4Addr::from(ip).to_string())
|
|
491
|
+
}),
|
|
492
|
+
)?;
|
|
493
|
+
}
|
|
494
|
+
require(generation.source_port_ranges.iter().any(|range| {
|
|
495
|
+
range.protocol == grant.protocol
|
|
496
|
+
&& range.first == grant.source_port_range.first
|
|
497
|
+
&& range.last == grant.source_port_range.last
|
|
498
|
+
}))?;
|
|
499
|
+
let target = destination(grant, &value.protection)?;
|
|
500
|
+
if let Destination::Public { .. } = &grant.destination {
|
|
501
|
+
require(
|
|
502
|
+
!value
|
|
503
|
+
.protection
|
|
504
|
+
.prefixes
|
|
505
|
+
.iter()
|
|
506
|
+
.any(|protected| covers(protected, &target) == Ok(true)),
|
|
507
|
+
)?;
|
|
508
|
+
}
|
|
509
|
+
for (previous, previous_target) in &classifiers {
|
|
510
|
+
let same_kind = std::mem::discriminant(&previous.destination)
|
|
511
|
+
== std::mem::discriminant(&grant.destination);
|
|
512
|
+
let ports_overlap =
|
|
513
|
+
|a: Option<u16>, b: Option<u16>| a.is_none() || b.is_none() || a == b;
|
|
514
|
+
require(
|
|
515
|
+
!(same_kind
|
|
516
|
+
&& previous.source_endpoint == grant.source_endpoint
|
|
517
|
+
&& previous.protocol == grant.protocol
|
|
518
|
+
&& ports_overlap(previous.source_port, grant.source_port)
|
|
519
|
+
&& ports_overlap(previous.destination_port, grant.destination_port)
|
|
520
|
+
&& overlaps(&previous.source_prefix, &grant.source_prefix)?
|
|
521
|
+
&& overlaps(previous_target, &target)?),
|
|
522
|
+
)?;
|
|
523
|
+
}
|
|
524
|
+
classifiers.push((grant, target));
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
unique_allocations(value.generations.iter().map(|item| {
|
|
528
|
+
(
|
|
529
|
+
&item.lease,
|
|
530
|
+
item.transit_source_address.as_str(),
|
|
531
|
+
item.source_port_ranges.as_slice(),
|
|
532
|
+
)
|
|
533
|
+
}))
|
|
534
|
+
}
|
|
535
|
+
fn normalize_host(value: &mut HostScope) -> Result<()> {
|
|
536
|
+
normalize_protection(&mut value.protection)?;
|
|
537
|
+
normalize_link(&mut value.uplink)?;
|
|
538
|
+
require(
|
|
539
|
+
value.handoffs.len() <= 32
|
|
540
|
+
&& value.uplink.interface_kind != "tun"
|
|
541
|
+
&& value
|
|
542
|
+
.uplink
|
|
543
|
+
.required_ipv4_addresses
|
|
544
|
+
.contains(&value.snat_address),
|
|
545
|
+
)?;
|
|
546
|
+
unicast(&value.snat_address)?;
|
|
547
|
+
value
|
|
548
|
+
.handoffs
|
|
549
|
+
.sort_by_key(|handoff| handoff.link.interface_index);
|
|
550
|
+
let mut count = 0;
|
|
551
|
+
let mut addresses = std::collections::BTreeMap::new();
|
|
552
|
+
let local_addresses: BTreeSet<_> = value
|
|
553
|
+
.handoffs
|
|
554
|
+
.iter()
|
|
555
|
+
.flat_map(|item| item.link.required_ipv4_addresses.iter().cloned())
|
|
556
|
+
.chain(value.uplink.required_ipv4_addresses.iter().cloned())
|
|
557
|
+
.collect();
|
|
558
|
+
for ip in &local_addresses {
|
|
559
|
+
require(
|
|
560
|
+
covers_address(&value.protection.prefixes, ip)?
|
|
561
|
+
&& value
|
|
562
|
+
.protection
|
|
563
|
+
.platform_endpoints
|
|
564
|
+
.iter()
|
|
565
|
+
.all(|endpoint| endpoint.address != *ip),
|
|
566
|
+
)?;
|
|
567
|
+
}
|
|
568
|
+
for handoff in &mut value.handoffs {
|
|
569
|
+
normalize_link(&mut handoff.link)?;
|
|
570
|
+
require(
|
|
571
|
+
handoff.link.interface_kind == "veth"
|
|
572
|
+
&& !handoff.link.required_ipv4_addresses.is_empty(),
|
|
573
|
+
)?;
|
|
574
|
+
for ip in &handoff.link.required_ipv4_addresses {
|
|
575
|
+
require(covers_address(&value.protection.prefixes, ip)?)?;
|
|
576
|
+
}
|
|
577
|
+
handoff.allocations.sort_by(|a, b| {
|
|
578
|
+
(&a.lease.id, a.lease.generation).cmp(&(&b.lease.id, b.lease.generation))
|
|
579
|
+
});
|
|
580
|
+
count += handoff.allocations.len();
|
|
581
|
+
require(count <= 32)?;
|
|
582
|
+
for allocation in &mut handoff.allocations {
|
|
583
|
+
normalize_allocation(
|
|
584
|
+
&allocation.lease,
|
|
585
|
+
&allocation.transit_source_address,
|
|
586
|
+
&mut allocation.source_port_ranges,
|
|
587
|
+
&value.protection,
|
|
588
|
+
)?;
|
|
589
|
+
require(
|
|
590
|
+
allocation.transit_source_address != value.snat_address
|
|
591
|
+
&& !local_addresses.contains(&allocation.transit_source_address),
|
|
592
|
+
)?;
|
|
593
|
+
if let Some(index) = addresses.insert(
|
|
594
|
+
allocation.transit_source_address.clone(),
|
|
595
|
+
handoff.link.interface_index,
|
|
596
|
+
) {
|
|
597
|
+
require(index == handoff.link.interface_index)?;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
unique_links(
|
|
602
|
+
value
|
|
603
|
+
.handoffs
|
|
604
|
+
.iter()
|
|
605
|
+
.map(|item| &item.link)
|
|
606
|
+
.chain(std::iter::once(&value.uplink)),
|
|
607
|
+
)?;
|
|
608
|
+
unique_allocations(
|
|
609
|
+
value
|
|
610
|
+
.handoffs
|
|
611
|
+
.iter()
|
|
612
|
+
.flat_map(|item| item.allocations.iter())
|
|
613
|
+
.map(|item| {
|
|
614
|
+
(
|
|
615
|
+
&item.lease,
|
|
616
|
+
item.transit_source_address.as_str(),
|
|
617
|
+
item.source_port_ranges.as_slice(),
|
|
618
|
+
)
|
|
619
|
+
}),
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
#[cfg(test)]
|
|
624
|
+
#[path = "egress_tests.rs"]
|
|
625
|
+
pub(crate) mod tests;
|
|
626
|
+
impl Policy {
|
|
627
|
+
pub fn prepare(self) -> Result<Prepared> {
|
|
628
|
+
use sha2::{Digest, Sha256};
|
|
629
|
+
let policy = self.normalize()?;
|
|
630
|
+
let mut hash = Sha256::new();
|
|
631
|
+
hash.update(b"smartnftables/managed-policy-v2\0");
|
|
632
|
+
hash.update(serde_json::to_vec(&policy).map_err(|_| Error::Invalid)?);
|
|
633
|
+
let result = Prepared {
|
|
634
|
+
policy,
|
|
635
|
+
digest: format!("sha256:{:x}", hash.finalize()),
|
|
636
|
+
};
|
|
637
|
+
result.program(&format!("snft_{}", "x".repeat(59)))?;
|
|
638
|
+
Ok(result)
|
|
639
|
+
}
|
|
640
|
+
pub fn normalize(mut self) -> Result<Self> {
|
|
641
|
+
require(self.schema_version == 2 && integer(self.revision))?;
|
|
642
|
+
match &mut self.scope {
|
|
643
|
+
Scope::RouterEgress(scope) => normalize_router(scope, self.revision)?,
|
|
644
|
+
Scope::HostTransit(scope) => normalize_host(scope)?,
|
|
645
|
+
}
|
|
646
|
+
Ok(self)
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
impl Prepared {
|
|
651
|
+
pub fn validate(&self) -> Result<()> {
|
|
652
|
+
require(self.policy.clone().prepare()? == *self)
|
|
653
|
+
}
|
|
654
|
+
pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<crate::wire::Attr>)>> {
|
|
655
|
+
compile::compile(self, table)
|
|
656
|
+
}
|
|
657
|
+
pub fn validate_interfaces(&self) -> Result<()> {
|
|
658
|
+
let links: Vec<&LocalLink> = match &self.policy.scope {
|
|
659
|
+
Scope::RouterEgress(scope) => scope
|
|
660
|
+
.links
|
|
661
|
+
.iter()
|
|
662
|
+
.chain(std::iter::once(&scope.handoff))
|
|
663
|
+
.collect(),
|
|
664
|
+
Scope::HostTransit(scope) => scope
|
|
665
|
+
.handoffs
|
|
666
|
+
.iter()
|
|
667
|
+
.map(|item| &item.link)
|
|
668
|
+
.chain(std::iter::once(&scope.uplink))
|
|
669
|
+
.collect(),
|
|
670
|
+
};
|
|
671
|
+
crate::wire::verify_bound_interfaces(links.into_iter().map(|link| {
|
|
672
|
+
crate::wire::BoundInterface {
|
|
673
|
+
index: link.interface_index,
|
|
674
|
+
name: &link.interface_name,
|
|
675
|
+
kind: &link.interface_kind,
|
|
676
|
+
mac: link.mac_address.as_deref(),
|
|
677
|
+
link_index: link.interface_link_index,
|
|
678
|
+
addresses: &link.required_ipv4_addresses,
|
|
679
|
+
}
|
|
680
|
+
}))
|
|
681
|
+
}
|
|
682
|
+
}
|