@push.rocks/smartnftables 1.2.1 → 1.3.1

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.
Files changed (86) hide show
  1. package/changelog.md +27 -0
  2. package/dist_rust/smartnftables_linux_amd64_musl +0 -0
  3. package/dist_rust/smartnftables_linux_amd64_musl.tsrust-build.json +14 -0
  4. package/dist_rust/smartnftables_linux_arm64_musl +0 -0
  5. package/dist_rust/smartnftables_linux_arm64_musl.tsrust-build.json +14 -0
  6. package/dist_ts/00_commitinfo_data.js +1 -1
  7. package/dist_ts/classes.managednftables.d.ts +21 -0
  8. package/dist_ts/classes.managednftables.js +176 -0
  9. package/dist_ts/index.d.ts +2 -0
  10. package/dist_ts/index.js +2 -1
  11. package/dist_ts/managed.types.d.ts +99 -0
  12. package/dist_ts/managed.types.js +2 -0
  13. package/dist_ts/plugins.d.ts +5 -2
  14. package/dist_ts/plugins.js +6 -3
  15. package/native-notices/crates/block-buffer-0.10.4/license-apache +201 -0
  16. package/native-notices/crates/block-buffer-0.10.4/license-mit +25 -0
  17. package/native-notices/crates/cfg-if-1.0.4/license-apache +201 -0
  18. package/native-notices/crates/cfg-if-1.0.4/license-mit +25 -0
  19. package/native-notices/crates/cpufeatures-0.2.17/license-apache +201 -0
  20. package/native-notices/crates/cpufeatures-0.2.17/license-mit +25 -0
  21. package/native-notices/crates/crypto-common-0.1.7/license-apache +201 -0
  22. package/native-notices/crates/crypto-common-0.1.7/license-mit +25 -0
  23. package/native-notices/crates/digest-0.10.7/license-apache +201 -0
  24. package/native-notices/crates/digest-0.10.7/license-mit +25 -0
  25. package/native-notices/crates/generic-array-0.14.7/license +21 -0
  26. package/native-notices/crates/itoa-1.0.18/license-apache +176 -0
  27. package/native-notices/crates/itoa-1.0.18/license-mit +23 -0
  28. package/native-notices/crates/libc-0.2.189/license-apache +176 -0
  29. package/native-notices/crates/libc-0.2.189/license-mit +25 -0
  30. package/native-notices/crates/memchr-2.8.3/copying +3 -0
  31. package/native-notices/crates/memchr-2.8.3/license-mit +21 -0
  32. package/native-notices/crates/proc-macro2-1.0.107/license-apache +176 -0
  33. package/native-notices/crates/proc-macro2-1.0.107/license-mit +23 -0
  34. package/native-notices/crates/quote-1.0.47/license-apache +176 -0
  35. package/native-notices/crates/quote-1.0.47/license-mit +23 -0
  36. package/native-notices/crates/serde-1.0.229/license-apache +176 -0
  37. package/native-notices/crates/serde-1.0.229/license-mit +23 -0
  38. package/native-notices/crates/serde_core-1.0.229/license-apache +176 -0
  39. package/native-notices/crates/serde_core-1.0.229/license-mit +23 -0
  40. package/native-notices/crates/serde_derive-1.0.229/license-apache +176 -0
  41. package/native-notices/crates/serde_derive-1.0.229/license-mit +23 -0
  42. package/native-notices/crates/serde_json-1.0.151/license-apache +176 -0
  43. package/native-notices/crates/serde_json-1.0.151/license-mit +23 -0
  44. package/native-notices/crates/sha2-0.10.9/license-apache +201 -0
  45. package/native-notices/crates/sha2-0.10.9/license-mit +27 -0
  46. package/native-notices/crates/syn-3.0.5/license-apache +176 -0
  47. package/native-notices/crates/syn-3.0.5/license-mit +23 -0
  48. package/native-notices/crates/typenum-1.20.1/license +1 -0
  49. package/native-notices/crates/typenum-1.20.1/license-apache +201 -0
  50. package/native-notices/crates/typenum-1.20.1/license-mit +21 -0
  51. package/native-notices/crates/unicode-ident-1.0.24/license-apache +176 -0
  52. package/native-notices/crates/unicode-ident-1.0.24/license-mit +23 -0
  53. package/native-notices/crates/unicode-ident-1.0.24/license-unicode +39 -0
  54. package/native-notices/crates/version_check-0.9.5/license-apache +201 -0
  55. package/native-notices/crates/version_check-0.9.5/license-mit +19 -0
  56. package/native-notices/crates/zmij-1.0.23/license-mit +23 -0
  57. package/native-notices/manifest.json +520 -0
  58. package/native-notices/readme.md +36 -0
  59. package/native-notices/rust-standard-library/addr2line-0.25.1-license-apache +201 -0
  60. package/native-notices/rust-standard-library/addr2line-0.25.1-license-mit +25 -0
  61. package/native-notices/rust-standard-library/cargo.lock +522 -0
  62. package/native-notices/rust-standard-library/compiler-builtins-license.txt +275 -0
  63. package/native-notices/rust-standard-library/dependency-notices.html +9072 -0
  64. package/native-notices/rust-standard-library/license-apache +176 -0
  65. package/native-notices/rust-standard-library/license-mit +25 -0
  66. package/native-notices/rust-standard-library/llvm-libunwind-license.txt +311 -0
  67. package/native-notices/rust-standard-library/musl-1.2.5-copyright.txt +193 -0
  68. package/native-notices/rust-standard-library/unicode-license.txt +39 -0
  69. package/package.json +22 -7
  70. package/readme.md +137 -2
  71. package/rust/.cargo/config.toml +2 -0
  72. package/rust/Cargo.lock +192 -0
  73. package/rust/Cargo.toml +13 -0
  74. package/rust/src/main.rs +141 -0
  75. package/rust/src/owner.rs +604 -0
  76. package/rust/src/owner_tests.rs +795 -0
  77. package/rust/src/policy.rs +427 -0
  78. package/rust/src/tests.rs +97 -0
  79. package/rust/src/wire.rs +437 -0
  80. package/scripts/check-native-notices.mjs +27 -0
  81. package/ts/00_commitinfo_data.ts +1 -1
  82. package/ts/classes.managednftables.ts +148 -0
  83. package/ts/index.ts +2 -0
  84. package/ts/managed.types.ts +87 -0
  85. package/ts/plugins.ts +6 -0
  86. /package/{license → license.md} +0 -0
