mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-09-27 20:04:52 +02:00
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:
+14
-1
@@ -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
@@ -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
@@ -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 {}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user