mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-28 12:24:52 +02:00
Provide (scoped) ROA suggestions. (#289)
This commit is contained in:
@@ -229,6 +229,18 @@ impl KrillClient {
|
||||
Ok(ApiResponse::BgpAnalysisRoas(report.into()))
|
||||
}
|
||||
|
||||
CaCommand::BgpAnalysisSuggest(handle, resources) => {
|
||||
let uri = format!("api/v1/cas/{}/routes/analysis/suggest", handle);
|
||||
|
||||
let suggestions = if let Some(resources) = resources {
|
||||
self.post_json_with_response(&uri, resources).await?
|
||||
} else {
|
||||
self.get_json(&uri).await?
|
||||
};
|
||||
|
||||
Ok(ApiResponse::BgpAnalysisSuggestions(suggestions))
|
||||
}
|
||||
|
||||
CaCommand::Show(handle) => {
|
||||
let uri = format!("api/v1/cas/{}", handle);
|
||||
let ca_info = self.get_json(&uri).await?;
|
||||
|
||||
@@ -611,6 +611,33 @@ impl Options {
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_suggestions_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub = SubCommand::with_name("suggest").about("Show ROA suggestions.");
|
||||
|
||||
sub = Self::add_general_args(sub);
|
||||
sub = Self::add_my_ca_arg(sub);
|
||||
|
||||
sub = sub
|
||||
.arg(
|
||||
Arg::with_name("ipv4")
|
||||
.short("4")
|
||||
.long("ipv4")
|
||||
.value_name("IPv4 resources")
|
||||
.help("Scope to these IPv4 resources")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("ipv6")
|
||||
.short("6")
|
||||
.long("ipv6")
|
||||
.value_name("IPv6 resources")
|
||||
.help("Scope to these IPv6 resources")
|
||||
.required(false),
|
||||
);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
|
||||
fn make_cas_routes_bgp_sc<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
|
||||
let mut sub =
|
||||
SubCommand::with_name("bgp").about("Show current authorizations in relation to known announcements.");
|
||||
@@ -618,6 +645,7 @@ impl Options {
|
||||
sub = Self::make_cas_routes_bgp_full_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_announcements_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_roas_sc(sub);
|
||||
sub = Self::make_cas_routes_bgp_suggestions_sc(sub);
|
||||
|
||||
app.subcommand(sub)
|
||||
}
|
||||
@@ -1321,6 +1349,24 @@ impl Options {
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp_suggest(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
let general_args = GeneralArgs::from_matches(matches)?;
|
||||
let my_ca = Self::parse_my_ca(matches)?;
|
||||
|
||||
let v4 = matches.value_of("ipv4").unwrap_or("");
|
||||
let v6 = matches.value_of("ipv6").unwrap_or("");
|
||||
|
||||
let resources = ResourceSet::from_strs("", v4, v6)
|
||||
.map_err(|e| Error::GeneralArgumentError(format!("Could not parse IP resources: {}", e)))?;
|
||||
|
||||
let resources = if resources.is_empty() { None } else { Some(resources) };
|
||||
|
||||
Ok(Options::make(
|
||||
general_args,
|
||||
Command::CertAuth(CaCommand::BgpAnalysisSuggest(my_ca, resources)),
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_matches_cas_routes_bgp(matches: &ArgMatches) -> Result<Options, Error> {
|
||||
if let Some(m) = matches.subcommand_matches("full") {
|
||||
Self::parse_matches_cas_routes_bgp_full(m)
|
||||
@@ -1328,6 +1374,8 @@ impl Options {
|
||||
Self::parse_matches_cas_routes_bgp_announcements(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("roas") {
|
||||
Self::parse_matches_cas_routes_bgp_roas(m)
|
||||
} else if let Some(m) = matches.subcommand_matches("suggest") {
|
||||
Self::parse_matches_cas_routes_bgp_suggest(m)
|
||||
} else {
|
||||
Err(Error::UnrecognisedSubCommand)
|
||||
}
|
||||
@@ -1722,6 +1770,9 @@ pub enum CaCommand {
|
||||
#[display(fmt = "Show ROA centric summary of ROA vs BGP analysis for ca: '{}'", _0)]
|
||||
BgpAnalysisRoas(Handle),
|
||||
|
||||
#[display(fmt = "Show ROA suggestions based on BGP analysis for ca: '{}'", _0)]
|
||||
BgpAnalysisSuggest(Handle, Option<ResourceSet>),
|
||||
|
||||
// Show details for this CA
|
||||
#[display(fmt = "Show details for ca: '{}'", _0)]
|
||||
Show(Handle),
|
||||
|
||||
+9
-1
@@ -10,7 +10,7 @@ use crate::commons::api::{
|
||||
CertAuthList, ChildCaInfo, CommandHistory, CurrentObjects, CurrentRepoState, ParentCaContact, PublisherDetails,
|
||||
PublisherList, RepositoryContact, RoaDefinition, ServerInfo, StoredEffect,
|
||||
};
|
||||
use crate::commons::bgp::{AnnouncementReport, BgpAnalysisReport, RoaReport};
|
||||
use crate::commons::bgp::{AnnouncementReport, BgpAnalysisReport, BgpAnalysisSuggestion, RoaReport};
|
||||
use crate::commons::eventsourcing::WithStorableDetails;
|
||||
use crate::commons::remote::api::ClientInfo;
|
||||
use crate::commons::remote::rfc8183;
|
||||
@@ -34,6 +34,7 @@ pub enum ApiResponse {
|
||||
BgpAnalysisFull(BgpAnalysisReport),
|
||||
BgpAnalysisAnnouncements(AnnouncementReport),
|
||||
BgpAnalysisRoas(RoaReport),
|
||||
BgpAnalysisSuggestions(BgpAnalysisSuggestion),
|
||||
|
||||
ParentCaContact(ParentCaContact),
|
||||
|
||||
@@ -78,6 +79,7 @@ impl ApiResponse {
|
||||
ApiResponse::BgpAnalysisFull(table) => Ok(Some(table.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisAnnouncements(summary) => Ok(Some(summary.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisRoas(summary) => Ok(Some(summary.report(fmt)?)),
|
||||
ApiResponse::BgpAnalysisSuggestions(suggestions) => Ok(Some(suggestions.report(fmt)?)),
|
||||
ApiResponse::ParentCaContact(contact) => Ok(Some(contact.report(fmt)?)),
|
||||
ApiResponse::ChildInfo(info) => Ok(Some(info.report(fmt)?)),
|
||||
ApiResponse::PublisherList(list) => Ok(Some(list.report(fmt)?)),
|
||||
@@ -430,6 +432,12 @@ impl Report for RoaReport {
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for BgpAnalysisSuggestion {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
Ok(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Report for CaRepoDetails {
|
||||
fn text(&self) -> Result<String, ReportError> {
|
||||
let mut res = String::new();
|
||||
|
||||
@@ -278,6 +278,10 @@ pub struct RoaDefinitionUpdates {
|
||||
}
|
||||
|
||||
impl RoaDefinitionUpdates {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.added.is_empty() && self.removed.is_empty()
|
||||
}
|
||||
|
||||
pub fn new(added: HashSet<RoaDefinition>, removed: HashSet<RoaDefinition>) -> Self {
|
||||
RoaDefinitionUpdates { added, removed }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ use rpki::x509::Time;
|
||||
use crate::commons::api::{ResourceSet, RoaDefinition, TypedPrefix};
|
||||
use crate::commons::bgp::{
|
||||
make_roa_tree, make_validated_announcement_tree, Announcement, AnnouncementValidity, Announcements,
|
||||
BgpAnalysisEntry, BgpAnalysisReport, IpRange, RisDumpError, RisDumpLoader, ValidatedAnnouncement,
|
||||
BgpAnalysisEntry, BgpAnalysisReport, BgpAnalysisState, BgpAnalysisSuggestion, IpRange, RisDumpError, RisDumpLoader,
|
||||
ValidatedAnnouncement,
|
||||
};
|
||||
use crate::constants::{BGP_RIS_REFRESH_MINUTES, KRILL_ENV_TEST_ANN};
|
||||
|
||||
@@ -66,13 +67,19 @@ impl BgpAnalyser {
|
||||
let seen = self.seen.read().unwrap();
|
||||
let mut entries = vec![];
|
||||
|
||||
let roas: Vec<RoaDefinition> = roas
|
||||
.iter()
|
||||
.filter(|roa| scope.contains_roa_address(&roa.as_roa_ip_address()))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if seen.last_updated().is_none() {
|
||||
// nothing to analyse, just push all ROAs as 'no announcement info'
|
||||
for roa in roas {
|
||||
entries.push(BgpAnalysisEntry::roa_no_announcement_info(*roa));
|
||||
entries.push(BgpAnalysisEntry::roa_no_announcement_info(roa));
|
||||
}
|
||||
} else {
|
||||
let roa_tree = make_roa_tree(roas);
|
||||
let roa_tree = make_roa_tree(roas.as_ref());
|
||||
|
||||
let (v4_scope, v6_scope) = IpRange::for_resource_set(&scope);
|
||||
|
||||
@@ -97,7 +104,7 @@ impl BgpAnalyser {
|
||||
let covered = validated_tree.matching_or_more_specific(&roa.prefix());
|
||||
|
||||
if covered.is_empty() {
|
||||
entries.push(BgpAnalysisEntry::roa_unseen(*roa))
|
||||
entries.push(BgpAnalysisEntry::roa_unseen(roa))
|
||||
} else {
|
||||
let authorizes: Vec<Announcement> = covered
|
||||
.iter()
|
||||
@@ -140,16 +147,16 @@ impl BgpAnalyser {
|
||||
.collect();
|
||||
|
||||
if authorizes.is_empty() && disallows.is_empty() {
|
||||
entries.push(BgpAnalysisEntry::roa_unseen(*roa))
|
||||
entries.push(BgpAnalysisEntry::roa_unseen(roa))
|
||||
} else if !authorizes_excess.is_empty() {
|
||||
entries.push(BgpAnalysisEntry::roa_too_permissive(
|
||||
*roa,
|
||||
roa,
|
||||
authorizes,
|
||||
disallows,
|
||||
authorizes_excess,
|
||||
))
|
||||
} else {
|
||||
entries.push(BgpAnalysisEntry::roa_seen(*roa, authorizes, disallows))
|
||||
entries.push(BgpAnalysisEntry::roa_seen(roa, authorizes, disallows))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,6 +192,33 @@ impl BgpAnalyser {
|
||||
BgpAnalysisReport::new(entries)
|
||||
}
|
||||
|
||||
pub fn suggest(&self, roas: &[RoaDefinition], scope: &ResourceSet) -> BgpAnalysisSuggestion {
|
||||
let mut suggestion = BgpAnalysisSuggestion::default();
|
||||
|
||||
// perform analysis
|
||||
for entry in self.analyse(roas, scope).into_entries() {
|
||||
match entry.state() {
|
||||
BgpAnalysisState::RoaUnseen => suggestion.add_stale(entry.into_definition()),
|
||||
BgpAnalysisState::RoaTooPermissive => {
|
||||
let replace_with = entry
|
||||
.authorizes()
|
||||
.iter()
|
||||
.map(|auth| RoaDefinition::from(*auth))
|
||||
.collect();
|
||||
suggestion.add_too_permissive(entry.into_definition(), replace_with);
|
||||
}
|
||||
BgpAnalysisState::AnnouncementNotFound => suggestion.add_not_found(entry.into_definition()),
|
||||
BgpAnalysisState::AnnouncementInvalidAsn => suggestion.add_invalid_asn(entry.into_definition()),
|
||||
BgpAnalysisState::AnnouncementInvalidLength => suggestion.add_invalid_length(entry.into_definition()),
|
||||
BgpAnalysisState::RoaSeen => suggestion.add_keep(entry.into_definition()),
|
||||
BgpAnalysisState::RoaNoAnnouncementInfo => suggestion.add_keep(entry.into_definition()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
suggestion
|
||||
}
|
||||
|
||||
fn test_announcements() -> Vec<Announcement> {
|
||||
use crate::test::announcement;
|
||||
|
||||
@@ -306,4 +340,48 @@ mod tests {
|
||||
|
||||
assert_eq!(roas_no_info.as_slice(), &[&roa1, &roa2, &roa3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn make_bgp_analysis_suggestion() {
|
||||
let roa_too_permissive = definition("10.0.0.0/22-23 => 64496");
|
||||
let roa_disallowing = definition("10.0.4.0/24 => 0");
|
||||
let roa_unseen_completely = definition("10.0.3.0/24 => 64497");
|
||||
let roa_authorizing_single = definition("192.168.1.0/24 => 64497");
|
||||
|
||||
let analyser = BgpAnalyser::with_test_announcements();
|
||||
|
||||
let scope = ResourceSet::from_strs("", "10.0.0.0/22", "").unwrap();
|
||||
let suggestion_resource_subset = analyser.suggest(
|
||||
&[
|
||||
roa_too_permissive,
|
||||
roa_disallowing,
|
||||
roa_unseen_completely,
|
||||
roa_authorizing_single,
|
||||
],
|
||||
&scope,
|
||||
);
|
||||
|
||||
let expected: BgpAnalysisSuggestion = serde_json::from_str(include_str!(
|
||||
"../../../test-resources/bgp/expected_suggestion_some_roas.json"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(suggestion_resource_subset, expected);
|
||||
|
||||
let scope = ResourceSet::from_strs("", "10.0.0.0/8,192.168.0.0/16", "").unwrap();
|
||||
let suggestion_all_roas_in_scope = analyser.suggest(
|
||||
&[
|
||||
roa_too_permissive,
|
||||
roa_disallowing,
|
||||
roa_unseen_completely,
|
||||
roa_authorizing_single,
|
||||
],
|
||||
&scope,
|
||||
);
|
||||
let expected: BgpAnalysisSuggestion = serde_json::from_str(include_str!(
|
||||
"../../../test-resources/bgp/expected_suggestion_all_roas.json"
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(suggestion_all_roas_in_scope, expected);
|
||||
}
|
||||
}
|
||||
|
||||
+168
-2
@@ -1,10 +1,168 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt;
|
||||
|
||||
use crate::commons::api::{BgpStats, RoaDefinition};
|
||||
use crate::commons::api::{BgpStats, RoaDefinition, RoaDefinitionUpdates};
|
||||
use crate::commons::bgp::Announcement;
|
||||
|
||||
//------------ BgpAnalysisSuggestion ---------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct BgpAnalysisSuggestion {
|
||||
stale: Vec<RoaDefinition>,
|
||||
not_found: Vec<RoaDefinition>,
|
||||
invalid_asn: Vec<RoaDefinition>,
|
||||
invalid_length: Vec<RoaDefinition>,
|
||||
too_permissive: Vec<ReplacementRoaSuggestion>,
|
||||
keep: Vec<RoaDefinition>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct ReplacementRoaSuggestion {
|
||||
current: RoaDefinition,
|
||||
new: Vec<RoaDefinition>,
|
||||
}
|
||||
|
||||
impl From<BgpAnalysisSuggestion> for RoaDefinitionUpdates {
|
||||
fn from(suggestion: BgpAnalysisSuggestion) -> Self {
|
||||
let (stale, not_found, invalid_asn, invalid_length, too_permissive) = (
|
||||
suggestion.stale,
|
||||
suggestion.not_found,
|
||||
suggestion.invalid_asn,
|
||||
suggestion.invalid_length,
|
||||
suggestion.too_permissive,
|
||||
);
|
||||
|
||||
let mut added = HashSet::new();
|
||||
let mut removed = HashSet::new();
|
||||
|
||||
for auth in not_found
|
||||
.into_iter()
|
||||
.chain(invalid_asn.into_iter())
|
||||
.chain(invalid_length.into_iter())
|
||||
{
|
||||
added.insert(auth);
|
||||
}
|
||||
|
||||
for auth in stale.into_iter() {
|
||||
removed.insert(auth);
|
||||
}
|
||||
|
||||
for suggestion in too_permissive.into_iter() {
|
||||
removed.insert(suggestion.current);
|
||||
for auth in suggestion.new.into_iter() {
|
||||
added.insert(auth);
|
||||
}
|
||||
}
|
||||
|
||||
RoaDefinitionUpdates::new(added, removed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BgpAnalysisSuggestion {
|
||||
fn default() -> Self {
|
||||
BgpAnalysisSuggestion {
|
||||
stale: vec![],
|
||||
not_found: vec![],
|
||||
invalid_asn: vec![],
|
||||
invalid_length: vec![],
|
||||
too_permissive: vec![],
|
||||
keep: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BgpAnalysisSuggestion {
|
||||
pub fn add_stale(&mut self, authorization: RoaDefinition) {
|
||||
self.stale.push(authorization)
|
||||
}
|
||||
|
||||
pub fn add_too_permissive(&mut self, current: RoaDefinition, new: Vec<RoaDefinition>) {
|
||||
let replacement = ReplacementRoaSuggestion { current, new };
|
||||
self.too_permissive.push(replacement)
|
||||
}
|
||||
|
||||
pub fn add_not_found(&mut self, authorization: RoaDefinition) {
|
||||
self.not_found.push(authorization)
|
||||
}
|
||||
|
||||
pub fn add_invalid_asn(&mut self, authorization: RoaDefinition) {
|
||||
self.invalid_asn.push(authorization)
|
||||
}
|
||||
|
||||
pub fn add_invalid_length(&mut self, authorization: RoaDefinition) {
|
||||
self.invalid_length.push(authorization)
|
||||
}
|
||||
|
||||
pub fn add_keep(&mut self, authorization: RoaDefinition) {
|
||||
self.keep.push(authorization)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
impl fmt::Display for BgpAnalysisSuggestion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if !self.stale.is_empty() {
|
||||
writeln!(f, "Remove the following stale entries:")?;
|
||||
for auth in &self.stale {
|
||||
writeln!(f, " {}", auth)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if !self.too_permissive.is_empty() {
|
||||
writeln!(f, "Replace the following too permissive entries:")?;
|
||||
for entry in &self.too_permissive {
|
||||
writeln!(f, " Remove: {}", entry.current)?;
|
||||
for replace in &entry.new {
|
||||
writeln!(f, " Add: {}", replace)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.not_found.is_empty() {
|
||||
writeln!(f, "Authorize these announcements which are currently not covered:")?;
|
||||
for auth in &self.not_found {
|
||||
writeln!(f, " {}", auth)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if !self.invalid_length.is_empty() {
|
||||
writeln!(
|
||||
f,
|
||||
"Authorize these announcements which are currently invalid because they are too specific:"
|
||||
)?;
|
||||
for auth in &self.invalid_length {
|
||||
writeln!(f, " {}", auth)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if !self.invalid_asn.is_empty() {
|
||||
writeln!(
|
||||
f,
|
||||
"Authorize these announcements which are currently invalid because they are not allowed for these ASNs:"
|
||||
)?;
|
||||
for auth in &self.invalid_asn {
|
||||
writeln!(f, " {}", auth)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
if !self.keep.is_empty() {
|
||||
writeln!(f, "Keep the following entries:")?;
|
||||
for auth in &self.keep {
|
||||
writeln!(f, " {}", auth)?;
|
||||
}
|
||||
writeln!(f)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
//------------ BgpAnalysisReport -------------------------------------------
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
@@ -20,6 +178,10 @@ impl BgpAnalysisReport {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn into_entries(self) -> Vec<BgpAnalysisEntry> {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn matching_defs(&self, state: BgpAnalysisState) -> Vec<&RoaDefinition> {
|
||||
self.matching_entries(state)
|
||||
.into_iter()
|
||||
@@ -208,6 +370,10 @@ impl BgpAnalysisEntry {
|
||||
&self.definition
|
||||
}
|
||||
|
||||
pub fn into_definition(self) -> RoaDefinition {
|
||||
self.definition
|
||||
}
|
||||
|
||||
pub fn state(&self) -> BgpAnalysisState {
|
||||
self.state
|
||||
}
|
||||
|
||||
@@ -589,10 +589,7 @@ async fn api_ca_routes(req: Request, path: &mut RequestPath, ca: Handle) -> Rout
|
||||
Method::POST => ca_routes_update(req, ca).await,
|
||||
_ => render_unknown_method(),
|
||||
},
|
||||
Some("analysis") => match *req.method() {
|
||||
Method::GET => ca_routes_analysis(req, path, ca).await,
|
||||
_ => render_unknown_method(),
|
||||
},
|
||||
Some("analysis") => ca_routes_analysis(req, path, ca).await,
|
||||
_ => render_unknown_method(),
|
||||
}
|
||||
}
|
||||
@@ -1078,6 +1075,19 @@ async fn ca_routes_show(req: Request, handle: Handle) -> RoutingResult {
|
||||
async fn ca_routes_analysis(req: Request, path: &mut RequestPath, handle: Handle) -> RoutingResult {
|
||||
match path.next() {
|
||||
Some("full") => render_json_res(req.state().read().await.ca_routes_bgp_analysis(&handle)),
|
||||
Some("suggest") => match *req.method() {
|
||||
Method::GET => render_json_res(req.state().read().await.ca_routes_bgp_suggest(&handle, None)),
|
||||
Method::POST => {
|
||||
let server = req.state().clone();
|
||||
match req.json().await {
|
||||
Err(e) => render_error(e),
|
||||
Ok(resources) => {
|
||||
render_json_res(server.read().await.ca_routes_bgp_suggest(&handle, Some(resources)))
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => render_unknown_method(),
|
||||
},
|
||||
_ => render_unknown_method(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ use crate::commons::api::{
|
||||
AddChildRequest, AllCertAuthIssues, CaCommandDetails, CaRepoDetails, CertAuthInfo, CertAuthInit, CertAuthIssues,
|
||||
CertAuthList, CertAuthStats, ChildCaInfo, ChildHandle, CommandHistory, CommandHistoryCriteria, CurrentRepoState,
|
||||
Handle, ListReply, ParentCaContact, ParentCaReq, ParentHandle, PublishDelta, PublisherDetails, PublisherHandle,
|
||||
RepoInfo, RepositoryContact, RepositoryUpdate, RoaDefinition, RoaDefinitionUpdates, ServerInfo, TaCertDetails,
|
||||
UpdateChildRequest,
|
||||
RepoInfo, RepositoryContact, RepositoryUpdate, ResourceSet, RoaDefinition, RoaDefinitionUpdates, ServerInfo,
|
||||
TaCertDetails, UpdateChildRequest,
|
||||
};
|
||||
use crate::commons::bgp::{BgpAnalyser, BgpAnalysisReport};
|
||||
use crate::commons::bgp::{BgpAnalyser, BgpAnalysisReport, BgpAnalysisSuggestion};
|
||||
use crate::commons::error::Error;
|
||||
use crate::commons::eventsourcing::CommandKey;
|
||||
use crate::commons::remote::rfc8183;
|
||||
@@ -621,6 +621,22 @@ impl KrillServer {
|
||||
let resources = ca.all_resources();
|
||||
Ok(self.bgp_analyser.analyse(definitions.as_slice(), &resources))
|
||||
}
|
||||
|
||||
pub fn ca_routes_bgp_suggest(
|
||||
&self,
|
||||
handle: &Handle,
|
||||
scope: Option<ResourceSet>,
|
||||
) -> KrillResult<BgpAnalysisSuggestion> {
|
||||
let ca = self.caserver.get_ca(handle)?;
|
||||
let definitions = ca.roa_definitions();
|
||||
let mut resources = ca.all_resources();
|
||||
|
||||
if let Some(scope) = scope {
|
||||
resources = resources.intersection(&scope);
|
||||
}
|
||||
|
||||
Ok(self.bgp_analyser.suggest(definitions.as_slice(), &resources))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Handle publication requests
|
||||
|
||||
+12
-2
@@ -25,11 +25,11 @@ use crate::commons::api::{
|
||||
ResourceClassKeysInfo, ResourceClassName, ResourceSet, RoaDefinition, RoaDefinitionUpdates, TypedPrefix,
|
||||
UpdateChildRequest,
|
||||
};
|
||||
use crate::commons::bgp::Announcement;
|
||||
use crate::commons::bgp::{Announcement, BgpAnalysisSuggestion};
|
||||
use crate::commons::remote::rfc8183;
|
||||
use crate::commons::remote::rfc8183::ChildRequest;
|
||||
use crate::commons::util::httpclient;
|
||||
use crate::constants::KRILL_ENV_TEST_UNIT_DATA;
|
||||
use crate::constants::{KRILL_ENV_TEST_ANN, KRILL_ENV_TEST_UNIT_DATA};
|
||||
use crate::daemon::ca::{ta_handle, ResourceTaggedAttestation, RtaRequest, SignSupport};
|
||||
use crate::daemon::http::server;
|
||||
|
||||
@@ -62,6 +62,7 @@ pub async fn start_krill() -> PathBuf {
|
||||
let data_dir = sub_dir(&dir);
|
||||
|
||||
env::set_var(KRILL_ENV_TEST_UNIT_DATA, data_dir.to_string_lossy().to_string());
|
||||
env::set_var(KRILL_ENV_TEST_ANN, "1");
|
||||
|
||||
tokio::spawn(server::start());
|
||||
|
||||
@@ -93,6 +94,7 @@ pub async fn init_child(handle: &Handle) {
|
||||
krill_admin(Command::CertAuth(CaCommand::Init(CertAuthInit::new(handle.clone())))).await;
|
||||
}
|
||||
|
||||
// We use embedded when not testing RFC 8181 - so that the CMS signing/verification overhead can be reduced.
|
||||
pub async fn init_child_with_embedded_repo(handle: &Handle) {
|
||||
krill_admin(Command::CertAuth(CaCommand::Init(CertAuthInit::new(handle.clone())))).await;
|
||||
krill_admin(Command::CertAuth(CaCommand::RepoUpdate(
|
||||
@@ -125,6 +127,7 @@ pub async fn child_request(handle: &Handle) -> rfc8183::ChildRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// We use embedded when not testing RFC 6492 - so that the CMS signing/verification overhead can be reduced.
|
||||
pub async fn add_child_to_ta_embedded(handle: &Handle, resources: ResourceSet) -> ParentCaContact {
|
||||
let auth = ChildAuthRequest::Embedded;
|
||||
let req = AddChildRequest::new(handle.clone(), resources, auth);
|
||||
@@ -236,6 +239,13 @@ pub async fn ca_route_authorizations_update_expect_error(handle: &Handle, update
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn ca_route_authorizations_suggestions(handle: &Handle) -> BgpAnalysisSuggestion {
|
||||
match krill_admin(Command::CertAuth(CaCommand::BgpAnalysisSuggest(handle.clone(), None))).await {
|
||||
ApiResponse::BgpAnalysisSuggestions(suggestion) => suggestion,
|
||||
_ => panic!("Expected ROA suggestion"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ca_details(handle: &Handle) -> CertAuthInfo {
|
||||
match krill_admin(Command::CertAuth(CaCommand::Show(handle.clone()))).await {
|
||||
ApiResponse::CertAuthInfo(inf) => inf,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"stale": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "10.0.3.0/24"
|
||||
},
|
||||
{
|
||||
"asn": 0,
|
||||
"prefix": "10.0.4.0/24"
|
||||
}
|
||||
],
|
||||
"not_found": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "10.0.0.0/21"
|
||||
},
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "192.168.0.0/24"
|
||||
},
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "192.168.0.0/24"
|
||||
}
|
||||
],
|
||||
"invalid_asn": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "10.0.0.0/22"
|
||||
}
|
||||
],
|
||||
"invalid_length": [
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/24"
|
||||
}
|
||||
],
|
||||
"too_permissive": [
|
||||
{
|
||||
"current": {
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/22",
|
||||
"max_length": 23
|
||||
},
|
||||
"new": [
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/22"
|
||||
},
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.2.0/23"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"keep": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "192.168.1.0/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"stale": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "10.0.3.0/24"
|
||||
}
|
||||
],
|
||||
"not_found": [],
|
||||
"invalid_asn": [
|
||||
{
|
||||
"asn": 64497,
|
||||
"prefix": "10.0.0.0/22"
|
||||
}
|
||||
],
|
||||
"invalid_length": [
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/24"
|
||||
}
|
||||
],
|
||||
"too_permissive": [
|
||||
{
|
||||
"current": {
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/22",
|
||||
"max_length": 23
|
||||
},
|
||||
"new": [
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.0.0/22"
|
||||
},
|
||||
{
|
||||
"asn": 64496,
|
||||
"prefix": "10.0.2.0/23"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"keep": []
|
||||
}
|
||||
@@ -129,5 +129,17 @@ async fn ca_roas() {
|
||||
ca_route_authorizations_update(&child, updates).await;
|
||||
assert!(will_publish_objects(&child, &[crl_file, mft_file, route1_file],).await);
|
||||
|
||||
// Get ROA suggestions, expect that we can submit the suggestions as an update, and then we
|
||||
// should see no more suggestions.
|
||||
let suggestion = ca_route_authorizations_suggestions(&child).await;
|
||||
let updates: RoaDefinitionUpdates = suggestion.into();
|
||||
assert!(!updates.is_empty());
|
||||
|
||||
ca_route_authorizations_update(&child, updates).await;
|
||||
|
||||
let remaining_suggestion = ca_route_authorizations_suggestions(&child).await;
|
||||
let updates: RoaDefinitionUpdates = remaining_suggestion.into();
|
||||
assert!(updates.is_empty());
|
||||
|
||||
let _ = fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user