mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-26 19:34:51 +02:00
Use normalised route authorizations in the API. (Closes #126)
This commit is contained in:
+3
-3
@@ -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),
|
||||
|
||||
+2
-1
@@ -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 ---------------------------------------------------
|
||||
|
||||
|
||||
@@ -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<Bytes> for ObjectName {
|
||||
fn into(self) -> Bytes {
|
||||
Bytes::from(self.0)
|
||||
|
||||
+218
-212
@@ -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<u8>,
|
||||
}
|
||||
|
||||
impl RoaDefinition {
|
||||
pub fn new(asn: AsNumber, prefix: TypedPrefix, max_length: Option<u8>) -> 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<u8> {
|
||||
self.max_length
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RoaDefinition {
|
||||
type Err = AuthorizationFmtError;
|
||||
|
||||
// "192.168.0.0/16 => 64496"
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
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<RouteAuthorization>,
|
||||
removed: HashSet<RouteAuthorization>,
|
||||
pub struct RoaDefinitionUpdates {
|
||||
added: HashSet<RoaDefinition>,
|
||||
removed: HashSet<RoaDefinition>,
|
||||
}
|
||||
|
||||
impl RouteAuthorizationUpdates {
|
||||
pub fn new(added: HashSet<RouteAuthorization>, removed: HashSet<RouteAuthorization>) -> Self {
|
||||
RouteAuthorizationUpdates { added, removed }
|
||||
impl RoaDefinitionUpdates {
|
||||
pub fn new(added: HashSet<RoaDefinition>, removed: HashSet<RoaDefinition>) -> Self {
|
||||
RoaDefinitionUpdates { added, removed }
|
||||
}
|
||||
|
||||
/// Unpack this and return all added (left), and all removed (right) route
|
||||
/// authorizations.
|
||||
pub fn unpack(self) -> (HashSet<RouteAuthorization>, HashSet<RouteAuthorization>) {
|
||||
pub fn unpack(self) -> (HashSet<RoaDefinition>, HashSet<RoaDefinition>) {
|
||||
(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<Self, Self::Err> {
|
||||
@@ -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<Self, Self::Err> {
|
||||
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<Self, Self::Err> {
|
||||
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<Prefix> 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<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
@@ -164,157 +258,54 @@ impl Serialize for RouteAuthorization {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RouteAuthorization {
|
||||
fn deserialize<D>(d: D) -> Result<RouteAuthorization, D::Error>
|
||||
impl<'de> Deserialize<'de> for TypedPrefix {
|
||||
fn deserialize<D>(d: D) -> Result<TypedPrefix, D::Error>
|
||||
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<TypedPrefix> 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<u8>,
|
||||
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<u8> {
|
||||
self.max_length
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoaPrefix> 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<H: Hasher>(&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<Self, Self::Err> {
|
||||
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.to_string().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RoaPrefix {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 ---------------------------------------------------
|
||||
|
||||
@@ -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 -----------------------------------------------------
|
||||
|
||||
|
||||
@@ -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 ---------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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 -----------------------------------------------------------
|
||||
|
||||
@@ -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 ----------------------------------------------
|
||||
|
||||
|
||||
+2
-2
@@ -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 -----------------------------------------------
|
||||
|
||||
+132
-4
@@ -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<RoaDefinition> 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<S>(&self, s: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.to_string().serialize(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for RouteAuthorization {
|
||||
fn deserialize<D>(d: D) -> Result<RouteAuthorization, D::Error>
|
||||
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<RoaDefinition> for RouteAuthorization {
|
||||
fn from(def: RoaDefinition) -> Self {
|
||||
RouteAuthorization(def)
|
||||
}
|
||||
}
|
||||
|
||||
//------------ RouteAuthorizationUpdates -----------------------------------
|
||||
|
||||
///
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct RouteAuthorizationUpdates {
|
||||
added: HashSet<RouteAuthorization>,
|
||||
removed: HashSet<RouteAuthorization>,
|
||||
}
|
||||
|
||||
impl RouteAuthorizationUpdates {
|
||||
pub fn unpack(self) -> (HashSet<RouteAuthorization>, HashSet<RouteAuthorization>) {
|
||||
(self.added, self.removed)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RoaDefinitionUpdates> 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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<AppServer>,
|
||||
auth: Auth,
|
||||
handle: Path<Handle>,
|
||||
updates: Json<RouteAuthorizationUpdates>,
|
||||
updates: Json<RoaDefinitionUpdates>,
|
||||
) -> HttpResponse {
|
||||
if_api_allowed(&server, &auth, || {
|
||||
render_empty_res(
|
||||
|
||||
@@ -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())?)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-6
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user