diff --git a/src/cli/options.rs b/src/cli/options.rs index d9a69be6..d8d68082 100644 --- a/src/cli/options.rs +++ b/src/cli/options.rs @@ -14,7 +14,7 @@ use crate::commons::api::RepositoryUpdate; use crate::commons::api::{ AddChildRequest, AuthorizationFmtError, CertAuthInit, ChildAuthRequest, ChildHandle, Handle, ParentCaContact, ParentCaReq, ParentHandle, PublisherHandle, ResSetErr, ResourceSet, - RouteAuthorizationUpdates, Token, UpdateChildRequest, + RoaDefinitionUpdates, Token, UpdateChildRequest, }; use crate::commons::remote::id::IdCert; use crate::commons::remote::rfc8183; @@ -919,7 +919,7 @@ impl Options { let path = matches.value_of("delta").map(PathBuf::from).unwrap(); let bytes = file::read(&path)?; let updates_str = unsafe { from_utf8_unchecked(&bytes) }; - RouteAuthorizationUpdates::from_str(updates_str)? + RoaDefinitionUpdates::from_str(updates_str)? }; let command = Command::CertAuth(CaCommand::RouteAuthorizationsUpdate(my_ca, updates)); @@ -1175,7 +1175,7 @@ pub enum CaCommand { RouteAuthorizationsList(Handle), // Update the Route Authorizations for this CA - RouteAuthorizationsUpdate(Handle, RouteAuthorizationUpdates), + RouteAuthorizationsUpdate(Handle, RoaDefinitionUpdates), // Show details for this CA Show(Handle), diff --git a/src/cli/report.rs b/src/cli/report.rs index 02e49d87..7c2e524c 100644 --- a/src/cli/report.rs +++ b/src/cli/report.rs @@ -2,10 +2,11 @@ use std::str::{from_utf8_unchecked, FromStr}; use crate::commons::api::{ CaRepoDetails, CertAuthHistory, CertAuthInfo, CertAuthList, CurrentObjects, CurrentRepoState, - ParentCaContact, PublisherDetails, PublisherList, RepositoryContact, RouteAuthorization, + ParentCaContact, PublisherDetails, PublisherList, RepositoryContact, }; use crate::commons::remote::api::ClientInfo; use crate::commons::remote::rfc8183; +use crate::daemon::ca::RouteAuthorization; //------------ ApiResponse --------------------------------------------------- diff --git a/src/commons/api/ca.rs b/src/commons/api/ca.rs index d5ba80af..1bf639de 100644 --- a/src/commons/api/ca.rs +++ b/src/commons/api/ca.rs @@ -24,12 +24,12 @@ use crate::commons::api::publication; use crate::commons::api::publication::Publish; use crate::commons::api::{ Base64, HexEncodedHash, IssuanceRequest, ListReply, ParentHandle, RepositoryContact, - RequestResourceLimit, RouteAuthorization, + RequestResourceLimit, RoaDefinition, }; use crate::commons::eventsourcing::AggregateHistory; use crate::commons::remote::id::IdCert; use crate::commons::util::ext_serde; -use crate::daemon::ca::{self, CertAuth, Signer}; +use crate::daemon::ca::{self, CertAuth, RouteAuthorization, Signer}; //------------ ResourceClassName ------------------------------------------- @@ -617,6 +617,12 @@ impl From<&RouteAuthorization> for ObjectName { } } +impl From<&RoaDefinition> for ObjectName { + fn from(def: &RoaDefinition) -> Self { + ObjectName(format!("{}.roa", hex::encode(def.to_string()))) + } +} + impl Into for ObjectName { fn into(self) -> Bytes { Bytes::from(self.0) diff --git a/src/commons/api/roas.rs b/src/commons/api/roas.rs index 54146e28..d9d549e8 100644 --- a/src/commons/api/roas.rs +++ b/src/commons/api/roas.rs @@ -1,15 +1,93 @@ use std::collections::HashSet; use std::fmt; -use std::hash::{Hash, Hasher}; use std::net::IpAddr; +use std::ops::Deref; use std::str::FromStr; -use serde::de; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use rpki::resources::{AddressFamily, AsBlocks, AsId, IpBlocks, IpBlocksBuilder, Prefix}; +use rpki::resources::{AsBlocks, AsId, IpBlocks, IpBlocksBuilder, Prefix}; -use crate::commons::api::ca::ResourceSet; +use crate::commons::api::ResourceSet; + +//------------ RoaDefinition ----------------------------------------------- + +/// This type defines the definition of a Route Origin Authorization (ROA), i.e. +/// the originating asn, IPv4 or IPv6 prefix, and optionally a max length. +#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct RoaDefinition { + asn: AsNumber, + prefix: TypedPrefix, + #[serde(skip_serializing_if = "Option::is_none")] + max_length: Option, +} + +impl RoaDefinition { + pub fn new(asn: AsNumber, prefix: TypedPrefix, max_length: Option) -> Self { + RoaDefinition { + asn, + prefix, + max_length, + } + } + + pub fn asn(&self) -> AsNumber { + self.asn + } + + pub fn prefix(&self) -> TypedPrefix { + self.prefix + } + + pub fn max_length(&self) -> Option { + self.max_length + } +} + +impl FromStr for RoaDefinition { + type Err = AuthorizationFmtError; + + // "192.168.0.0/16 => 64496" + fn from_str(s: &str) -> Result { + let mut parts = s.split("=>"); + + let prefix_part = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?; + let mut prefix_parts = prefix_part.split('-'); + let prefix_str = prefix_parts + .next() + .ok_or_else(|| AuthorizationFmtError::auth(s))?; + + let prefix = TypedPrefix::from_str(&prefix_str.trim())?; + + let max_length = match prefix_parts.next() { + None => None, + Some(length_str) => { + Some(u8::from_str(&length_str.trim()).map_err(|_| AuthorizationFmtError::auth(s))?) + } + }; + + let asn_str = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?; + if parts.next().is_some() { + return Err(AuthorizationFmtError::auth(s)); + } + let origin = AsNumber::from_str(&asn_str.trim())?; + + Ok(RoaDefinition { + asn: origin, + prefix, + max_length, + }) + } +} + +impl fmt::Display for RoaDefinition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.max_length { + None => write!(f, "{} => {}", self.prefix, self.asn), + Some(length) => write!(f, "{}-{} => {}", self.prefix, length, self.asn), + } + } +} //------------ RouteAuthorizationUpdates ----------------------------------- @@ -22,19 +100,19 @@ use crate::commons::api::ca::ResourceSet; /// all authorisations for a given prefix are published together in order to /// avoid invalidating announcements. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct RouteAuthorizationUpdates { - added: HashSet, - removed: HashSet, +pub struct RoaDefinitionUpdates { + added: HashSet, + removed: HashSet, } -impl RouteAuthorizationUpdates { - pub fn new(added: HashSet, removed: HashSet) -> Self { - RouteAuthorizationUpdates { added, removed } +impl RoaDefinitionUpdates { + pub fn new(added: HashSet, removed: HashSet) -> Self { + RoaDefinitionUpdates { added, removed } } /// Unpack this and return all added (left), and all removed (right) route /// authorizations. - pub fn unpack(self) -> (HashSet, HashSet) { + pub fn unpack(self) -> (HashSet, HashSet) { (self.added, self.removed) } @@ -42,25 +120,25 @@ impl RouteAuthorizationUpdates { Self::default() } - pub fn add(&mut self, add: RouteAuthorization) { + pub fn add(&mut self, add: RoaDefinition) { self.added.insert(add); } - pub fn remove(&mut self, rem: RouteAuthorization) { + pub fn remove(&mut self, rem: RoaDefinition) { self.removed.insert(rem); } } -impl Default for RouteAuthorizationUpdates { +impl Default for RoaDefinitionUpdates { fn default() -> Self { - RouteAuthorizationUpdates { + RoaDefinitionUpdates { added: HashSet::new(), removed: HashSet::new(), } } } -impl fmt::Display for RouteAuthorizationUpdates { +impl fmt::Display for RoaDefinitionUpdates { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { for a in &self.added { writeln!(f, "A: {}", a)?; @@ -72,7 +150,7 @@ impl fmt::Display for RouteAuthorizationUpdates { } } -impl FromStr for RouteAuthorizationUpdates { +impl FromStr for RoaDefinitionUpdates { type Err = AuthorizationFmtError; fn from_str(s: &str) -> Result { @@ -91,71 +169,87 @@ impl FromStr for RouteAuthorizationUpdates { } else if line.starts_with("A:") { let line = &line[2..]; let line = line.trim(); - let auth = RouteAuthorization::from_str(line)?; + let auth = RoaDefinition::from_str(line)?; added.insert(auth); } else if line.starts_with("R:") { let line = &line[2..]; let line = line.trim(); - let auth = RouteAuthorization::from_str(line)?; + let auth = RoaDefinition::from_str(line)?; removed.insert(auth); } else { return Err(AuthorizationFmtError::delta(line)); } } - Ok(RouteAuthorizationUpdates { added, removed }) + Ok(RoaDefinitionUpdates { added, removed }) } } -//------------ RouteAuthorization ------------------------------------------ - -/// This type defines a prefix and optional maximum length (other than the -/// prefix length) which is to be authorized for the given origin ASN. +//------------ TypedPrefix ------------------------------------------------- #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub struct RouteAuthorization { - origin: AsNumber, - prefix: RoaPrefix, +pub enum TypedPrefix { + V4(Ipv4Prefix), + V6(Ipv6Prefix), } -impl RouteAuthorization { - pub fn new(origin: AsNumber, prefix: RoaPrefix) -> Self { - RouteAuthorization { origin, prefix } +impl TypedPrefix { + pub fn prefix(&self) -> &Prefix { + self.as_ref() } - pub fn origin(&self) -> AsNumber { - self.origin - } - - pub fn prefix(&self) -> RoaPrefix { - self.prefix + pub fn ip_addr(&self) -> IpAddr { + match self { + TypedPrefix::V4(v4) => IpAddr::V4(v4.0.to_v4()), + TypedPrefix::V6(v6) => IpAddr::V6(v6.0.to_v6()), + } } } -impl FromStr for RouteAuthorization { +impl FromStr for TypedPrefix { type Err = AuthorizationFmtError; - // "192.168.0.0/16 => 64496" - fn from_str(s: &str) -> Result { - let mut parts = s.split("=>"); - let prefix_str = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?; - let asn_str = parts.next().ok_or_else(|| AuthorizationFmtError::auth(s))?; - if parts.next().is_some() { - return Err(AuthorizationFmtError::auth(s)); + fn from_str(prefix: &str) -> Result { + if prefix.contains('.') { + Ok(TypedPrefix::V4(Ipv4Prefix( + Prefix::from_v4_str(prefix.trim()) + .map_err(|_| AuthorizationFmtError::pfx(prefix))?, + ))) + } else { + Ok(TypedPrefix::V6(Ipv6Prefix( + Prefix::from_v6_str(prefix.trim()) + .map_err(|_| AuthorizationFmtError::pfx(prefix))?, + ))) } - let prefix = RoaPrefix::from_str(&prefix_str)?; - let origin = AsNumber::from_str(&asn_str)?; - - Ok(RouteAuthorization { origin, prefix }) } } -impl fmt::Display for RouteAuthorization { +impl fmt::Display for TypedPrefix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} => {}", self.prefix, self.origin) + match self { + TypedPrefix::V4(pfx) => pfx.fmt(f), + TypedPrefix::V6(pfx) => pfx.fmt(f), + } } } -impl Serialize for RouteAuthorization { +impl AsRef for TypedPrefix { + fn as_ref(&self) -> &Prefix { + match self { + TypedPrefix::V4(v4) => &v4.0, + TypedPrefix::V6(v6) => &v6.0, + } + } +} + +impl Deref for TypedPrefix { + type Target = Prefix; + + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl Serialize for TypedPrefix { fn serialize(&self, s: S) -> Result where S: Serializer, @@ -164,157 +258,54 @@ impl Serialize for RouteAuthorization { } } -impl<'de> Deserialize<'de> for RouteAuthorization { - fn deserialize(d: D) -> Result +impl<'de> Deserialize<'de> for TypedPrefix { + fn deserialize(d: D) -> Result where D: Deserializer<'de>, { let string = String::deserialize(d)?; - RouteAuthorization::from_str(string.as_str()).map_err(de::Error::custom) + TypedPrefix::from_str(string.as_str()).map_err(de::Error::custom) } } -//------------ RoaPrefix --------------------------------------------------- +impl From for ResourceSet { + fn from(tp: TypedPrefix) -> ResourceSet { + match tp { + TypedPrefix::V4(v4) => { + let mut builder = IpBlocksBuilder::new(); + builder.push(v4.0); + let blocks = builder.finalize(); -/// This type defines a ROA IPv4 or IPv6 prefix and optional max length. -#[derive(Clone, Copy, Debug)] -pub struct RoaPrefix { - prefix: Prefix, - max_length: Option, - family: AddressFamily, -} + ResourceSet::new(AsBlocks::empty(), blocks, IpBlocks::empty()) + } + TypedPrefix::V6(v6) => { + let mut builder = IpBlocksBuilder::new(); + builder.push(v6.0); + let blocks = builder.finalize(); -impl RoaPrefix { - pub fn addr(&self) -> IpAddr { - match self.family { - AddressFamily::Ipv4 => IpAddr::V4(self.prefix.to_v4()), - AddressFamily::Ipv6 => IpAddr::V6(self.prefix.to_v6()), - } - } - - pub fn length(&self) -> u8 { - self.prefix.addr_len() - } - - pub fn max_length(&self) -> Option { - self.max_length - } -} - -impl From for ResourceSet { - fn from(pfx: RoaPrefix) -> Self { - let mut builder = IpBlocksBuilder::new(); - builder.push(pfx.prefix); - let blocks = builder.finalize(); - - match pfx.family { - AddressFamily::Ipv4 => ResourceSet::new(AsBlocks::empty(), blocks, IpBlocks::empty()), - AddressFamily::Ipv6 => ResourceSet::new(AsBlocks::empty(), IpBlocks::empty(), blocks), + ResourceSet::new(AsBlocks::empty(), IpBlocks::empty(), blocks) + } } } } -impl Hash for RoaPrefix { - fn hash(&self, state: &mut H) { - self.prefix.hash(state); - self.max_length.hash(state); - match self.family { - AddressFamily::Ipv4 => 1.hash(state), - AddressFamily::Ipv6 => 2.hash(state), - } - } -} +//------------ Ipv4Prefix -------------------------------------------------- +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Ipv4Prefix(Prefix); -impl PartialEq for RoaPrefix { - fn eq(&self, other: &RoaPrefix) -> bool { - self.prefix == other.prefix - && self.max_length == other.max_length - && self.family == other.family - } -} - -impl Eq for RoaPrefix {} - -impl fmt::Display for RoaPrefix { +impl fmt::Display for Ipv4Prefix { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let add = self.prefix.addr(); - let add_str = match self.family { - AddressFamily::Ipv4 => add.to_v4().to_string(), - AddressFamily::Ipv6 => add.to_v6().to_string(), - }; - match self.max_length { - None => write!(f, "{}/{}", add_str, self.prefix.addr_len()), - Some(max) => write!(f, "{}/{}-{}", add_str, self.prefix.addr_len(), max), - } + write!(f, "{}/{}", self.0.to_v4(), self.0.addr_len()) } } -impl FromStr for RoaPrefix { - type Err = AuthorizationFmtError; +//------------ Ipv6Prefix -------------------------------------------------- +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub struct Ipv6Prefix(Prefix); - fn from_str(s: &str) -> Result { - let s = s.trim(); - - let mut parts = s.split('-'); - let prefix = parts.next().ok_or_else(|| AuthorizationFmtError::pfx(s))?; - - let family = if s.contains('.') { - AddressFamily::Ipv4 - } else if s.contains(':') { - AddressFamily::Ipv6 - } else { - return Err(AuthorizationFmtError::pfx(s)); - }; - - let prefix = match family { - AddressFamily::Ipv4 => { - Prefix::from_v4_str(prefix).map_err(|_| AuthorizationFmtError::pfx(s)) - } - AddressFamily::Ipv6 => { - Prefix::from_v6_str(prefix).map_err(|_| AuthorizationFmtError::pfx(s)) - } - } - .map_err(|_| AuthorizationFmtError::pfx(s))?; - - let max_length = match parts.next() { - None => None, - Some(s) => Some(u8::from_str(s).map_err(|_| AuthorizationFmtError::pfx(s))?), - }; - - if let Some(max) = max_length { - let too_long = match family { - AddressFamily::Ipv4 => max > 32, - AddressFamily::Ipv6 => max > 128, - }; - if max < prefix.addr_len() || too_long { - return Err(AuthorizationFmtError::pfx(s)); - } - } - - Ok(RoaPrefix { - prefix, - max_length, - family, - }) - } -} - -impl Serialize for RoaPrefix { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.to_string().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for RoaPrefix { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let string = String::deserialize(deserializer)?; - RoaPrefix::from_str(string.as_str()).map_err(de::Error::custom) +impl fmt::Display for Ipv6Prefix { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}/{}", self.0.to_v6(), self.0.addr_len()) } } @@ -377,11 +368,11 @@ impl AuthorizationFmtError { AuthorizationFmtError::Asn(s.to_string()) } - fn auth(s: &str) -> Self { + pub fn auth(s: &str) -> Self { AuthorizationFmtError::Auth(s.to_string()) } - fn delta(s: &str) -> Self { + pub fn delta(s: &str) -> Self { AuthorizationFmtError::Delta(s.to_string()) } } @@ -392,26 +383,6 @@ impl AuthorizationFmtError { mod tests { use super::*; - #[test] - fn parse_roa_prefix() { - assert!(RoaPrefix::from_str("192.168.0.0/16").is_ok()); - assert!(RoaPrefix::from_str("192.168.0.0/16-16").is_ok()); - assert!(RoaPrefix::from_str("192.168.0.0/16-24").is_ok()); - assert!(RoaPrefix::from_str("192.168.0.0/16-15").is_err()); - assert!(RoaPrefix::from_str("192.168.0.0/16-33").is_err()); - } - - #[test] - fn parse_route_authorization() { - fn parse_encode_authorization(s: &str) { - let authz = RouteAuthorization::from_str(s).unwrap(); - assert_eq!(s, authz.to_string().as_str()); - } - - parse_encode_authorization("192.168.0.0/16 => 64496"); - parse_encode_authorization("192.168.0.0/16-24 => 64496"); - } - #[test] fn parse_delta() { let delta = concat!( @@ -425,19 +396,54 @@ mod tests { let expected = { let mut added = HashSet::new(); - added.insert(RouteAuthorization::from_str("192.168.0.0/16 => 64496").unwrap()); - added.insert(RouteAuthorization::from_str("192.168.1.0/24 => 64496").unwrap()); + added.insert(RoaDefinition::from_str("192.168.0.0/16 => 64496").unwrap()); + added.insert(RoaDefinition::from_str("192.168.1.0/24 => 64496").unwrap()); let mut removed = HashSet::new(); - removed.insert(RouteAuthorization::from_str("192.168.3.0/24 => 64496").unwrap()); - RouteAuthorizationUpdates::new(added, removed) + removed.insert(RoaDefinition::from_str("192.168.3.0/24 => 64496").unwrap()); + RoaDefinitionUpdates::new(added, removed) }; - let parsed = RouteAuthorizationUpdates::from_str(delta).unwrap(); + let parsed = RoaDefinitionUpdates::from_str(delta).unwrap(); assert_eq!(expected, parsed); - let reparsed = RouteAuthorizationUpdates::from_str(&parsed.to_string()).unwrap(); + let reparsed = RoaDefinitionUpdates::from_str(&parsed.to_string()).unwrap(); assert_eq!(parsed, reparsed); } + #[test] + fn parse_type_prefix() { + assert!(TypedPrefix::from_str("192.168.0.0/16").is_ok()); + assert!(TypedPrefix::from_str("2001:db8::/32").is_ok()); + } + + #[test] + fn normalize_roa_definition_json() { + let def = RoaDefinition::from_str("192.168.0.0/16 => 64496").unwrap(); + let json = serde_json::to_string(&def).unwrap(); + let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\"}"; + assert_eq!(json, expected); + + let def = RoaDefinition::from_str("192.168.0.0/16-24 => 64496").unwrap(); + let json = serde_json::to_string(&def).unwrap(); + let expected = "{\"asn\":64496,\"prefix\":\"192.168.0.0/16\",\"max_length\":24}"; + assert_eq!(json, expected); + } + + #[test] + fn serde_roa_definition() { + fn parse_ser_de_print_definition(s: &str) { + let def = RoaDefinition::from_str(s).unwrap(); + let ser = serde_json::to_string(&def).unwrap(); + let de = serde_json::from_str(&ser).unwrap(); + assert_eq!(def, de); + assert_eq!(s, de.to_string().as_str()) + } + + parse_ser_de_print_definition("192.168.0.0/16 => 64496"); + parse_ser_de_print_definition("192.168.0.0/16-24 => 64496"); + parse_ser_de_print_definition("2001:db8::/32 => 64496"); + parse_ser_de_print_definition("2001:db8::/32-48 => 64496"); + } + } diff --git a/src/daemon/ca/certauth.rs b/src/daemon/ca/certauth.rs index 714e3ebe..a35030bf 100644 --- a/src/daemon/ca/certauth.rs +++ b/src/daemon/ca/certauth.rs @@ -16,7 +16,7 @@ use crate::commons::api::{ self, CertAuthInfo, ChildHandle, EntitlementClass, Entitlements, Handle, IssuanceRequest, IssuedCert, ObjectsDelta, ParentCaContact, ParentHandle, RcvdCert, RepositoryContact, RequestResourceLimit, ResourceClassName, ResourceSet, RevocationRequest, RevocationResponse, - RouteAuthorization, RouteAuthorizationUpdates, SigningCert, UpdateChildRequest, + SigningCert, UpdateChildRequest, }; use crate::commons::eventsourcing::{Aggregate, StoredEvent}; use crate::commons::remote::builder::{IdCertBuilder, SignedMessageBuilder}; @@ -30,7 +30,7 @@ use crate::daemon::ca::rc::PublishMode; use crate::daemon::ca::signing::CsrInfo; use crate::daemon::ca::{ self, ta_handle, ChildDetails, Cmd, CmdDet, CurrentObjectSetDelta, Error, Evt, EvtDet, Ini, - ResourceClass, Result, Routes, Signer, + ResourceClass, Result, RouteAuthorization, RouteAuthorizationUpdates, Routes, Signer, }; //------------ Rfc8183Id --------------------------------------------------- diff --git a/src/daemon/ca/commands.rs b/src/daemon/ca/commands.rs index 86295382..a910d481 100644 --- a/src/daemon/ca/commands.rs +++ b/src/daemon/ca/commands.rs @@ -6,11 +6,11 @@ use chrono::Duration; use crate::commons::api::{ ChildHandle, Entitlements, Handle, IssuanceRequest, ParentCaContact, ParentHandle, RcvdCert, RepositoryContact, ResourceClassName, ResourceSet, RevocationRequest, RevocationResponse, - RouteAuthorizationUpdates, UpdateChildRequest, + UpdateChildRequest, }; use crate::commons::eventsourcing; use crate::commons::remote::id::IdCert; -use crate::daemon::ca::{Evt, Signer}; +use crate::daemon::ca::{Evt, RouteAuthorizationUpdates, Signer}; //------------ Command ----------------------------------------------------- diff --git a/src/daemon/ca/error.rs b/src/daemon/ca/error.rs index 6a5b9fb1..8752dda3 100644 --- a/src/daemon/ca/error.rs +++ b/src/daemon/ca/error.rs @@ -3,10 +3,11 @@ use std::{fmt, io}; use rpki::crypto::KeyIdentifier; -use crate::commons::api::{Handle, RouteAuthorization}; +use crate::commons::api::Handle; use crate::commons::eventsourcing::AggregateStoreError; use crate::commons::remote::rfc6492; use crate::commons::util::httpclient; +use crate::daemon::ca::RouteAuthorization; //------------ Error --------------------------------------------------------- diff --git a/src/daemon/ca/events.rs b/src/daemon/ca/events.rs index ebcc98f8..768b1c8e 100644 --- a/src/daemon/ca/events.rs +++ b/src/daemon/ca/events.rs @@ -11,15 +11,15 @@ use rpki::x509::{Serial, Time, Validity}; use crate::commons::api::{ AddedObject, ChildHandle, CurrentObject, Handle, IssuanceRequest, IssuedCert, ObjectName, ObjectsDelta, ParentCaContact, ParentHandle, RcvdCert, RepoInfo, RepositoryContact, - ResourceClassName, ResourceSet, Revocation, RevocationRequest, RevokedObject, - RouteAuthorization, TaCertDetails, TrustAnchorLocator, UpdatedObject, WithdrawnObject, + ResourceClassName, ResourceSet, Revocation, RevocationRequest, RevokedObject, TaCertDetails, + TrustAnchorLocator, UpdatedObject, WithdrawnObject, }; use crate::commons::eventsourcing::StoredEvent; use crate::commons::remote::id::IdCert; use crate::daemon::ca::signing::Signer; use crate::daemon::ca::{ CertifiedKey, ChildDetails, CurrentObjectSetDelta, Error, ResourceClass, Result, Rfc8183Id, - RoaInfo, + RoaInfo, RouteAuthorization, }; //------------ Ini ----------------------------------------------------------- diff --git a/src/daemon/ca/publishing.rs b/src/daemon/ca/publishing.rs index 7b1cb629..08ab8a97 100644 --- a/src/daemon/ca/publishing.rs +++ b/src/daemon/ca/publishing.rs @@ -12,9 +12,9 @@ use rpki::x509::{Serial, Time, Validity}; use crate::commons::api::{ AddedObject, CurrentObject, HexEncodedHash, IssuedCert, ObjectName, ObjectsDelta, RcvdCert, - Revocation, Revocations, RevocationsDelta, RouteAuthorization, UpdatedObject, WithdrawnObject, + Revocation, Revocations, RevocationsDelta, UpdatedObject, WithdrawnObject, }; -use crate::daemon::ca::{self, RoaInfo, Signer}; +use crate::daemon::ca::{self, RoaInfo, RouteAuthorization, Signer}; //------------ AddedOrUpdated ---------------------------------------------- diff --git a/src/daemon/ca/rc.rs b/src/daemon/ca/rc.rs index 426ea7f2..f8800b9a 100644 --- a/src/daemon/ca/rc.rs +++ b/src/daemon/ca/rc.rs @@ -13,14 +13,14 @@ use crate::commons::api::{ AddedObject, CurrentObject, CurrentObjects, EntitlementClass, HexEncodedHash, IssuanceRequest, IssuedCert, ObjectName, ObjectsDelta, ParentHandle, RcvdCert, ReplacedObject, RepoInfo, RequestResourceLimit, ResourceClassInfo, ResourceClassName, ResourceSet, Revocation, - RevocationRequest, RevokedObject, RouteAuthorization, UpdatedObject, WithdrawnObject, + RevocationRequest, RevokedObject, UpdatedObject, WithdrawnObject, }; use crate::daemon::ca::events::{ChildCertificateUpdates, RoaUpdates}; use crate::daemon::ca::signing::CsrInfo; use crate::daemon::ca::{ self, ta_handle, AddedOrUpdated, CertifiedKey, ChildCertificates, CrlBuilder, CurrentKey, CurrentObjectSetDelta, Error, EvtDet, KeyState, ManifestBuilder, NewKey, OldKey, PendingKey, - Result, RoaInfo, Roas, SignSupport, Signer, + Result, RoaInfo, Roas, RouteAuthorization, SignSupport, Signer, }; //------------ ResourceClass ----------------------------------------------- diff --git a/src/daemon/ca/routes.rs b/src/daemon/ca/routes.rs index d094c788..b553f9e7 100644 --- a/src/daemon/ca/routes.rs +++ b/src/daemon/ca/routes.rs @@ -1,14 +1,115 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; +use std::fmt; +use std::ops::Deref; +use std::str::FromStr; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use rpki::roa::{Roa, RoaBuilder}; use rpki::sigobj::SignedObjectBuilder; use rpki::uri; use rpki::x509::{Serial, Time}; -use crate::commons::api::{ObjectName, ReplacedObject, RouteAuthorization}; +use crate::commons::api::{ObjectName, ReplacedObject, RoaDefinition, RoaDefinitionUpdates}; use crate::daemon::ca::events::RoaUpdates; use crate::daemon::ca::{self, CertifiedKey, SignSupport, Signer}; +//------------ RouteAuthorization ------------------------------------------ + +/// This type defines a prefix and optional maximum length (other than the +/// prefix length) which is to be authorized for the given origin ASN. +#[derive(Clone, Copy, Debug, Display, Eq, Hash, PartialEq)] +pub struct RouteAuthorization(RoaDefinition); + +impl RouteAuthorization { + pub fn new(definition: RoaDefinition) -> Self { + RouteAuthorization(definition) + } +} + +impl AsRef for RouteAuthorization { + fn as_ref(&self) -> &RoaDefinition { + &self.0 + } +} + +impl Deref for RouteAuthorization { + type Target = RoaDefinition; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Serialize for RouteAuthorization { + fn serialize(&self, s: S) -> Result + where + S: Serializer, + { + self.to_string().serialize(s) + } +} + +impl<'de> Deserialize<'de> for RouteAuthorization { + fn deserialize(d: D) -> Result + where + D: Deserializer<'de>, + { + let string = String::deserialize(d)?; + let def = RoaDefinition::from_str(string.as_str()).map_err(de::Error::custom)?; + Ok(RouteAuthorization(def)) + } +} + +impl From for RouteAuthorization { + fn from(def: RoaDefinition) -> Self { + RouteAuthorization(def) + } +} + +//------------ RouteAuthorizationUpdates ----------------------------------- + +/// +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct RouteAuthorizationUpdates { + added: HashSet, + removed: HashSet, +} + +impl RouteAuthorizationUpdates { + pub fn unpack(self) -> (HashSet, HashSet) { + (self.added, self.removed) + } +} + +impl From for RouteAuthorizationUpdates { + fn from(definitions: RoaDefinitionUpdates) -> Self { + let (added, removed) = definitions.unpack(); + let added = added.into_iter().map(RoaDefinition::into).collect(); + let removed = removed.into_iter().map(RoaDefinition::into).collect(); + RouteAuthorizationUpdates { added, removed } + } +} + +impl fmt::Display for RouteAuthorizationUpdates { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if !self.added.is_empty() { + write!(f, "added:")?; + for a in &self.added { + write!(f, " {}", a)?; + } + write!(f, " ")?; + } + if !self.removed.is_empty() { + write!(f, "removed:")?; + for r in &self.removed { + write!(f, " {}", r)?; + } + } + Ok(()) + } +} + //------------ Routes ------------------------------------------------------ /// The current authorizations and corresponding meta-information for a CA. @@ -181,8 +282,8 @@ impl Roas { let signing_key = certified_key.key_id(); - let mut roa_builder = RoaBuilder::new(auth.origin().into()); - roa_builder.push_addr(prefix.addr(), prefix.length(), prefix.max_length()); + let mut roa_builder = RoaBuilder::new(auth.asn().into()); + roa_builder.push_addr(prefix.ip_addr(), prefix.addr_len(), auth.max_length()); let mut object_builder = SignedObjectBuilder::new( Serial::random(signer).map_err(ca::Error::signer)?, SignSupport::sign_validity_year(), @@ -198,3 +299,30 @@ impl Roas { .map_err(ca::Error::signer) } } + +//------------ Tests ------------------------------------------------------- + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn serde_route_authorization() { + fn parse_encode_authorization(s: &str) { + let def = RoaDefinition::from_str(s).unwrap(); + let auth = RouteAuthorization(def); + + let json = serde_json::to_string(&auth).unwrap(); + assert_eq!(format!("\"{}\"", s), json); + + let des: RouteAuthorization = serde_json::from_str(&json).unwrap(); + assert_eq!(des, auth); + } + + parse_encode_authorization("192.168.0.0/16 => 64496"); + parse_encode_authorization("192.168.0.0/16-24 => 64496"); + parse_encode_authorization("2001:db8::/32 => 64496"); + parse_encode_authorization("2001:db8::/32-48 => 64496"); + } +} diff --git a/src/daemon/ca/server.rs b/src/daemon/ca/server.rs index c5311dfc..2ab1bb61 100644 --- a/src/daemon/ca/server.rs +++ b/src/daemon/ca/server.rs @@ -15,7 +15,7 @@ use crate::commons::api::{ ChildHandle, Entitlements, Handle, IssuanceRequest, IssuanceResponse, IssuedCert, ListReply, ParentCaContact, ParentCaReq, ParentHandle, PublishDelta, RcvdCert, RepoInfo, RepositoryContact, ResourceClassName, ResourceSet, RevocationRequest, RevocationResponse, - RouteAuthorizationUpdates, UpdateChildRequest, + UpdateChildRequest, }; use crate::commons::eventsourcing::{Aggregate, AggregateStore, Command, DiskAggregateStore}; use crate::commons::remote::builder::SignedMessageBuilder; @@ -24,7 +24,8 @@ use crate::commons::remote::sigmsg::SignedMessage; use crate::commons::remote::{rfc6492, rfc8181, rfc8183}; use crate::commons::util::httpclient; use crate::daemon::ca::{ - self, ta_handle, CertAuth, Cmd, CmdDet, IniDet, ServerError, ServerResult, Signer, + self, ta_handle, CertAuth, Cmd, CmdDet, IniDet, RouteAuthorizationUpdates, ServerError, + ServerResult, Signer, }; use crate::daemon::mq::EventQueueListener; diff --git a/src/daemon/endpoints.rs b/src/daemon/endpoints.rs index d6ce8e71..2202c4c7 100644 --- a/src/daemon/endpoints.rs +++ b/src/daemon/endpoints.rs @@ -8,7 +8,7 @@ use serde::Serialize; use crate::commons::api::rrdp::VerificationError; use crate::commons::api::{ AddChildRequest, CertAuthInit, ErrorCode, ErrorResponse, Handle, ParentCaContact, ParentCaReq, - ParentHandle, PublisherHandle, PublisherList, RepositoryUpdate, RouteAuthorizationUpdates, + ParentHandle, PublisherHandle, PublisherList, RepositoryUpdate, RoaDefinitionUpdates, UpdateChildRequest, }; use crate::commons::remote::sigmsg::SignedMessage; @@ -450,7 +450,7 @@ pub fn ca_routes_update( server: web::Data, auth: Auth, handle: Path, - updates: Json, + updates: Json, ) -> HttpResponse { if_api_allowed(&server, &auth, || { render_empty_res( diff --git a/src/daemon/krillserver.rs b/src/daemon/krillserver.rs index 9385d1a4..d1527fa4 100644 --- a/src/daemon/krillserver.rs +++ b/src/daemon/krillserver.rs @@ -12,7 +12,7 @@ use crate::commons::api::{ AddChildRequest, CaRepoDetails, CertAuthHistory, CertAuthInfo, CertAuthInit, CertAuthList, ChildCaInfo, ChildHandle, CurrentRepoState, Handle, ListReply, ParentCaContact, ParentCaReq, ParentHandle, PublishDelta, PublisherDetails, PublisherHandle, RepoInfo, RepositoryContact, - RepositoryUpdate, RouteAuthorizationUpdates, TaCertDetails, Token, UpdateChildRequest, + RepositoryUpdate, RoaDefinitionUpdates, TaCertDetails, Token, UpdateChildRequest, }; use crate::commons::remote::rfc8183; use crate::commons::remote::sigmsg::SignedMessage; @@ -458,8 +458,8 @@ impl KrillServer { /// # Handle route authorization requests /// impl KrillServer { - pub fn ca_routes_update(&self, handle: Handle, updates: RouteAuthorizationUpdates) -> EmptyRes { - Ok(self.caserver.ca_routes_update(handle, updates)?) + pub fn ca_routes_update(&self, handle: Handle, updates: RoaDefinitionUpdates) -> EmptyRes { + Ok(self.caserver.ca_routes_update(handle, updates.into())?) } } diff --git a/src/daemon/test.rs b/src/daemon/test.rs index d6197f2a..2c2d91ca 100644 --- a/src/daemon/test.rs +++ b/src/daemon/test.rs @@ -11,7 +11,7 @@ use crate::cli::{Error, KrillClient}; use crate::commons::api::{ AddChildRequest, CertAuthInfo, CertAuthInit, CertifiedKeyInfo, ChildAuthRequest, ChildHandle, Handle, ParentCaContact, ParentCaReq, ParentHandle, Publish, PublisherDetails, PublisherHandle, - ResourceClassKeysInfo, ResourceClassName, ResourceSet, RouteAuthorizationUpdates, + ResourceClassKeysInfo, ResourceClassName, ResourceSet, RoaDefinitionUpdates, UpdateChildRequest, }; use crate::commons::remote::rfc8183; @@ -280,17 +280,14 @@ pub fn ca_roll_activate(handle: &Handle) { ))); } -pub fn ca_route_authorizations_update(handle: &Handle, updates: RouteAuthorizationUpdates) { +pub fn ca_route_authorizations_update(handle: &Handle, updates: RoaDefinitionUpdates) { krill_admin(Command::CertAuth(CaCommand::RouteAuthorizationsUpdate( handle.clone(), updates, ))); } -pub fn ca_route_authorizations_update_expect_error( - handle: &Handle, - updates: RouteAuthorizationUpdates, -) { +pub fn ca_route_authorizations_update_expect_error(handle: &Handle, updates: RoaDefinitionUpdates) { krill_admin_expect_error(Command::CertAuth(CaCommand::RouteAuthorizationsUpdate( handle.clone(), updates, diff --git a/tests/ca_roas.rs b/tests/ca_roas.rs index 4e982b63..9d48fa93 100644 --- a/tests/ca_roas.rs +++ b/tests/ca_roas.rs @@ -3,7 +3,7 @@ extern crate krill; use std::str::FromStr; use krill::commons::api::{ - Handle, ObjectName, ParentCaReq, ResourceSet, RouteAuthorization, RouteAuthorizationUpdates, + Handle, ObjectName, ParentCaReq, ResourceSet, RoaDefinition, RoaDefinitionUpdates, }; use krill::daemon::ca::ta_handle; use krill::daemon::test::*; @@ -31,9 +31,9 @@ fn ca_roas() { } // Add some Route Authorizations - let route_1 = RouteAuthorization::from_str("10.0.0.0/24 => 64496").unwrap(); - let route_2 = RouteAuthorization::from_str("2001:DB8::/32-48 => 64496").unwrap(); - let route_3 = RouteAuthorization::from_str("192.168.0.0/24 => 64496").unwrap(); + let route_1 = RoaDefinition::from_str("10.0.0.0/24 => 64496").unwrap(); + let route_2 = RoaDefinition::from_str("2001:DB8::/32-48 => 64496").unwrap(); + let route_3 = RoaDefinition::from_str("192.168.0.0/24 => 64496").unwrap(); let crl_file = ".crl"; let mft_file = ".mft"; @@ -44,20 +44,20 @@ fn ca_roas() { let route3_file = ObjectName::from(&route_3).to_string(); let route3_file = route3_file.as_str(); - let mut updates = RouteAuthorizationUpdates::empty(); + let mut updates = RoaDefinitionUpdates::empty(); updates.add(route_1); updates.add(route_2); ca_route_authorizations_update(&child, updates); wait_for_published_objects(&child, &[crl_file, mft_file, route1_file, route2_file]); // Remove a Route Authorization - let mut updates = RouteAuthorizationUpdates::empty(); + let mut updates = RoaDefinitionUpdates::empty(); updates.remove(route_1); ca_route_authorizations_update(&child, updates); wait_for_published_objects(&child, &[crl_file, mft_file, route2_file]); // Refuse authorization for prefix not held by CA - let mut updates = RouteAuthorizationUpdates::empty(); + let mut updates = RoaDefinitionUpdates::empty(); updates.add(route_3); ca_route_authorizations_update_expect_error(&child, updates); @@ -67,7 +67,7 @@ fn ca_roas() { wait_for_published_objects(&child, &[crl_file, mft_file]); // Now route3 can be added - let mut updates = RouteAuthorizationUpdates::empty(); + let mut updates = RoaDefinitionUpdates::empty(); updates.add(route_3); ca_route_authorizations_update(&child, updates); wait_for_published_objects(&child, &[crl_file, mft_file, route3_file]);