312 lines
9.7 KiB
Rust
312 lines
9.7 KiB
Rust
use base64::Engine;
|
|
use base64::engine::general_purpose::STANDARD_NO_PAD;
|
|
|
|
use rpki::data_model::resources::as_resources::Asn;
|
|
use rpki::data_model::resources::ip_resources::{IPAddress, IPAddressPrefix};
|
|
use rpki::rtr::payload::{Aspa, Payload, RouteOrigin, RouterKey, Ski};
|
|
use rpki::slurm::file::{SlurmFile, SlurmVersion};
|
|
|
|
fn sample_spki() -> Vec<u8> {
|
|
vec![
|
|
0x30, 0x13, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01,
|
|
0x05, 0x00, 0x03, 0x02, 0x00, 0x00,
|
|
]
|
|
}
|
|
|
|
fn sample_ski() -> [u8; 20] {
|
|
[
|
|
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
|
|
0xff, 0x10, 0x20, 0x30, 0x40,
|
|
]
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rfc8416_v1_slurm() {
|
|
let ski_hex = hex::encode(sample_ski());
|
|
let router_public_key = STANDARD_NO_PAD.encode(sample_spki());
|
|
let json = format!(
|
|
r#"{{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {{
|
|
"prefixFilters": [
|
|
{{ "prefix": "192.0.2.0/24", "asn": 64496, "comment": "drop roa" }}
|
|
],
|
|
"bgpsecFilters": [
|
|
{{ "asn": 64497, "SKI": "{ski_hex}" }}
|
|
]
|
|
}},
|
|
"locallyAddedAssertions": {{
|
|
"prefixAssertions": [
|
|
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
|
],
|
|
"bgpsecAssertions": [
|
|
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{router_public_key}" }}
|
|
]
|
|
}}
|
|
}}"#
|
|
);
|
|
|
|
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
|
|
|
assert_eq!(slurm.version(), SlurmVersion::V1);
|
|
assert_eq!(slurm.validation_output_filters().prefix_filters.len(), 1);
|
|
assert_eq!(slurm.validation_output_filters().bgpsec_filters.len(), 1);
|
|
assert!(slurm.validation_output_filters().aspa_filters.is_empty());
|
|
assert_eq!(slurm.locally_added_assertions().prefix_assertions.len(), 1);
|
|
assert_eq!(slurm.locally_added_assertions().bgpsec_assertions.len(), 1);
|
|
assert!(slurm.locally_added_assertions().aspa_assertions.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn parses_v2_slurm_with_aspa_extensions() {
|
|
let json = r#"{
|
|
"slurmVersion": 2,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": [],
|
|
"aspaFilters": [
|
|
{ "customerAsn": 64496 }
|
|
]
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": [],
|
|
"aspaAssertions": [
|
|
{ "customerAsn": 64510, "providerAsns": [64511, 64512] }
|
|
]
|
|
}
|
|
}"#;
|
|
|
|
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
|
|
|
assert_eq!(slurm.version(), SlurmVersion::V2);
|
|
assert_eq!(slurm.validation_output_filters().aspa_filters.len(), 1);
|
|
assert_eq!(slurm.locally_added_assertions().aspa_assertions.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_v1_file_with_aspa_members() {
|
|
let json = r#"{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": [],
|
|
"aspaFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": []
|
|
}
|
|
}"#;
|
|
|
|
let err = SlurmFile::from_slice(json.as_bytes()).unwrap_err();
|
|
assert!(err.to_string().contains("unknown field"));
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_non_canonical_prefixes_and_unsorted_aspa_providers() {
|
|
let non_canonical = r#"{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [
|
|
{ "prefix": "192.0.2.1/24" }
|
|
],
|
|
"bgpsecFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": []
|
|
}
|
|
}"#;
|
|
let non_canonical_err = SlurmFile::from_slice(non_canonical.as_bytes()).unwrap_err();
|
|
assert!(non_canonical_err.to_string().contains("not canonical"));
|
|
|
|
let unsorted_aspa = r#"{
|
|
"slurmVersion": 2,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": [],
|
|
"aspaFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": [],
|
|
"aspaAssertions": [
|
|
{ "customerAsn": 64500, "providerAsns": [64502, 64501] }
|
|
]
|
|
}
|
|
}"#;
|
|
let aspa_err = SlurmFile::from_slice(unsorted_aspa.as_bytes()).unwrap_err();
|
|
assert!(aspa_err.to_string().contains("strictly increasing"));
|
|
}
|
|
|
|
#[test]
|
|
fn applies_filters_before_assertions_and_excludes_duplicates() {
|
|
let ski = Ski::from_bytes(sample_ski());
|
|
let spki = sample_spki();
|
|
let spki_b64 = STANDARD_NO_PAD.encode(&spki);
|
|
let ski_hex = hex::encode(sample_ski());
|
|
let json = format!(
|
|
r#"{{
|
|
"slurmVersion": 2,
|
|
"validationOutputFilters": {{
|
|
"prefixFilters": [
|
|
{{ "prefix": "192.0.2.0/24", "asn": 64496 }}
|
|
],
|
|
"bgpsecFilters": [
|
|
{{ "SKI": "{ski_hex}" }}
|
|
],
|
|
"aspaFilters": [
|
|
{{ "customerAsn": 64496 }}
|
|
]
|
|
}},
|
|
"locallyAddedAssertions": {{
|
|
"prefixAssertions": [
|
|
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }},
|
|
{{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }}
|
|
],
|
|
"bgpsecAssertions": [
|
|
{{ "asn": 64501, "SKI": "{ski_hex}", "routerPublicKey": "{spki_b64}" }}
|
|
],
|
|
"aspaAssertions": [
|
|
{{ "customerAsn": 64510, "providerAsns": [64511, 64512] }}
|
|
]
|
|
}}
|
|
}}"#
|
|
);
|
|
let slurm = SlurmFile::from_slice(json.as_bytes()).unwrap();
|
|
|
|
let input = vec![
|
|
Payload::RouteOrigin(RouteOrigin::new(
|
|
IPAddressPrefix::new(IPAddress::from_ipv4("192.0.2.0".parse().unwrap()), 24),
|
|
24,
|
|
Asn::from(64496u32),
|
|
)),
|
|
Payload::RouteOrigin(RouteOrigin::new(
|
|
IPAddressPrefix::new(IPAddress::from_ipv4("203.0.113.0".parse().unwrap()), 24),
|
|
24,
|
|
Asn::from(64497u32),
|
|
)),
|
|
Payload::RouterKey(RouterKey::new(ski, Asn::from(64497u32), spki.clone())),
|
|
Payload::Aspa(Aspa::new(Asn::from(64496u32), vec![Asn::from(64498u32)])),
|
|
];
|
|
|
|
let output = slurm.apply(&input);
|
|
|
|
assert_eq!(output.len(), 4);
|
|
assert!(output.iter().any(|payload| matches!(
|
|
payload,
|
|
Payload::RouteOrigin(route_origin)
|
|
if route_origin.prefix().address() == IPAddress::from_ipv4("203.0.113.0".parse().unwrap())
|
|
)));
|
|
assert!(output.iter().any(|payload| matches!(
|
|
payload,
|
|
Payload::RouteOrigin(route_origin)
|
|
if route_origin.prefix().address() == IPAddress::from_ipv4("198.51.100.0".parse().unwrap())
|
|
)));
|
|
assert!(output.iter().any(|payload| matches!(
|
|
payload,
|
|
Payload::RouterKey(router_key)
|
|
if router_key.asn() == Asn::from(64501u32)
|
|
)));
|
|
assert!(output.iter().any(|payload| matches!(
|
|
payload,
|
|
Payload::Aspa(aspa)
|
|
if aspa.customer_asn() == Asn::from(64510u32)
|
|
)));
|
|
}
|
|
|
|
#[test]
|
|
fn merges_multiple_slurm_files_without_conflict() {
|
|
let a = r#"{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [
|
|
{ "prefix": "198.51.100.0/24", "asn": 64500, "maxPrefixLength": 24 }
|
|
],
|
|
"bgpsecAssertions": []
|
|
}
|
|
}"#;
|
|
|
|
let b = r#"{
|
|
"slurmVersion": 2,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": [],
|
|
"aspaFilters": [
|
|
{ "customerAsn": 64510 }
|
|
]
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": [],
|
|
"aspaAssertions": []
|
|
}
|
|
}"#;
|
|
|
|
let merged = SlurmFile::merge_named(vec![
|
|
(
|
|
"a.slurm".to_string(),
|
|
SlurmFile::from_slice(a.as_bytes()).unwrap(),
|
|
),
|
|
(
|
|
"b.slurm".to_string(),
|
|
SlurmFile::from_slice(b.as_bytes()).unwrap(),
|
|
),
|
|
])
|
|
.unwrap();
|
|
|
|
assert_eq!(merged.version(), SlurmVersion::V2);
|
|
assert_eq!(merged.locally_added_assertions().prefix_assertions.len(), 1);
|
|
assert_eq!(merged.validation_output_filters().aspa_filters.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_conflicting_multiple_slurm_files() {
|
|
let a = r#"{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [],
|
|
"bgpsecFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [
|
|
{ "prefix": "10.0.0.0/8", "asn": 64500, "maxPrefixLength": 24 }
|
|
],
|
|
"bgpsecAssertions": []
|
|
}
|
|
}"#;
|
|
|
|
let b = r#"{
|
|
"slurmVersion": 1,
|
|
"validationOutputFilters": {
|
|
"prefixFilters": [
|
|
{ "prefix": "10.0.0.0/16" }
|
|
],
|
|
"bgpsecFilters": []
|
|
},
|
|
"locallyAddedAssertions": {
|
|
"prefixAssertions": [],
|
|
"bgpsecAssertions": []
|
|
}
|
|
}"#;
|
|
|
|
let err = SlurmFile::merge_named(vec![
|
|
(
|
|
"a.slurm".to_string(),
|
|
SlurmFile::from_slice(a.as_bytes()).unwrap(),
|
|
),
|
|
(
|
|
"b.slurm".to_string(),
|
|
SlurmFile::from_slice(b.as_bytes()).unwrap(),
|
|
),
|
|
])
|
|
.unwrap_err();
|
|
|
|
assert!(err.to_string().contains("conflicting SLURM files"));
|
|
}
|