Import delegated ca 1133 (#1136)

* Add mapping to support that parent and child use different rc names.
* Export child (so that we can test importing it).
* Import child.
* Force parent refresh in resource check loop.
This commit is contained in:
Tim Bruijnzeels
2023-10-17 15:31:11 +02:00
parent 375ff4dcad
commit ec42abed0a
16 changed files with 769 additions and 99 deletions
+14 -1
View File
@@ -281,6 +281,16 @@ impl KrillClient {
delete(&self.server, &self.token, &uri).await?;
Ok(ApiResponse::Empty)
}
CaCommand::ChildExport(handle, child) => {
let uri = format!("api/v1/cas/{}/children/{}/export", handle, child);
let response = get_json(&self.server, &self.token, &uri).await?;
Ok(ApiResponse::ChildExported(response))
}
CaCommand::ChildImport(handle, child) => {
let uri = format!("api/v1/cas/{}/children/{}/import", handle, child.name);
post_json(&self.server, &self.token, &uri, child).await?;
Ok(ApiResponse::Empty)
}
CaCommand::ChildConnections(handle) => {
let uri = format!("api/v1/cas/{}/stats/children/connections", handle);
let stats: ChildrenConnectionStats = get_json(&self.server, &self.token, &uri).await?;
@@ -547,7 +557,10 @@ impl KrillClient {
);
if let Some(storage_uri) = details.data_dir() {
config = config.replace("### storage_uri = \"./data\"", &format!("storage_uri = \"{}\"", storage_uri))
config = config.replace(
"### storage_uri = \"./data\"",
&format!("storage_uri = \"{}\"", storage_uri),
)
}
if let Some(log_file) = details.log_file() {
+6 -4
View File
@@ -30,10 +30,10 @@ use crate::{
cli::report::{ReportError, ReportFormat},
commons::{
api::{
self, AddChildRequest, AspaCustomer, AspaDefinition, AspaDefinitionFormatError, AspaProvidersUpdate,
AuthorizationFmtError, BgpSecAsnKey, BgpSecDefinition, CertAuthInit, ParentCaReq, PublicationServerUris,
RepoFileDeleteCriteria, RoaConfiguration, RoaConfigurationUpdates, RoaPayload, RtaName, Token,
UpdateChildRequest,
self, import::ImportChild, AddChildRequest, AspaCustomer, AspaDefinition, AspaDefinitionFormatError,
AspaProvidersUpdate, AuthorizationFmtError, BgpSecAsnKey, BgpSecDefinition, CertAuthInit, ParentCaReq,
PublicationServerUris, RepoFileDeleteCriteria, RoaConfiguration, RoaConfigurationUpdates, RoaPayload,
RtaName, Token, UpdateChildRequest,
},
crypto::SignSupport,
error::KrillIoError,
@@ -2542,6 +2542,8 @@ pub enum CaCommand {
ChildAdd(CaHandle, AddChildRequest),
ChildUpdate(CaHandle, ChildHandle, UpdateChildRequest),
ChildDelete(CaHandle, ChildHandle),
ChildExport(CaHandle, ChildHandle),
ChildImport(CaHandle, ImportChild),
ChildConnections(CaHandle),
// Key Management
+7 -4
View File
@@ -8,10 +8,10 @@ use rpki::ca::idexchange;
use crate::{
commons::{
api::{
AllCertAuthIssues, AspaDefinitionList, BgpSecCsrInfoList, CaCommandDetails, CaRepoDetails, CertAuthInfo,
CertAuthIssues, CertAuthList, ChildCaInfo, ChildrenConnectionStats, CommandHistory, ConfiguredRoas,
IdCertInfo, ParentCaContact, ParentStatuses, PublisherDetails, PublisherList, RepoStatus,
RepositoryContact, RtaList, RtaPrepResponse, ServerInfo,
import::ExportChild, AllCertAuthIssues, AspaDefinitionList, BgpSecCsrInfoList, CaCommandDetails,
CaRepoDetails, CertAuthInfo, CertAuthIssues, CertAuthList, ChildCaInfo, ChildrenConnectionStats,
CommandHistory, ConfiguredRoas, IdCertInfo, ParentCaContact, ParentStatuses, PublisherDetails,
PublisherList, RepoStatus, RepositoryContact, RtaList, RtaPrepResponse, ServerInfo,
},
bgp::{BgpAnalysisAdvice, BgpAnalysisReport, BgpAnalysisSuggestion},
},
@@ -50,6 +50,7 @@ pub enum ApiResponse {
ParentStatuses(ParentStatuses),
ChildInfo(ChildCaInfo),
ChildExported(ExportChild),
ChildrenStats(ChildrenConnectionStats),
PublisherDetails(PublisherDetails),
@@ -98,6 +99,7 @@ impl ApiResponse {
ApiResponse::ParentCaContact(contact) => Ok(Some(contact.report(fmt)?)),
ApiResponse::ParentStatuses(statuses) => Ok(Some(statuses.report(fmt)?)),
ApiResponse::ChildInfo(info) => Ok(Some(info.report(fmt)?)),
ApiResponse::ChildExported(child) => Ok(Some(child.report(fmt)?)),
ApiResponse::ChildrenStats(stats) => Ok(Some(stats.report(fmt)?)),
ApiResponse::PublisherList(list) => Ok(Some(list.report(fmt)?)),
ApiResponse::PublisherDetails(details) => Ok(Some(details.report(fmt)?)),
@@ -187,6 +189,7 @@ impl Report for IdCertInfo {}
impl Report for RepositoryContact {}
impl Report for ChildCaInfo {}
impl Report for ExportChild {}
impl Report for ParentCaContact {}
impl Report for ParentStatuses {}
+38 -2
View File
@@ -9,6 +9,7 @@ use rpki::{
idcert::IdCert,
idexchange::{self, ServiceUri},
idexchange::{CaHandle, ChildHandle, ParentHandle, PublisherHandle, RepoInfo},
provisioning::ResourceClassName,
},
crypto::PublicKey,
repository::resources::ResourceSet,
@@ -546,6 +547,15 @@ pub struct UpdateChildRequest {
#[serde(skip_serializing_if = "Option::is_none")]
suspend: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
resource_class_name_mapping: Option<ResourceClassNameMapping>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ResourceClassNameMapping {
pub name_in_parent: ResourceClassName,
pub name_for_child: ResourceClassName,
}
impl UpdateChildRequest {
@@ -554,6 +564,7 @@ impl UpdateChildRequest {
id_cert,
resources,
suspend,
resource_class_name_mapping: None,
}
}
pub fn id_cert(id_cert: IdCert) -> Self {
@@ -561,6 +572,7 @@ impl UpdateChildRequest {
id_cert: Some(id_cert),
resources: None,
suspend: None,
resource_class_name_mapping: None,
}
}
@@ -569,6 +581,7 @@ impl UpdateChildRequest {
id_cert: None,
resources: Some(resources),
suspend: None,
resource_class_name_mapping: None,
}
}
@@ -577,6 +590,7 @@ impl UpdateChildRequest {
id_cert: None,
resources: None,
suspend: Some(true),
resource_class_name_mapping: None,
}
}
@@ -585,11 +599,33 @@ impl UpdateChildRequest {
id_cert: None,
resources: None,
suspend: Some(false),
resource_class_name_mapping: None,
}
}
pub fn unpack(self) -> (Option<IdCert>, Option<ResourceSet>, Option<bool>) {
(self.id_cert, self.resources, self.suspend)
pub fn resource_class_name_mapping(mapping: ResourceClassNameMapping) -> Self {
UpdateChildRequest {
id_cert: None,
resources: None,
suspend: None,
resource_class_name_mapping: Some(mapping),
}
}
pub fn unpack(
self,
) -> (
Option<IdCert>,
Option<ResourceSet>,
Option<bool>,
Option<ResourceClassNameMapping>,
) {
(
self.id_cert,
self.resources,
self.suspend,
self.resource_class_name_mapping,
)
}
}
+37 -1
View File
@@ -23,7 +23,7 @@ use crate::{
daemon::ca::{CertAuth, DropReason},
};
use super::{AspaDefinitionUpdates, ResourceSetSummary};
use super::{AspaDefinitionUpdates, ResourceClassNameMapping, ResourceSetSummary};
//------------ CommandHistory ------------------------------------------------
@@ -367,6 +367,11 @@ pub enum CertAuthStorableCommand {
ski: String,
resources: ResourceSet,
},
ChildImport {
child: ChildHandle,
ski: String,
resources: ResourceSet,
},
ChildUpdateResources {
child: ChildHandle,
resources: ResourceSet,
@@ -375,6 +380,10 @@ pub enum CertAuthStorableCommand {
child: ChildHandle,
ski: String,
},
ChildUpdateResourceClassNameMapping {
child: ChildHandle,
mapping: ResourceClassNameMapping,
},
ChildCertify {
child: ChildHandle,
resource_class_name: ResourceClassName,
@@ -474,6 +483,12 @@ impl WithStorableDetails for CertAuthStorableCommand {
.with_id_ski(ski.as_ref())
.with_resources(resources)
}
CertAuthStorableCommand::ChildImport { child, ski, resources } => {
CommandSummary::new("cmd-ca-child-import", self)
.with_child(child)
.with_id_ski(ski)
.with_resources(resources)
}
CertAuthStorableCommand::ChildUpdateResources { child, resources } => {
CommandSummary::new("cmd-ca-child-update-res", self)
.with_child(child)
@@ -484,6 +499,12 @@ impl WithStorableDetails for CertAuthStorableCommand {
.with_child(child)
.with_id_ski(ski)
}
CertAuthStorableCommand::ChildUpdateResourceClassNameMapping { child, mapping } => {
CommandSummary::new("cmd-ca-child-update-rcn-mapping", self)
.with_child(child)
.with_arg("parent_rcn", &mapping.name_in_parent)
.with_arg("child_rcn", &mapping.name_for_child)
}
CertAuthStorableCommand::ChildCertify {
child,
resource_class_name,
@@ -614,6 +635,14 @@ impl fmt::Display for CertAuthStorableCommand {
child, ski, summary
)
}
CertAuthStorableCommand::ChildImport { child, ski, resources } => {
let summary = ResourceSetSummary::from(resources);
write!(
f,
"Import child '{}' with RFC8183 key '{}' and resources '{}'",
child, ski, summary
)
}
CertAuthStorableCommand::ChildUpdateResources { child, resources } => {
let summary = ResourceSetSummary::from(resources);
write!(f, "Update resources for child '{}' to: {}", child, summary)
@@ -621,6 +650,13 @@ impl fmt::Display for CertAuthStorableCommand {
CertAuthStorableCommand::ChildUpdateId { child, ski } => {
write!(f, "Update child '{}' RFC 8183 key '{}'", child, ski)
}
CertAuthStorableCommand::ChildUpdateResourceClassNameMapping { child, mapping } => {
write!(
f,
"Update child '{}' map parent RC '{}' to '{}' for child",
child, mapping.name_in_parent, mapping.name_for_child
)
}
CertAuthStorableCommand::ChildCertify { child, ki, .. } => {
write!(f, "Issue certificate to child '{}' for key '{}'", child, ki)
}
+81 -16
View File
@@ -1,23 +1,29 @@
//! Data types used to support importing a CA structure for testing or automated set ups.
use std::collections::HashMap;
use std::{collections::HashMap, fmt};
use serde::{Deserialize, Deserializer};
use rpki::{
ca::idexchange::{CaHandle, ParentHandle},
ca::{
idcert::IdCert,
idexchange::{CaHandle, ChildHandle, ParentHandle},
provisioning::ResourceClassName,
},
repository::resources::ResourceSet,
uri,
};
use crate::{
commons::{api::PublicationServerUris, error::Error, KrillResult},
commons::{api::PublicationServerUris, crypto::CsrInfo, error::Error, KrillResult},
daemon::config,
ta::ta_handle,
};
use super::RoaConfiguration;
//------------ Structure -----------------------------------------------------
/// This type contains the full structure of CAs and signed objects etc that is
/// set up when the import API is used.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
@@ -30,19 +36,6 @@ pub struct Structure {
pub cas: Vec<ImportCa>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ImportTa {
pub ta_aia: uri::Rsync,
pub ta_uri: uri::Https,
pub ta_key_pem: Option<String>,
}
impl ImportTa {
pub fn unpack(self) -> (uri::Rsync, Vec<uri::Https>, Option<String>) {
(self.ta_aia, vec![self.ta_uri], self.ta_key_pem)
}
}
impl Structure {
pub fn new(
ta_aia: uri::Rsync,
@@ -129,6 +122,23 @@ where
config::OneOrMany::<ImportParent>::deserialize(deserializer).map(|oom| oom.into())
}
//------------ ImportTa ------------------------------------------------------
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ImportTa {
pub ta_aia: uri::Rsync,
pub ta_uri: uri::Https,
pub ta_key_pem: Option<String>,
}
impl ImportTa {
pub fn unpack(self) -> (uri::Rsync, Vec<uri::Https>, Option<String>) {
(self.ta_aia, vec![self.ta_uri], self.ta_key_pem)
}
}
//------------ ImportCa ------------------------------------------------------
/// This type describes a CaStructure that needs to be imported. I.e. it describes
/// a CA at the top of a branch and recursively includes 0 or more children of this
/// same type.
@@ -155,6 +165,8 @@ impl ImportCa {
}
}
//------------ ImportParent --------------------------------------------------
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ImportParent {
handle: ParentHandle,
@@ -175,6 +187,59 @@ impl ImportParent {
}
}
//------------ ImportChild ---------------------------------------------------
pub type ExportChild = ImportChild;
/// Describes a child CA that can be imported from, or exported to,
/// another parent CA instance.
///
/// Only supports the simplest scenario where the child has only
/// one certificate, in only one resource class.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ImportChild {
pub name: ChildHandle,
pub id_cert: IdCert,
pub resources: ResourceSet,
pub issued_cert: ImportChildCertificate,
}
pub type ChildResourceClassName = ResourceClassName;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ImportChildCertificate {
#[serde(flatten)]
pub csr: CsrInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub class_name: Option<ChildResourceClassName>,
}
impl fmt::Display for ImportChild {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "Name: {}", self.name)?;
writeln!(
f,
"Id Key: {}",
self.id_cert.public_key().key_identifier().to_string()
)?;
writeln!(f, "Resources: {}", self.resources)?;
if let Some(class_name) = &self.issued_cert.class_name {
writeln!(f, "Classname: {}", class_name)?;
}
let (ca_repository, rpki_manifest, rpki_notify, key) = self.issued_cert.csr.clone().unpack();
writeln!(f, "Issued Certificate:")?;
writeln!(f, " Key Id: {}", key.key_identifier())?;
writeln!(f, " CA repo: {}", ca_repository)?;
writeln!(f, " CA mft: {}", rpki_manifest)?;
if let Some(rrdp) = rpki_notify {
writeln!(f, " RRDP: {}", rrdp)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
+209 -41
View File
@@ -1,4 +1,10 @@
use std::{collections::HashMap, convert::TryFrom, ops::Deref, sync::Arc, vec};
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
ops::Deref,
sync::Arc,
vec,
};
use bytes::Bytes;
use chrono::Duration;
@@ -25,10 +31,11 @@ use rpki::{
use crate::{
commons::{
api::{
import::{ExportChild, ImportChild, ImportChildCertificate},
AspaCustomer, AspaDefinition, AspaDefinitionList, AspaDefinitionUpdates, AspaProvidersUpdate, BgpSecAsnKey,
BgpSecCsrInfoList, BgpSecDefinitionUpdates, CertAuthInfo, CertAuthStorableCommand, ConfiguredRoa,
IdCertInfo, IssuedCertificate, ObjectName, ParentCaContact, ReceivedCert, RepositoryContact, Revocation,
RoaConfiguration, RoaConfigurationUpdates, RtaList, RtaName, RtaPrepResponse,
IdCertInfo, ObjectName, ParentCaContact, ReceivedCert, RepositoryContact, ResourceClassNameMapping,
Revocation, RoaConfiguration, RoaConfigurationUpdates, RtaList, RtaName, RtaPrepResponse,
},
crypto::{CsrInfo, KrillSigner},
error::{Error, RoaDeltaError},
@@ -215,6 +222,16 @@ impl Aggregate for CertAuth {
self.children.get_mut(&child).unwrap().set_resources(resources)
}
CertAuthEvent::ChildUpdatedResourceClassNameMapping {
child,
name_in_parent,
name_for_child,
} => self
.children
.get_mut(&child)
.unwrap()
.add_mapping(name_in_parent, name_for_child),
CertAuthEvent::ChildRemoved { child } => {
self.children.remove(&child);
}
@@ -417,10 +434,16 @@ impl Aggregate for CertAuth {
match command.into_details() {
// being a parent
CertAuthCommandDetails::ChildAdd(child, id_cert, resources) => self.child_add(child, id_cert, resources),
CertAuthCommandDetails::ChildImport(import_child, config, signer) => {
self.child_import(import_child, &config, signer)
}
CertAuthCommandDetails::ChildUpdateResources(child, res) => self.child_update_resources(&child, res),
CertAuthCommandDetails::ChildUpdateId(child, id_cert) => self.child_update_id_cert(&child, id_cert),
CertAuthCommandDetails::ChildUpdateResourceClassNameMapping(child, mapping) => {
self.child_resource_class_name_mapping(child, mapping)
}
CertAuthCommandDetails::ChildCertify(child, request, config, signer) => {
self.child_certify(child, request, &config, signer)
self.child_certify_from_command(child, request, &config, signer)
}
CertAuthCommandDetails::ChildRevokeKey(child, request) => self.child_revoke_key(child, request),
CertAuthCommandDetails::ChildRemove(child) => self.child_remove(&child),
@@ -596,6 +619,55 @@ impl CertAuth {
/// # Being a parent
///
impl CertAuth {
/// Export a child under this CA, if possible.
pub fn child_export(&self, child_handle: &ChildHandle) -> KrillResult<ExportChild> {
let child = self.get_child(child_handle)?;
let id_cert = child.id_cert().try_into()?;
let resources = child.resources().clone();
if self.resources.len() != 1 {
return Err(Error::custom(
"export child is not supported for multiple resource classes.",
));
}
let (my_rcn, rc) = self.resources.iter().next().unwrap(); // there is exactly 1 entry
let issued_key = {
let issued_keys = child.issued(my_rcn);
if issued_keys.len() != 1 {
return Err(Error::custom(
"export child is not supported if child has no issued certificate, or is doing a key rollover.",
));
}
issued_keys[0]
};
let issued_cert = rc
.issued(&issued_key)
.ok_or(Error::custom("no issued certificate found for child to export"))?;
let csr = issued_cert.csr_info().clone();
let class_name = {
let child_rcn = child.name_for_parent_rcn(my_rcn);
if my_rcn != &child_rcn {
Some(child_rcn)
} else {
None
}
};
let issued_cert = ImportChildCertificate { csr, class_name };
Ok(ExportChild {
name: child_handle.clone(),
id_cert,
resources,
issued_cert,
})
}
pub fn verify_rfc6492(&self, cms: ProvisioningCms) -> KrillResult<provisioning::Message> {
let child_handle = cms.message().sender().convert();
let child = self.get_child(&child_handle).map_err(|e| {
@@ -635,8 +707,8 @@ impl CertAuth {
) -> KrillResult<ResourceClassListResponse> {
let mut classes = vec![];
for rcn in self.resources.keys() {
if let Some(class) = self.entitlement_class(child_handle, rcn, issuance_timing)? {
for my_rcn in self.resources.keys() {
if let Some(class) = self.entitlement_class(child_handle, my_rcn, issuance_timing)? {
classes.push(class);
}
}
@@ -649,12 +721,12 @@ impl CertAuth {
pub fn issuance_response(
&self,
child_handle: &ChildHandle,
class_name: &ResourceClassName,
my_rcn: &ResourceClassName,
pub_key: &PublicKey,
issuance_timing: &IssuanceTimingConfig,
) -> KrillResult<IssuanceResponse> {
let entitlement_class = self
.entitlement_class(child_handle, class_name, issuance_timing)?
.entitlement_class(child_handle, my_rcn, issuance_timing)?
.ok_or(Error::KeyUseNoIssuedCert)?;
entitlement_class
@@ -666,10 +738,10 @@ impl CertAuth {
fn entitlement_class(
&self,
child_handle: &ChildHandle,
rcn: &ResourceClassName,
my_rcn: &ResourceClassName,
issuance_timing: &IssuanceTimingConfig,
) -> KrillResult<Option<ResourceClassEntitlements>> {
let my_rc = match self.resources.get(rcn) {
let my_rc = match self.resources.get(my_rcn) {
Some(rc) => rc,
None => return Ok(None),
};
@@ -700,7 +772,7 @@ impl CertAuth {
return Ok(None);
}
let child_keys = child.issued(rcn);
let child_keys = child.issued(my_rcn);
let mut issued_certs = vec![];
@@ -739,8 +811,10 @@ impl CertAuth {
}
}
let child_rcn = child.name_for_parent_rcn(my_rcn);
Ok(Some(ResourceClassEntitlements::new(
rcn.clone(),
child_rcn,
child_resources,
not_after,
issued_certs,
@@ -785,65 +859,132 @@ impl CertAuth {
}
}
/// Import a child (from another CA) and adopt it as our own.
fn child_import(
&self,
import_child: ImportChild,
config: &Config,
signer: Arc<KrillSigner>,
) -> KrillResult<Vec<CertAuthEvent>> {
// overview:
// - perform checks (e.g. not supported in case we have multiple RCs)
// - add the child
// - add the resource class mapping if given
// - sign a new certificate for the child
// Combine all events and return them.
let (child_handle, id_cert, resources, issued_cert) = (
import_child.name,
import_child.id_cert,
import_child.resources,
import_child.issued_cert,
);
let id_cert_info = IdCertInfo::from(id_cert);
let (class_name_override, csr_info) = (issued_cert.class_name, issued_cert.csr);
let limit = RequestResourceLimit::default(); // i.e. no limit
// Ensure that we have one, and only one, resource class
// and get its name.
let my_rcn = if self.resources.len() != 1 {
Err(Error::custom(
"cannot import CA unless parent has exactly one resource class",
))
} else {
self.resources
.keys()
.next()
.ok_or(Error::custom("cannot get resource class"))
}?
.clone();
let mut events = vec![];
// Add the child
events.append(&mut self.child_add(child_handle.clone(), id_cert_info, resources.clone())?);
// Add a resource class name mapping if applicable
if let Some(name_for_child) = class_name_override {
if name_for_child != my_rcn {
let mapping = ResourceClassNameMapping {
name_in_parent: my_rcn.clone(),
name_for_child,
};
events.push(CertAuthEvent::child_updated_resource_class_name_mapping(
child_handle.clone(),
mapping,
));
}
}
// Issue a certificate for the imported child
events.append(&mut self.child_certify(child_handle, &resources, my_rcn, csr_info, limit, config, signer)?);
Ok(events)
}
/// Certifies a child, unless:
/// = the child is unknown,
/// = the child is not authorized,
/// = the csr is invalid,
/// = the limit exceeds the child allocation,
/// = the signer throws up..
fn child_certify(
fn child_certify_from_command(
&self,
child: ChildHandle,
child_handle: ChildHandle,
request: IssuanceRequest,
config: &Config,
signer: Arc<KrillSigner>,
) -> KrillResult<Vec<CertAuthEvent>> {
let (rcn, limit, csr) = request.unpack();
let (child_rcn, limit, csr) = request.unpack();
let child = self.get_child(&child_handle)?;
let my_rcn = child.parent_name_for_rcn(&child_rcn);
let csr_info = CsrInfo::try_from(&csr)?;
self.child_certify(child_handle, child.resources(), my_rcn, csr_info, limit, config, signer)
}
fn child_certify(
&self,
child_handle: ChildHandle,
resources: &ResourceSet,
my_rcn: ResourceClassName,
csr_info: CsrInfo,
limit: RequestResourceLimit,
config: &Config,
signer: Arc<KrillSigner>,
) -> KrillResult<Vec<CertAuthEvent>> {
if !csr_info.global_uris() && !test_mode_enabled() {
return Err(Error::invalid_csr(
"MUST use hostnames in URIs for certificate requests.",
));
}
let issued =
self.issue_child_certificate(&child, rcn.clone(), csr_info, limit, &config.issuance_timing, &signer)?;
let my_rc = self
.resources
.get(&my_rcn)
.ok_or_else(|| Error::ResourceClassUnknown(my_rcn.clone()))?;
let issued = my_rc.issue_cert(csr_info, resources, limit, &config.issuance_timing, &signer)?;
let cert_name = ObjectName::new(&issued.key_identifier(), "cer");
info!(
"CA '{}' issued certificate '{}' to child '{}'",
self.handle, cert_name, child
self.handle, cert_name, child_handle
);
let issued_event = CertAuthEvent::child_certificate_issued(child, rcn.clone(), issued.key_identifier());
let issued_event =
CertAuthEvent::child_certificate_issued(child_handle, my_rcn.clone(), issued.key_identifier());
let mut cert_updates = ChildCertificateUpdates::default();
cert_updates.issue(issued);
let child_certs_updated = CertAuthEvent::child_certificates_updated(rcn, cert_updates);
let child_certs_updated = CertAuthEvent::child_certificates_updated(my_rcn, cert_updates);
Ok(vec![issued_event, child_certs_updated])
}
/// Issue a new child certificate.
fn issue_child_certificate(
&self,
child: &ChildHandle,
rcn: ResourceClassName,
csr_info: CsrInfo,
limit: RequestResourceLimit,
issuance_timing: &IssuanceTimingConfig,
signer: &KrillSigner,
) -> KrillResult<IssuedCertificate> {
let my_rc = self.resources.get(&rcn).ok_or(Error::ResourceClassUnknown(rcn))?;
let child = self.get_child(child)?;
// note this will ultimately return an error if the requested limit exceeds
// the child's resources.
my_rc.issue_cert(csr_info, child.resources(), limit, issuance_timing, signer)
}
/// Updates child Resource entitlements.
///
/// This does not yet revoke / reissue / republish anything.
@@ -906,6 +1047,32 @@ impl CertAuth {
}
}
/// Updates the child resource class name mapping
fn child_resource_class_name_mapping(
&self,
child_handle: ChildHandle,
mapping: ResourceClassNameMapping,
) -> KrillResult<Vec<CertAuthEvent>> {
// fails if the child is unknown.
let child = self.get_child(&child_handle)?;
if !self.resources.contains_key(&mapping.name_in_parent) {
warn!("About to update resource class name mapping for child '{}, but parent does not have any resource class called '{}', or at least not yet.", child_handle, &mapping.name_in_parent);
}
if !child.issued(&mapping.name_in_parent).is_empty() {
return Err(Error::Custom(format!(
"Cannot add mapping for RC '{}', child already received certificate(s).",
mapping.name_in_parent
)));
}
Ok(vec![CertAuthEvent::child_updated_resource_class_name_mapping(
child_handle,
mapping,
)])
}
/// Revokes a key for a child. So, add the last cert for the key to the CRL, and withdraw
/// the .cer file for it.
fn child_revoke_key(
@@ -913,9 +1080,10 @@ impl CertAuth {
child_handle: ChildHandle,
request: RevocationRequest,
) -> KrillResult<Vec<CertAuthEvent>> {
let (rcn, key) = request.unpack();
let (child_rcn, key) = request.unpack();
let child = self.get_child(&child_handle)?;
let my_rcn = child.parent_name_for_rcn(&child_rcn);
if !child.is_issued(&key) {
return Err(Error::KeyUseNoIssuedCert);
@@ -930,8 +1098,8 @@ impl CertAuth {
self.handle, cert_name, child_handle
);
let rev = CertAuthEvent::child_revoke_key(child_handle, rcn.clone(), key);
let upd = CertAuthEvent::child_certificates_updated(rcn, child_certificate_updates);
let rev = CertAuthEvent::child_revoke_key(child_handle, my_rcn.clone(), key);
let upd = CertAuthEvent::child_certificates_updated(my_rcn, child_certificate_updates);
Ok(vec![rev, upd])
}
+44 -9
View File
@@ -24,7 +24,7 @@ use crate::{
#[allow(clippy::large_enum_variant)]
#[serde(rename_all = "snake_case")]
pub enum UsedKeyState {
Current(ResourceClassName),
InUse(ResourceClassName), // Multiple keys are possible during a key rollover.
Revoked,
}
@@ -41,6 +41,8 @@ pub struct ChildDetails {
id_cert: IdCertInfo,
resources: ResourceSet,
used_keys: HashMap<KeyIdentifier, UsedKeyState>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
rcn_map: HashMap<ResourceClassName, ResourceClassName>,
}
impl ChildDetails {
@@ -50,6 +52,7 @@ impl ChildDetails {
id_cert,
resources,
used_keys: HashMap::new(),
rcn_map: HashMap::new(),
}
}
@@ -81,12 +84,44 @@ impl ChildDetails {
self.resources = resources;
}
pub fn issued(&self, rcn: &ResourceClassName) -> Vec<KeyIdentifier> {
pub fn add_mapping(&mut self, name_in_parent: ResourceClassName, name_for_child: ResourceClassName) {
self.rcn_map.insert(name_in_parent, name_for_child);
}
/// Resolve the resource class name used by the child, to the
/// internal name used by its parent.
///
/// Note that the parent and child usually use the same name, but
/// we need this mapping in case a delegated child CA was exported
/// from somewhere and then imported into Krill. In such cases the
/// resource class names that were used for the child may not match
/// the internal resource class names used. See issue: 1133
pub(super) fn name_for_parent_rcn(&self, name_in_parent: &ResourceClassName) -> ResourceClassName {
self.rcn_map.get(name_in_parent).unwrap_or(name_in_parent).clone()
}
/// Resolve the resource class name used by the parent, to the
/// name used by the child in request and responses.
///
/// Note that the parent and child usually use the same name, but
/// we need this mapping in case a delegated child CA was exported
/// from somewhere and then imported into Krill. In such cases the
/// resource class names that were used for the child may not match
/// the internal resource class names used. See issue: 1133
pub(super) fn parent_name_for_rcn(&self, name_in_child: &ResourceClassName) -> ResourceClassName {
self.rcn_map
.iter()
.find(|(_k, v)| *v == name_in_child)
.map(|(k, _v)| k.clone())
.unwrap_or_else(|| name_in_child.clone())
}
pub fn issued(&self, parent_rcn: &ResourceClassName) -> Vec<KeyIdentifier> {
let mut res = vec![];
for (ki, used_key_state) in self.used_keys.iter() {
if let UsedKeyState::Current(found_rcn) = used_key_state {
if found_rcn == rcn {
if let UsedKeyState::InUse(found_rcn) = used_key_state {
if found_rcn == parent_rcn {
res.push(*ki)
}
}
@@ -96,11 +131,11 @@ impl ChildDetails {
}
pub fn is_issued(&self, ki: &KeyIdentifier) -> bool {
matches!(self.used_keys.get(ki), Some(UsedKeyState::Current(_)))
matches!(self.used_keys.get(ki), Some(UsedKeyState::InUse(_)))
}
pub fn add_issue_response(&mut self, rcn: ResourceClassName, ki: KeyIdentifier) {
self.used_keys.insert(ki, UsedKeyState::Current(rcn));
pub fn add_issue_response(&mut self, parent_rcn: ResourceClassName, ki: KeyIdentifier) {
self.used_keys.insert(ki, UsedKeyState::InUse(parent_rcn));
}
pub fn add_revoke_response(&mut self, ki: KeyIdentifier) {
@@ -108,11 +143,11 @@ impl ChildDetails {
}
/// Returns an error in case the key is already in use in another class.
pub fn verify_key_allowed(&self, ki: &KeyIdentifier, rcn: &ResourceClassName) -> KrillResult<()> {
pub fn verify_key_allowed(&self, ki: &KeyIdentifier, parent_rcn: &ResourceClassName) -> KrillResult<()> {
if let Some(last_response) = self.used_keys.get(ki) {
let allowed = match last_response {
UsedKeyState::Revoked => false,
UsedKeyState::Current(found) => found == rcn,
UsedKeyState::InUse(found) => found == parent_rcn,
};
if !allowed {
return Err(Error::KeyUseAttemptReuse);
+47 -3
View File
@@ -17,9 +17,9 @@ use crate::{
commons::{
actor::Actor,
api::{
AspaCustomer, AspaDefinitionUpdates, AspaProvidersUpdate, BgpSecDefinitionUpdates, CertAuthStorableCommand,
IdCertInfo, ParentCaContact, ReceivedCert, RepositoryContact, RoaConfigurationUpdates, RtaName,
StorableRcEntitlement,
import::ImportChild, AspaCustomer, AspaDefinitionUpdates, AspaProvidersUpdate, BgpSecDefinitionUpdates,
CertAuthStorableCommand, IdCertInfo, ParentCaContact, ReceivedCert, RepositoryContact,
ResourceClassNameMapping, RoaConfigurationUpdates, RtaName, StorableRcEntitlement,
},
crypto::KrillSigner,
eventsourcing::{self, InitCommandDetails, SentCommand, SentInitCommand, WithStorableDetails},
@@ -83,6 +83,9 @@ pub enum CertAuthCommandDetails {
// Add a new child under this parent CA
ChildAdd(ChildHandle, IdCertInfo, ResourceSet),
// Import a child under this parent CA
ChildImport(ImportChild, Arc<Config>, Arc<KrillSigner>),
// Update the resource entitlements for an existing child.
ChildUpdateResources(ChildHandle, ResourceSet),
@@ -90,6 +93,10 @@ pub enum CertAuthCommandDetails {
// provisioning protocol.
ChildUpdateId(ChildHandle, IdCertInfo),
// Update the mapping the parent uses to map its own resource
// class name to another name for the child.
ChildUpdateResourceClassNameMapping(ChildHandle, ResourceClassNameMapping),
// Process an issuance request sent by an existing child.
ChildCertify(ChildHandle, IssuanceRequest, Arc<Config>, Arc<KrillSigner>),
@@ -258,6 +265,11 @@ impl From<CertAuthCommandDetails> for CertAuthStorableCommand {
ski: id_cert.public_key().key_identifier().to_string(),
resources,
},
CertAuthCommandDetails::ChildImport(import_child, _, _) => CertAuthStorableCommand::ChildImport {
child: import_child.name,
ski: import_child.id_cert.public_key().key_identifier().to_string(),
resources: import_child.resources,
},
CertAuthCommandDetails::ChildUpdateResources(child, resources) => {
CertAuthStorableCommand::ChildUpdateResources { child, resources }
}
@@ -265,6 +277,9 @@ impl From<CertAuthCommandDetails> for CertAuthStorableCommand {
child,
ski: id_cert.public_key().key_identifier().to_string(),
},
CertAuthCommandDetails::ChildUpdateResourceClassNameMapping(child, mapping) => {
CertAuthStorableCommand::ChildUpdateResourceClassNameMapping { child, mapping }
}
CertAuthCommandDetails::ChildCertify(child, req, _, _) => {
let (resource_class_name, limit, csr) = req.unpack();
let ki = csr.public_key().key_identifier();
@@ -397,6 +412,21 @@ impl CertAuthCommandDetails {
)
}
pub fn child_import(
handle: &CaHandle,
child: ImportChild,
config: Arc<Config>,
signer: Arc<KrillSigner>,
actor: &Actor,
) -> CertAuthCommand {
eventsourcing::SentCommand::new(
handle,
None,
CertAuthCommandDetails::ChildImport(child, config, signer),
actor,
)
}
pub fn child_update_resources(
handle: &CaHandle,
child_handle: ChildHandle,
@@ -425,6 +455,20 @@ impl CertAuthCommandDetails {
)
}
pub fn child_update_resource_class_name_mapping(
handle: &CaHandle,
child_handle: ChildHandle,
mapping: ResourceClassNameMapping,
actor: &Actor,
) -> CertAuthCommand {
eventsourcing::SentCommand::new(
handle,
None,
CertAuthCommandDetails::ChildUpdateResourceClassNameMapping(child_handle, mapping),
actor,
)
}
/// Certify a child. Will return an error in case the child is
/// unknown, or in case resources are not held by the child.
pub fn child_certify(
+30 -2
View File
@@ -13,7 +13,8 @@ use crate::{
commons::{
api::{
AspaCustomer, AspaDefinition, AspaProvidersUpdate, BgpSecAsnKey, IdCertInfo, IssuedCertificate, ObjectName,
ParentCaContact, ReceivedCert, RepositoryContact, RoaAggregateKey, RtaName, SuspendedCert, UnsuspendedCert,
ParentCaContact, ReceivedCert, RepositoryContact, ResourceClassNameMapping, RoaAggregateKey, RtaName,
SuspendedCert, UnsuspendedCert,
},
crypto::KrillSigner,
eventsourcing::{Event, InitEvent},
@@ -460,6 +461,11 @@ pub enum CertAuthEvent {
child: ChildHandle,
resources: ResourceSet,
},
ChildUpdatedResourceClassNameMapping {
child: ChildHandle,
name_in_parent: ResourceClassName,
name_for_child: ResourceClassName,
},
ChildRemoved {
child: ChildHandle,
},
@@ -704,7 +710,6 @@ impl CertAuthEvent {
updates,
}
}
pub(super) fn child_removed(child: ChildHandle) -> CertAuthEvent {
CertAuthEvent::ChildRemoved { child }
}
@@ -716,6 +721,17 @@ impl CertAuthEvent {
pub(super) fn child_unsuspended(child: ChildHandle) -> CertAuthEvent {
CertAuthEvent::ChildUnsuspended { child }
}
pub(super) fn child_updated_resource_class_name_mapping(
child: ChildHandle,
mapping: ResourceClassNameMapping,
) -> CertAuthEvent {
CertAuthEvent::ChildUpdatedResourceClassNameMapping {
child,
name_in_parent: mapping.name_in_parent,
name_for_child: mapping.name_for_child,
}
}
}
impl fmt::Display for CertAuthEvent {
@@ -804,6 +820,18 @@ impl fmt::Display for CertAuthEvent {
CertAuthEvent::ChildUpdatedResources { child, resources } => {
write!(f, "updated child '{}' resources to '{}'", child, resources)
}
CertAuthEvent::ChildUpdatedResourceClassNameMapping {
child,
name_in_parent,
name_for_child,
} => {
write!(
f,
"updated child '{}' map parent RC name '{}' to '{}' for child",
child, name_in_parent, name_for_child
)
}
CertAuthEvent::ChildRemoved { child } => write!(f, "removed child '{}'", child),
CertAuthEvent::ChildSuspended { child } => write!(f, "suspended child '{}'", child),
CertAuthEvent::ChildUnsuspended { child } => write!(f, "unsuspended child '{}'", child),
+52 -11
View File
@@ -24,8 +24,10 @@ use crate::{
commons::{
actor::Actor,
api::{
rrdp::PublishElement, BgpSecCsrInfoList, BgpSecDefinitionUpdates, IdCertInfo, ParentServerInfo,
PublicationServerInfo, RoaConfigurationUpdates, Timestamp,
import::{ExportChild, ImportChild},
rrdp::PublishElement,
BgpSecCsrInfoList, BgpSecDefinitionUpdates, IdCertInfo, ParentServerInfo, PublicationServerInfo,
RoaConfigurationUpdates, Timestamp,
},
api::{
AddChildRequest, AspaCustomer, AspaDefinitionList, AspaDefinitionUpdates, AspaProvidersUpdate,
@@ -650,6 +652,35 @@ impl CaManager {
ca.get_child(child).map(|details| details.clone().into())
}
/// Export a child. Fails if:
/// - the child does not exist
/// - the child has no received certificate
/// - the child has more than one received certificate or resource class
///
/// Primarily meant for testing that the child import function works.
pub async fn ca_child_export(&self, ca: &CaHandle, child_handle: &ChildHandle) -> KrillResult<ExportChild> {
trace!("Exporting CA: {} under parent: {}", child_handle, ca);
self.get_ca(ca).await?.child_export(child_handle)
}
/// Import a child under the given CA. Will fail if:
/// - the ca does not exist
/// - the ca has less than, or more than one resource class
/// - the ca does not hold the resources for the child
/// - the child already exists
pub async fn ca_child_import(&self, ca: &CaHandle, import_child: ImportChild, actor: &Actor) -> KrillResult<()> {
trace!("Importing CA: {} under parent: {}", import_child.name, ca);
self.send_ca_command(CertAuthCommandDetails::child_import(
ca,
import_child,
self.config.clone(),
self.signer.clone(),
actor,
))
.await?;
Ok(())
}
/// Show a contact for a child.
pub async fn ca_parent_contact(
&self,
@@ -710,7 +741,7 @@ impl CaManager {
req: UpdateChildRequest,
actor: &Actor,
) -> KrillResult<()> {
let (id_opt, resources_opt, suspend_opt) = req.unpack();
let (id_opt, resources_opt, suspend_opt, resource_class_name_mapping_opt) = req.unpack();
if let Some(id) = id_opt {
self.send_ca_command(CertAuthCommandDetails::child_update_id(
@@ -732,13 +763,19 @@ impl CaManager {
}
if let Some(suspend) = suspend_opt {
if suspend {
self.send_ca_command(CertAuthCommandDetails::child_suspend_inactive(ca, child, actor))
self.send_ca_command(CertAuthCommandDetails::child_suspend_inactive(ca, child.clone(), actor))
.await?;
} else {
self.send_ca_command(CertAuthCommandDetails::child_unsuspend(ca, child, actor))
self.send_ca_command(CertAuthCommandDetails::child_unsuspend(ca, child.clone(), actor))
.await?;
}
}
if let Some(mapping) = resource_class_name_mapping_opt {
self.send_ca_command(CertAuthCommandDetails::child_update_resource_class_name_mapping(
ca, child, mapping, actor,
))
.await?;
}
Ok(())
}
@@ -895,20 +932,21 @@ impl CaManager {
async fn issue(
&self,
ca_handle: &CaHandle,
child: ChildHandle,
child_handle: ChildHandle,
issue_req: IssuanceRequest,
actor: &Actor,
) -> KrillResult<provisioning::Message> {
if ca_handle.as_str() == TA_NAME {
let request = ta::ProvisioningRequest::Issuance(issue_req);
self.ta_slow_rfc6492_request(ca_handle, child, request, actor).await
self.ta_slow_rfc6492_request(ca_handle, child_handle, request, actor)
.await
} else {
let class_name = issue_req.class_name();
let child_rcn = issue_req.class_name();
let pub_key = issue_req.csr().public_key();
let cmd = CertAuthCommandDetails::child_certify(
ca_handle,
child.clone(),
child_handle.clone(),
issue_req.clone(),
self.config.clone(),
self.signer.clone(),
@@ -918,11 +956,14 @@ impl CaManager {
let ca = self.send_ca_command(cmd).await?;
// The updated CA will now include the newly issued certificate.
let response = ca.issuance_response(&child, class_name, pub_key, &self.config.issuance_timing)?;
let child = ca.get_child(&child_handle)?;
let my_rcn = child.parent_name_for_rcn(child_rcn);
let response = ca.issuance_response(&child_handle, &my_rcn, pub_key, &self.config.issuance_timing)?;
Ok(provisioning::Message::issue_response(
ca_handle.convert(),
child.into_converted(),
child_handle.into_converted(),
response,
))
}
+22
View File
@@ -1440,6 +1440,26 @@ async fn api_ca_child_show(req: Request, ca: CaHandle, child: ChildHandle) -> Ro
)
}
async fn api_ca_child_export(req: Request, ca: CaHandle, child: ChildHandle) -> RoutingResult {
aa!(
req,
Permission::CA_READ,
Handle::from(&ca),
render_json_res(req.state().api_ca_child_export(&ca, &child).await)
)
}
async fn api_ca_child_import(req: Request, ca: CaHandle) -> RoutingResult {
aa!(req, Permission::CA_ADMIN, Handle::from(&ca), {
let actor = req.actor();
let server = req.state().clone();
match req.json().await {
Ok(import_child) => render_empty_res(server.api_ca_child_import(&ca, import_child, &actor).await),
Err(e) => render_error(e),
}
})
}
async fn api_ca_stats_child_connections(req: Request, ca: CaHandle) -> RoutingResult {
aa!(
req,
@@ -1648,6 +1668,8 @@ async fn api_ca_children(req: Request, path: &mut RequestPath, ca: CaHandle) ->
},
Some("contact") | Some("parent_response.json") => api_ca_parent_res_json(req, ca, child).await,
Some("parent_response.xml") => api_ca_parent_res_xml(req, ca, child).await,
Some("export") => api_ca_child_export(req, ca, child).await,
Some("import") => api_ca_child_import(req, ca).await,
_ => render_unknown_method(),
},
None => match *req.method() {
+14 -3
View File
@@ -19,7 +19,9 @@ use crate::{
commons::{
actor::{Actor, ActorDef},
api::{
self, AddChildRequest, AllCertAuthIssues, AspaCustomer, AspaDefinitionList, AspaDefinitionUpdates,
self,
import::{ExportChild, ImportChild},
AddChildRequest, AllCertAuthIssues, AspaCustomer, AspaDefinitionList, AspaDefinitionUpdates,
AspaProvidersUpdate, BgpSecCsrInfoList, BgpSecDefinitionUpdates, CaCommandDetails, CaRepoDetails,
CertAuthInfo, CertAuthInit, CertAuthIssues, CertAuthList, CertAuthStats, ChildCaInfo,
ChildrenConnectionStats, CommandHistory, CommandHistoryCriteria, ConfiguredRoa, IdCertInfo,
@@ -482,8 +484,17 @@ impl KrillServer {
/// Show details for a child under the CA.
pub async fn ca_child_show(&self, ca: &CaHandle, child: &ChildHandle) -> KrillResult<ChildCaInfo> {
let child = self.ca_manager.ca_show_child(ca, child).await?;
Ok(child)
self.ca_manager.ca_show_child(ca, child).await
}
/// Export a child under the CA.
pub async fn api_ca_child_export(&self, ca: &CaHandle, child: &ChildHandle) -> KrillResult<ExportChild> {
self.ca_manager.ca_child_export(ca, child).await
}
/// Import a child under the CA.
pub async fn api_ca_child_import(&self, ca: &CaHandle, child: ImportChild, actor: &Actor) -> KrillResult<()> {
self.ca_manager.ca_child_import(ca, child, actor).await
}
/// Show children stats under the CA.
+1 -1
View File
@@ -366,7 +366,7 @@ impl TaskQueue {
now(),
),
CertAuthEvent::ParentAdded { parent, .. } => {
CertAuthEvent::ParentAdded { parent, .. } | CertAuthEvent::ParentUpdated { parent, .. } => {
if ca.repository_contact().is_ok() {
debug!("Parent {} added to CA {}, scheduling sync", parent, ca_handle);
self.schedule(
+1 -1
View File
@@ -434,7 +434,7 @@ impl eventsourcing::Aggregate for TrustAnchorProxy {
ProvisioningResponse::Issuance(_) => {
child_details
.used_keys
.insert(key_id, UsedKeyState::Current("default".into()));
.insert(key_id, UsedKeyState::InUse("default".into()));
}
ProvisioningResponse::Revocation(_) => {
child_details.used_keys.insert(key_id, UsedKeyState::Revoked);
+166
View File
@@ -0,0 +1,166 @@
//! Test export and import of a delegated CA child from
//! a parent in one Krill instance into a parent in another
//! Krill instance.
use krill::{
cli::{
options::{CaCommand, Command},
report::ApiResponse,
},
commons::api::{
import::{ExportChild, ImportChild},
ParentCaReq, ResourceClassNameMapping, UpdateChildRequest,
},
test::*,
};
use rpki::{
ca::{
idexchange::{CaHandle, ParentResponse},
provisioning::ResourceClassName,
},
repository::resources::ResourceSet,
};
#[tokio::test]
async fn functional_delegated_ca_import() {
async fn start_testbed(port: u16) -> impl FnOnce() {
let (data_dir, cleanup) = tmp_dir();
let storage_uri = mem_storage();
let mut config = test_config(&storage_uri, Some(&data_dir), true, true, false, false);
config.port = port;
start_krill(config).await;
cleanup
}
// Start a testbed
// Start a second testbed
// Add child in testbed one
// - add child
// - override default resource class name for child
// - add parent to child
// Export the child in testbed one
// Import the child in testbed two
// Update the child to use testbed two
// Update the child resources
let testbed = ca_handle("testbed");
let parent_1 = ca_handle("parent_1");
let parent_2 = ca_handle("parent_2");
let parent_res = ResourceSet::all();
let child = ca_handle("child");
let child_res = resources("AS65000", "10.0.0.0/16", "");
let child_res_2 = resources("AS65000-AS65010", "10.0.0.0/8", "2001:db8::/32");
let child_rcn = ResourceClassName::from("custom");
// Start a testbed
let clean = start_testbed(3000).await;
// Add parent_1
set_up_ca_with_repo(&parent_1).await;
set_up_ca_under_parent(&parent_1, &testbed, &parent_res, None).await;
// Add child under parent_1
set_up_ca_with_repo(&child).await;
set_up_ca_under_parent(&child, &parent_1, &child_res, Some(child_rcn)).await;
// Export the child
let exported_child = export_child(&parent_1, &child).await;
// Add parent_2
set_up_ca_with_repo(&parent_2).await;
set_up_ca_under_parent(&parent_2, &testbed, &parent_res, None).await;
// Import child into parent_2
import_child(&parent_2, exported_child).await;
// Add testbed in other server as parent to child
let response = parent_contact(&parent_2, &child).await;
let parent_ca_req = ParentCaReq::new(parent_2.convert(), response);
add_parent_to_ca(&child, parent_ca_req).await;
// Remove the child from the original parent
delete_child(&parent_1, &child).await;
// Update the resources for the child in the new
// parent, then synchronise it, and verify that
// the resources are received.
update_child_resources(&parent_2, &child, &child_res_2).await;
assert!(ca_contains_resources(&child, &child_res_2).await);
clean();
}
async fn export_child(parent: &CaHandle, child: &CaHandle) -> ExportChild {
match krill_admin(Command::CertAuth(CaCommand::ChildExport(
parent.clone(),
child.convert(),
)))
.await
{
ApiResponse::ChildExported(child) => child,
_ => {
panic!("Expected exported child")
}
}
}
async fn import_child(parent: &CaHandle, child: ImportChild) {
match krill_admin(Command::CertAuth(CaCommand::ChildImport(parent.clone(), child))).await {
ApiResponse::Empty => {}
_ => {
panic!("Expected exported child")
}
}
}
async fn set_up_ca_under_parent(
ca: &CaHandle,
parent: &CaHandle,
resources: &ResourceSet,
child_rcn: Option<ResourceClassName>,
) {
let child_request = request(ca).await;
let parent_ca_req = {
let response = add_child_rfc6492(parent.convert(), ca.convert(), child_request, resources.clone()).await;
ParentCaReq::new(parent.convert(), response)
};
if let Some(child_rcn) = child_rcn {
let mapping = ResourceClassNameMapping {
name_in_parent: rcn(0),
name_for_child: child_rcn,
};
krill_admin(krill::cli::options::Command::CertAuth(CaCommand::ChildUpdate(
parent.convert(),
ca.convert(),
UpdateChildRequest::resource_class_name_mapping(mapping),
)))
.await;
}
add_parent_to_ca(ca, parent_ca_req).await;
assert!(ca_contains_resources(ca, resources).await);
}
async fn parent_contact(ca: &CaHandle, child: &CaHandle) -> ParentResponse {
match krill_admin(Command::CertAuth(CaCommand::ParentResponse(
ca.clone(),
child.convert(),
)))
.await
{
ApiResponse::Rfc8183ParentResponse(response) => response,
_ => panic!("Expected RFC 8183 Parent Response"),
}
}
async fn update_child_resources(ca: &CaHandle, child: &CaHandle, resources: &ResourceSet) {
let child_handle = child.convert();
let req = UpdateChildRequest::resources(resources.clone());
match krill_admin(Command::CertAuth(CaCommand::ChildUpdate(ca.clone(), child_handle, req))).await {
ApiResponse::Empty => {}
_ => panic!("Expected empty ok response"),
}
}