@@ -0,0 +1,427 @@
1
+ use crate::wire::{self, Attr};
2
+ use crate::{Error, Result};
3
+ use serde::{Deserialize, Serialize};
4
+ use sha2::{Digest, Sha256};
5
+ use std::collections::BTreeSet;
6
+ use std::net::Ipv4Addr;
7
+
8
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
9
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
10
+ pub struct Endpoint {
11
+ pub id: String,
12
+ pub interface_index: u32,
13
+ pub interface_name: String,
14
+ pub interface_kind: String,
15
+ pub source_prefixes: Vec<String>,
16
+ }
17
+
18
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
19
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
20
+ pub struct Rule {
21
+ #[serde(deserialize_with = "required_nullable")]
22
+ pub source_endpoint: Option<String>,
23
+ #[serde(deserialize_with = "required_nullable")]
24
+ pub destination_endpoint: Option<String>,
25
+ pub source_prefix: String,
26
+ pub destination_prefix: String,
27
+ pub protocol: String,
28
+ #[serde(deserialize_with = "required_nullable")]
29
+ pub source_port: Option<u16>,
30
+ #[serde(deserialize_with = "required_nullable")]
31
+ pub destination_port: Option<u16>,
32
+ }
33
+
34
+ pub fn required_nullable<'de, D: serde::Deserializer<'de>, T: Deserialize<'de>>(
35
+ deserializer: D,
36
+ ) -> std::result::Result<Option<T>, D::Error> {
37
+ Option::<T>::deserialize(deserializer)
38
+ }
39
+
40
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
41
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
42
+ pub struct Policy {
43
+ pub schema_version: u8,
44
+ pub revision: u64,
45
+ pub endpoints: Vec<Endpoint>,
46
+ pub rules: Vec<Rule>,
47
+ }
48
+
49
+ #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
50
+ #[serde(rename_all = "camelCase", deny_unknown_fields)]
51
+ pub struct Prepared {
52
+ pub policy: Policy,
53
+ pub digest: String,
54
+ }
55
+
56
+ pub fn identifier(value: &str) -> bool {
57
+ !value.is_empty()
58
+ && value.len() <= 64
59
+ && value
60
+ .bytes()
61
+ .all(|v| v.is_ascii_alphanumeric() || b"_-".contains(&v))
62
+ }
63
+
64
+ pub fn prefix(value: &str) -> Result<(u32, u32)> {
65
+ let (address, bits) = value.split_once('/').ok_or(Error::Invalid)?;
66
+ let ip: Ipv4Addr = address.parse().map_err(|_| Error::Invalid)?;
67
+ let length: u32 = bits.parse().map_err(|_| Error::Invalid)?;
68
+ if !(1..=32).contains(&length)
69
+ || length.to_string() != bits
70
+ || ip.to_string() != address
71
+ || ip.is_loopback()
72
+ || ip.is_multicast()
73
+ || ip.is_unspecified()
74
+ || ip == Ipv4Addr::BROADCAST
75
+ {
76
+ return Err(Error::Invalid);
77
+ }
78
+ let mask = u32::MAX << (32 - length);
79
+ let address = u32::from(ip);
80
+ if address & mask != address {
81
+ return Err(Error::Invalid);
82
+ }
83
+ Ok((address, mask))
84
+ }
85
+
86
+ fn covers(parent: &str, child: &str) -> Result<bool> {
87
+ let (a, mask) = prefix(parent)?;
88
+ let (b, other_mask) = prefix(child)?;
89
+ Ok(other_mask & mask == mask && b & mask == a)
90
+ }
91
+
92
+ impl Policy {
93
+ pub fn prepare(mut self) -> Result<Prepared> {
94
+ if self.schema_version != 1
95
+ || self.revision == 0
96
+ || self.revision > 9_007_199_254_740_991
97
+ || self.endpoints.len() > 32
98
+ || self.rules.len() > 128
99
+ {
100
+ return Err(Error::Invalid);
101
+ }
102
+ let mut ids = BTreeSet::new();
103
+ let mut indices = BTreeSet::new();
104
+ let mut names = BTreeSet::new();
105
+ for endpoint in &mut self.endpoints {
106
+ if !identifier(&endpoint.id)
107
+ || !ids.insert(endpoint.id.clone())
108
+ || endpoint.interface_index == 0
109
+ || endpoint.interface_index > i32::MAX as u32
110
+ || !["veth", "tun"].contains(&endpoint.interface_kind.as_str())
111
+ || !indices.insert(endpoint.interface_index)
112
+ || endpoint.interface_name.len() > 15
113
+ || !identifier(&endpoint.interface_name)
114
+ || endpoint.interface_name == "lo"
115
+ || !names.insert(endpoint.interface_name.clone())
116
+ || endpoint.source_prefixes.is_empty()
117
+ || endpoint.source_prefixes.len() > 16
118
+ {
119
+ return Err(Error::Invalid);
120
+ }
121
+ endpoint.source_prefixes.sort();
122
+ for (index, item) in endpoint.source_prefixes.iter().enumerate() {
123
+ prefix(item)?;
124
+ for other in &endpoint.source_prefixes[..index] {
125
+ if covers(item, other)? || covers(other, item)? {
126
+ return Err(Error::Invalid);
127
+ }
128
+ }
129
+ }
130
+ }
131
+ self.endpoints.sort_by(|a, b| a.id.cmp(&b.id));
132
+ for (index, endpoint) in self.endpoints.iter().enumerate() {
133
+ for other in &self.endpoints[..index] {
134
+ for a in &endpoint.source_prefixes {
135
+ for b in &other.source_prefixes {
136
+ if covers(a, b)? || covers(b, a)? {
137
+ return Err(Error::Invalid);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+ self.rules.sort();
144
+ if self.rules.windows(2).any(|pair| pair[0] == pair[1]) {
145
+ return Err(Error::Invalid);
146
+ }
147
+ for rule in &self.rules {
148
+ if rule.source_endpoint.is_none() && rule.destination_endpoint.is_none()
149
+ || !["any", "tcp", "udp"].contains(&rule.protocol.as_str())
150
+ || rule.destination_port == Some(0)
151
+ || rule.source_port == Some(0)
152
+ || rule.protocol == "any"
153
+ && (rule.destination_port.is_some() || rule.source_port.is_some())
154
+ {
155
+ return Err(Error::Invalid);
156
+ }
157
+ for (id, cidr) in [
158
+ (&rule.source_endpoint, &rule.source_prefix),
159
+ (&rule.destination_endpoint, &rule.destination_prefix),
160
+ ] {
161
+ prefix(cidr)?;
162
+ if let Some(id) = id {
163
+ let endpoint = self
164
+ .endpoints
165
+ .iter()
166
+ .find(|ep| &ep.id == id)
167
+ .ok_or(Error::Invalid)?;
168
+ if !endpoint
169
+ .source_prefixes
170
+ .iter()
171
+ .any(|parent| covers(parent, cidr).unwrap_or(false))
172
+ {
173
+ return Err(Error::Invalid);
174
+ }
175
+ } else {
176
+ // A remote/host selector cannot grant an overlapping local source
177
+ // an interface-independent route through another workload's grant.
178
+ for endpoint in &self.endpoints {
179
+ for local in &endpoint.source_prefixes {
180
+ if covers(local, cidr)? || covers(cidr, local)? {
181
+ return Err(Error::Invalid);
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }
187
+ }
188
+ let digest = format!(
189
+ "sha256:{:x}",
190
+ Sha256::digest(serde_json::to_vec(&self).map_err(|_| Error::Invalid)?)
191
+ );
192
+ let prepared = Prepared {
193
+ policy: self,
194
+ digest,
195
+ };
196
+ // Compile during preparation; an oversized native batch never reaches the kernel.
197
+ let program = prepared.program(&format!("snft_{}", "x".repeat(59)))?;
198
+ // Reserve enough of the 240 KiB batch for deleting a previous maximum
199
+ // graph, then installing this complete graph in the same transaction.
200
+ if program.len() > 768
201
+ || program
202
+ .iter()
203
+ .map(|(_, attrs)| wire::encode_attrs(attrs).len() + 20)
204
+ .sum::<usize>()
205
+ > 100_000
206
+ {
207
+ return Err(Error::Invalid);
208
+ }
209
+ Ok(prepared)
210
+ }
211
+ }
212
+
213
+ fn expr(name: &str, data: Vec<Attr>) -> Attr {
214
+ Attr::nested(1, vec![Attr::string(1, name), Attr::nested(2, data)])
215
+ }
216
+ fn compare(value: Vec<u8>) -> Attr {
217
+ expr(
218
+ "cmp",
219
+ vec![
220
+ Attr::u32(1, 1),
221
+ Attr::u32(2, 0),
222
+ Attr::nested(3, vec![Attr::bytes(1, value)]),
223
+ ],
224
+ )
225
+ }
226
+ fn meta(key: u32, value: Vec<u8>) -> Vec<Attr> {
227
+ vec![
228
+ expr("meta", vec![Attr::u32(1, 1), Attr::u32(2, key)]),
229
+ compare(value),
230
+ ]
231
+ }
232
+ fn interface(endpoint: &Endpoint, incoming: bool) -> Vec<Attr> {
233
+ let mut result = meta(
234
+ if incoming { 4 } else { 5 },
235
+ endpoint.interface_index.to_ne_bytes().to_vec(),
236
+ );
237
+ let mut name = endpoint.interface_name.as_bytes().to_vec();
238
+ name.push(0);
239
+ result.extend(meta(if incoming { 6 } else { 7 }, name));
240
+ result
241
+ }
242
+ fn ipv4() -> Vec<Attr> {
243
+ meta(15, vec![libc::NFPROTO_IPV4 as u8])
244
+ }
245
+ fn address(cidr: &str, source: bool) -> Result<Vec<Attr>> {
246
+ let (address, mask) = prefix(cidr)?;
247
+ Ok(vec![
248
+ expr(
249
+ "payload",
250
+ vec![
251
+ Attr::u32(1, 1),
252
+ Attr::u32(2, 1),
253
+ Attr::u32(3, if source { 12 } else { 16 }),
254
+ Attr::u32(4, 4),
255
+ ],
256
+ ),
257
+ expr(
258
+ "bitwise",
259
+ vec![
260
+ Attr::u32(1, 1),
261
+ Attr::u32(2, 1),
262
+ Attr::u32(3, 4),
263
+ Attr::nested(4, vec![Attr::bytes(1, mask.to_be_bytes().to_vec())]),
264
+ Attr::nested(5, vec![Attr::bytes(1, vec![0; 4])]),
265
+ Attr::u32(6, 0),
266
+ ],
267
+ ),
268
+ compare(address.to_be_bytes().to_vec()),
269
+ ])
270
+ }
271
+ fn verdict(code: i32, chain: Option<&str>) -> Attr {
272
+ let mut data = vec![Attr::u32(1, code as u32)];
273
+ if let Some(chain) = chain {
274
+ data.push(Attr::string(2, chain));
275
+ }
276
+ expr(
277
+ "immediate",
278
+ vec![
279
+ Attr::u32(1, 0),
280
+ Attr::nested(2, vec![Attr::nested(2, data)]),
281
+ ],
282
+ )
283
+ }
284
+
285
+ impl Prepared {
286
+ pub fn validate(&self) -> Result<()> {
287
+ if self.policy.clone().prepare()? != *self {
288
+ return Err(Error::Invalid);
289
+ }
290
+ Ok(())
291
+ }
292
+ pub fn validate_interfaces(&self) -> Result<()> {
293
+ wire::verify_interfaces(self.policy.endpoints.iter().map(|endpoint| {
294
+ (
295
+ endpoint.interface_index,
296
+ endpoint.interface_name.as_str(),
297
+ endpoint.interface_kind.as_str(),
298
+ )
299
+ }))
300
+ }
301
+ /// Only typed policy can create these private UAPI messages. No raw command API exists.
302
+ pub fn program(&self, table: &str) -> Result<Vec<(u16, Vec<Attr>)>> {
303
+ let mut program = Vec::new();
304
+ let mut ordinal = 0;
305
+ let mut rule =
306
+ |chain: &str, expressions: Vec<Attr>, program: &mut Vec<(u16, Vec<Attr>)>| {
307
+ let stamp = format!("snft1:{}:{}:{ordinal}", self.policy.revision, self.digest);
308
+ ordinal += 1;
309
+ program.push((
310
+ 6,
311
+ vec![
312
+ Attr::string(1, table),
313
+ Attr::string(2, chain),
314
+ Attr::nested(4, expressions),
315
+ Attr::bytes(7, stamp.into_bytes()),
316
+ ],
317
+ ));
318
+ };
319
+ for (chain, hook) in [("input", 1), ("forward", 2), ("output", 3)] {
320
+ program.push((
321
+ 3,
322
+ vec![
323
+ Attr::string(1, table),
324
+ Attr::string(3, chain),
325
+ Attr::nested(4, vec![Attr::u32(1, hook), Attr::u32(2, (-150_i32) as u32)]),
326
+ Attr::u32(5, 1),
327
+ Attr::string(7, "filter"),
328
+ Attr::u32(10, 1),
329
+ ],
330
+ ));
331
+ }
332
+ for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
333
+ for incoming in [true, false] {
334
+ let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
335
+ program.push((3, vec![Attr::string(1, table), Attr::string(3, &name)]));
336
+ for cidr in &endpoint.source_prefixes {
337
+ let mut expressions = ipv4();
338
+ expressions.extend(address(cidr, incoming)?);
339
+ expressions.push(verdict(-5, None));
340
+ rule(&name, expressions, &mut program);
341
+ }
342
+ rule(&name, vec![verdict(0, None)], &mut program);
343
+ }
344
+ }
345
+ for chain in ["input", "forward", "output"] {
346
+ for (index, endpoint) in self.policy.endpoints.iter().enumerate() {
347
+ for incoming in [true, false] {
348
+ if (chain == "input" && !incoming) || (chain == "output" && incoming) {
349
+ continue;
350
+ }
351
+ let name = format!("guard_{index}_{}", if incoming { "in" } else { "out" });
352
+ let mut expressions = interface(endpoint, incoming);
353
+ expressions.push(verdict(-3, Some(&name)));
354
+ rule(chain, expressions, &mut program);
355
+ }
356
+ }
357
+ for grant in &self.policy.rules {
358
+ if chain == "input" && grant.destination_endpoint.is_some()
359
+ || chain == "output" && grant.source_endpoint.is_some()
360
+ || chain == "forward"
361
+ && (grant.source_endpoint.is_none() || grant.destination_endpoint.is_none())
362
+ {
363
+ continue;
364
+ }
365
+ let mut expressions = ipv4();
366
+ for (id, incoming) in [
367
+ (&grant.source_endpoint, true),
368
+ (&grant.destination_endpoint, false),
369
+ ] {
370
+ if let Some(id) = id {
371
+ let endpoint = self
372
+ .policy
373
+ .endpoints
374
+ .iter()
375
+ .find(|ep| &ep.id == id)
376
+ .ok_or(Error::Invalid)?;
377
+ expressions.extend(interface(endpoint, incoming));
378
+ }
379
+ }
380
+ expressions.extend(address(&grant.source_prefix, true)?);
381
+ expressions.extend(address(&grant.destination_prefix, false)?);
382
+ if grant.protocol != "any" {
383
+ expressions
384
+ .extend(meta(16, vec![if grant.protocol == "tcp" { 6 } else { 17 }]));
385
+ }
386
+ for (port, offset) in [(grant.source_port, 0), (grant.destination_port, 2)] {
387
+ if let Some(port) = port {
388
+ expressions.push(expr(
389
+ "payload",
390
+ vec![
391
+ Attr::u32(1, 1),
392
+ Attr::u32(2, 2),
393
+ Attr::u32(3, offset),
394
+ Attr::u32(4, 2),
395
+ ],
396
+ ));
397
+ expressions.push(compare(port.to_be_bytes().to_vec()));
398
+ }
399
+ }
400
+ expressions.push(verdict(1, None));
401
+ rule(chain, expressions, &mut program);
402
+ }
403
+ for endpoint in &self.policy.endpoints {
404
+ for incoming in [true, false] {
405
+ if (chain == "input" && !incoming) || (chain == "output" && incoming) {
406
+ continue;
407
+ }
408
+ // Either selector matching is enough to retain denial after rename/reuse.
409
+ for mut expressions in [
410
+ meta(
411
+ if incoming { 4 } else { 5 },
412
+ endpoint.interface_index.to_ne_bytes().to_vec(),
413
+ ),
414
+ meta(
415
+ if incoming { 6 } else { 7 },
416
+ [endpoint.interface_name.as_bytes(), &[0]].concat(),
417
+ ),
418
+ ] {
419
+ expressions.push(verdict(0, None));
420
+ rule(chain, expressions, &mut program);
421
+ }
422
+ }
423
+ }
424
+ }
425
+ Ok(program)
426
+ }
427
+ }
@@ -0,0 +1,97 @@
1
+ use crate::{decode, policy::Policy, wire, Error};
2
+
3
+ fn policy() -> Policy {
4
+ serde_json::from_str(r#"{"schemaVersion":1,"revision":1,"endpoints":[{"id":"a","interfaceIndex":2,"interfaceName":"worka","interfaceKind":"veth","sourcePrefixes":["10.81.0.2/32"]},{"id":"b","interfaceIndex":3,"interfaceName":"workb","interfaceKind":"veth","sourcePrefixes":["10.81.0.3/32"]}],"rules":[{"sourceEndpoint":"a","destinationEndpoint":"b","sourcePrefix":"10.81.0.2/32","destinationPrefix":"10.81.0.3/32","protocol":"udp","sourcePort":null,"destinationPort":53}]}"#).unwrap()
5
+ }
6
+
7
+ #[test]
8
+ fn canonical_policy_rejects_ambiguous_or_unbound_grants() {
9
+ let prepared = policy().prepare().unwrap();
10
+ let mut reordered = policy();
11
+ reordered.endpoints.reverse();
12
+ assert_eq!(reordered.prepare().unwrap(), prepared);
13
+ let mut altered = prepared.clone();
14
+ altered.policy.revision += 1;
15
+ assert_eq!(altered.validate(), Err(Error::Invalid));
16
+ let mut value = policy();
17
+ value.rules[0].source_endpoint = None;
18
+ assert_eq!(value.prepare(), Err(Error::Invalid));
19
+ let mut value = policy();
20
+ value.rules[0].source_prefix = "10.81.0.3/32".into();
21
+ assert_eq!(value.prepare(), Err(Error::Invalid));
22
+ let mut value = policy();
23
+ value.endpoints[1].source_prefixes = vec!["10.81.0.0/24".into()];
24
+ assert_eq!(value.prepare(), Err(Error::Invalid));
25
+ let mut value = policy();
26
+ value.rules.push(value.rules[0].clone());
27
+ assert_eq!(value.prepare(), Err(Error::Invalid));
28
+ let mut value = policy();
29
+ value.rules[0].protocol = "any".into();
30
+ assert_eq!(value.prepare(), Err(Error::Invalid));
31
+ }
32
+
33
+ #[test]
34
+ fn raw_input_rejects_duplicates_unknown_fields_and_omitted_nulls() {
35
+ for json in [
36
+ r#"{"schemaVersion":1,"schemaVersion":1,"revision":1,"endpoints":[],"rules":[]}"#,
37
+ r#"{"schemaVersion":1,"revision":1,"endpoints":[],"rules":[],"commands":[]}"#,
38
+ ] {
39
+ assert!(
40
+ decode::<Policy>(serde_json::value::RawValue::from_string(json.into()).unwrap())
41
+ .is_err()
42
+ );
43
+ }
44
+ let mut value = serde_json::to_value(policy()).unwrap();
45
+ value["rules"][0]
46
+ .as_object_mut()
47
+ .unwrap()
48
+ .remove("sourcePort");
49
+ assert!(serde_json::from_value::<Policy>(value).is_err());
50
+ let prepared = policy().prepare().unwrap();
51
+ assert!(serde_json::from_value::<crate::owner::Transition>(
52
+ serde_json::json!({"target":prepared})
53
+ )
54
+ .is_err());
55
+ }
56
+
57
+ #[test]
58
+ fn protocol_rejects_malformed_attributes_and_noncanonical_prefixes() {
59
+ for cidr in [
60
+ "0.0.0.0/0",
61
+ "127.0.0.1/32",
62
+ "224.0.0.0/4",
63
+ "10.1.0.1/24",
64
+ "10.1.0.0/024",
65
+ "::1/128",
66
+ ] {
67
+ assert_eq!(crate::policy::prefix(cidr), Err(Error::Invalid));
68
+ }
69
+ for bytes in [vec![0], vec![3, 0, 1, 0], vec![12, 0, 1, 0, 0, 0, 0, 0]] {
70
+ assert_eq!(wire::attrs(&bytes), Err(Error::Protocol));
71
+ }
72
+ let value = vec![wire::Attr::string(1, "exact"), wire::Attr::u32(2, 42)];
73
+ assert_eq!(wire::attrs(&wire::encode_attrs(&value)).unwrap(), value);
74
+ assert_eq!(
75
+ wire::number(&[wire::Attr::u32(1, 1), wire::Attr::u32(1, 2)], 1),
76
+ Err(Error::Protocol)
77
+ );
78
+ }
79
+
80
+ #[test]
81
+ fn compiler_bounds_full_policy_and_keeps_directed_port_grants() {
82
+ let prepared = policy().prepare().unwrap();
83
+ let program = prepared.program("snft_test").unwrap();
84
+ assert_eq!(program.iter().filter(|(kind, _)| *kind == 3).count(), 7);
85
+ assert!(program.iter().all(|(kind, _)| [3, 6].contains(kind)));
86
+ for (_, attributes) in program {
87
+ let encoded = wire::encode_attrs(&attributes);
88
+ assert!(encoded.len() < 65_535);
89
+ assert_eq!(wire::attrs(&encoded).unwrap(), attributes);
90
+ }
91
+ let mut value = policy();
92
+ value.endpoints[0].interface_name = "lo".into();
93
+ assert_eq!(value.prepare(), Err(Error::Invalid));
94
+ let mut value = policy();
95
+ value.revision = 9_007_199_254_740_992;
96
+ assert_eq!(value.prepare(), Err(Error::Invalid));
97
+ }