use std::{error, fmt, fmt::Display, io};
use hyper::StatusCode;
use rpki::{
ca::{
idexchange::{
CaHandle, ChildHandle, MyHandle, ParentHandle, PublisherHandle
},
provisioning,
provisioning::ResourceClassName,
publication,
},
crypto::KeyIdentifier,
repository::error::ValidationError,
uri,
};
use serde::{Deserialize, Serialize};
use crate::{
commons::{
httpclient,
actor::Actor,
crypto::SignerError,
eventsourcing::{AggregateStoreError, WalStoreError},
queue,
storage,
storage::KeyValueError,
},
daemon::http::tls_keys,
daemon::http::auth::Permission,
server::pubd::PublicationDeltaError,
upgrades::UpgradeError,
};
use crate::api::status::ErrorResponse;
use crate::api::aspa::CustomerAsn;
use crate::api::bgpsec::{BgpSecAsnKey, BgpSecDefinition};
use crate::api::roa::{RoaConfiguration, RoaPayload, RoaPayloadJsonMapKey};
use crate::api::ta::{Nonce as TaNonce};
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct RoaDeltaError {
duplicates: Vec<RoaConfiguration>,
notheld: Vec<RoaConfiguration>,
unknowns: Vec<RoaPayload>,
invalid_length: Vec<RoaConfiguration>,
}
impl RoaDeltaError {
pub fn add_duplicate(&mut self, addition: RoaConfiguration) {
self.duplicates.push(addition);
}
pub fn add_notheld(&mut self, addition: RoaConfiguration) {
self.notheld.push(addition);
}
pub fn add_unknown(&mut self, removal: RoaPayload) {
self.unknowns.push(removal);
}
pub fn add_invalid_length(&mut self, invalid: RoaConfiguration) {
self.invalid_length.push(invalid);
}
pub fn is_empty(&self) -> bool {
self.duplicates.is_empty()
&& self.notheld.is_empty()
&& self.unknowns.is_empty()
&& self.invalid_length.is_empty()
}
}
impl fmt::Display for RoaDeltaError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.duplicates.is_empty() {
writeln!(f, "Cannot add the following duplicate ROAs:")?;
for dup in self.duplicates.iter() {
writeln!(f, " {dup}")?;
}
}
if !self.notheld.is_empty() {
writeln!(
f,
"Cannot add the following ROAs with prefixes not on any of your certificates:"
)?;
for not in self.notheld.iter() {
writeln!(f, " {not}")?;
}
}
if !self.unknowns.is_empty() {
writeln!(f, "Cannot remove the following unknown ROAs:")?;
for unk in self.unknowns.iter() {
writeln!(f, " {unk}")?;
}
}
if !self.invalid_length.is_empty() {
writeln!(
f,
"The following ROAs have a max length which is invalid for the prefix:"
)?;
for unk in self.invalid_length.iter() {
writeln!(f, " {unk}")?;
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub enum ApiAuthError {
ApiInvalidCredentials(String),
ApiLoginError(String),
ApiAuthPermanentError(String),
ApiAuthTransientError(String),
ApiAuthSessionExpired(String),
ApiInsufficientRights(String),
}
impl ApiAuthError {
pub fn insufficient_rights(
actor: &Actor, perm: Permission, resource: Option<&MyHandle>
) -> Self {
Self::ApiInsufficientRights(
match resource {
Some(res) => {
format!(
"User '{actor}' does not have permission '{perm}' \
on resource '{res}'",
)
},
None => {
format!(
"User '{actor}' does not have permission '{perm}'",
)
}
}
)
}
}
impl Display for ApiAuthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ApiAuthError::ApiInvalidCredentials(err)
| ApiAuthError::ApiLoginError(err)
| ApiAuthError::ApiAuthPermanentError(err)
| ApiAuthError::ApiAuthTransientError(err)
| ApiAuthError::ApiAuthSessionExpired(err)
| ApiAuthError::ApiInsufficientRights(err) => {
write!(f, "{}", &err)
}
}
}
}
impl From<Error> for ApiAuthError {
fn from(e: Error) -> Self {
match e {
Error::ApiAuthPermanentError(e) => {
ApiAuthError::ApiAuthPermanentError(e)
}
Error::ApiLoginError(e) => ApiAuthError::ApiLoginError(e),
Error::ApiInsufficientRights(e) => {
ApiAuthError::ApiInsufficientRights(e)
}
Error::ApiAuthTransientError(e) => {
ApiAuthError::ApiAuthTransientError(e)
}
Error::ApiAuthSessionExpired(e) => {
ApiAuthError::ApiAuthSessionExpired(e)
}
Error::ApiInvalidCredentials(e) => {
ApiAuthError::ApiInvalidCredentials(e)
}
_ => ApiAuthError::ApiAuthPermanentError(e.to_string()),
}
}
}
#[derive(Debug)]
pub struct FatalError(pub Error);
impl fmt::Display for FatalError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl error::Error for FatalError { }
pub type KrillError = Error;
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Error {
IoError(KrillIoError),
KeyValueError(KeyValueError),
QueueError(queue::Error),
AggregateStoreError(AggregateStoreError),
WalStoreError(WalStoreError),
SignerError(String),
HttpsSetup(String),
HttpClientError(httpclient::Error),
ConfigError(String),
UpgradeError(UpgradeError),
NotImplemented,
UnexpectedBody,
JsonError(serde_json::Error),
InvalidUtf8Input,
ApiUnknownMethod,
ApiUnknownResource,
ApiInvalidHandle,
ApiInvalidSeconds,
PostTooBig,
PostCannotRead,
ApiInvalidCredentials(String),
ApiLoginError(String),
ApiAuthPermanentError(String),
ApiAuthTransientError(String),
ApiAuthSessionExpired(String),
ApiInsufficientRights(String),
RepoNotSet,
PublisherUnknown(PublisherHandle),
PublisherUriOutsideBase(String, String),
PublisherBaseUriNoSlash(String),
PublisherDuplicate(PublisherHandle),
RepositoryServerNotInitialized,
RepositoryServerHasPublishers,
RepositoryServerAlreadyInitialized,
Rfc8181Validation(ValidationError),
Rfc8181Decode(String),
Rfc8181(publication::Error),
Rfc8181Delta(PublicationDeltaError),
PublishingObjects(String),
CaDuplicate(CaHandle),
CaUnknown(CaHandle),
CaRepoInUse(CaHandle),
CaRepoIssue(CaHandle, String),
CaRepoResponseInvalid(CaHandle, String),
CaRepoResponseWrongXml(CaHandle),
CaParentDuplicateName(CaHandle, ParentHandle),
CaParentDuplicateInfo(CaHandle, ParentHandle),
CaParentUnknown(CaHandle, ParentHandle),
CaParentIssue(CaHandle, ParentHandle, String),
CaParentResponseInvalid(CaHandle, String),
CaParentResponseWrongXml(CaHandle),
CaParentAddNotResponsive(CaHandle, ParentHandle),
CaParentSyncError(CaHandle, ParentHandle, ResourceClassName, String),
Rfc8183(String),
Rfc6492(provisioning::Error),
Rfc6492NotPerformed(provisioning::NotPerformedResponse),
Rfc6492InvalidCsrSent(String),
CaChildImportHandleMismatch { path: ChildHandle, body: ChildHandle },
CaChildDuplicate(CaHandle, ChildHandle),
CaChildUnknown(CaHandle, ChildHandle),
CaChildMustHaveResources(CaHandle, ChildHandle),
CaChildExtraResources(CaHandle, ChildHandle),
CaChildUnauthorized(CaHandle, ChildHandle),
CaAuthorizationUnknown(CaHandle, RoaPayloadJsonMapKey),
CaAuthorizationDuplicate(CaHandle, RoaPayloadJsonMapKey),
CaAuthorizationInvalidMaxLength(CaHandle, RoaPayloadJsonMapKey),
CaAuthorizationNotEntitled(CaHandle, RoaPayloadJsonMapKey),
RoaDeltaError(CaHandle, RoaDeltaError),
AspaCustomerAsNotEntitled(CaHandle, CustomerAsn),
AspaCustomerAlreadyPresent(CaHandle, CustomerAsn),
AspaCustomerUnknown(CaHandle, CustomerAsn),
AspaCustomerAsProvider(CaHandle, CustomerAsn),
AspaProvidersDuplicates(CaHandle, CustomerAsn),
AspaProvidersEmpty(CaHandle, CustomerAsn),
BgpSecDefinitionUnknown(CaHandle, BgpSecAsnKey),
BgpSecDefinitionInvalidlySigned(CaHandle, BgpSecDefinition, String),
BgpSecDefinitionNotEntitled(CaHandle, BgpSecAsnKey),
KeyUseAttemptReuse,
KeyUseNoNewKey,
KeyUseNoCurrentKey,
KeyUseNoOldKey,
KeyUseNoIssuedCert,
KeyUseNoMatch(KeyIdentifier),
KeyRollInProgress,
KeyRollActivatePendingRequests,
ResourceClassUnknown(ResourceClassName),
ResourceSetError(String),
MissingResources,
TaNotAllowed,
TaNameReserved,
TaNotInitialized,
TaAlreadyInitialized,
TaProxyAlreadyHasRepository,
TaProxyHasNoRepository,
TaProxyHasNoSigner,
TaProxyAlreadyHasSigner,
TaProxyHasDifferentSigner,
TaProxyHasNoRequest,
TaProxyHasRequest,
TaProxyRequestNonceMismatch(TaNonce, TaNonce),
RtaResourcesNotHeld,
Custom(String),
Multiple(Vec<Error>),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::IoError(e) => write!(f, "I/O error: {e}"),
Error::KeyValueError(e) => write!(f, "Key/Value error: {e}"),
Error::QueueError(e) => write!(f, "Queue error: {e}"),
Error::AggregateStoreError(e) => write!(f, "Persistence (aggregate store) error: {e}"),
Error::WalStoreError(e) => write!(f, "Persistence (wal store) error: {e}"),
Error::SignerError(e) => write!(f, "Signing issue: {e}"),
Error::HttpsSetup(e) => write!(f, "Cannot set up HTTPS: {e}"),
Error::HttpClientError(e) => write!(f, "HTTP client error: {e}"),
Error::ConfigError(e) => write!(f, "Configuration error: {e}"),
Error::UpgradeError(e) => write!(f, "Could not upgrade Krill: {e}"),
Error::NotImplemented => write!(f, "Not yet implemented"),
Error::UnexpectedBody => write!(f, "Unexpected body in request"),
Error::JsonError(e) => write!(f,"Invalid JSON: {e}"),
Error::InvalidUtf8Input => write!(f, "Submitted bytes are invalid UTF8"),
Error::ApiUnknownMethod => write!(f,"Unknown API method"),
Error::ApiUnknownResource => write!(f, "Unknown resource"),
Error::ApiInvalidHandle => write!(f, "Invalid path argument for handle"),
Error::ApiInvalidSeconds => write!(f, "Invalid path argument for seconds"),
Error::PostTooBig => write!(f, "POST body exceeds configured limit"),
Error::PostCannotRead => write!(f, "POST body cannot be read"),
Error::ApiInvalidCredentials(e) => write!(f, "Invalid credentials: {e}"),
Error::ApiLoginError(e) => write!(f, "Login error: {e}"),
Error::ApiAuthPermanentError(e) => write!(f, "Authentication error: {e}"),
Error::ApiAuthTransientError(e) => write!(f, "Transient authentication error: {e}"),
Error::ApiAuthSessionExpired(e) => write!(f, "Session expired: {e}"),
Error::ApiInsufficientRights(e) => write!(f, "Insufficient rights: {e}"),
Error::RepoNotSet => write!(f, "No repository configured for CA"),
Error::PublisherUnknown(pbl) => write!(f, "Unknown publisher '{pbl}'"),
Error::PublisherUriOutsideBase(uri, jail) => write!(f, "Publishing uri '{uri}' outside repository uri '{jail}'"),
Error::PublisherBaseUriNoSlash(uri) => write!(f, "Publisher uri '{uri}' must have a trailing slash"),
Error::PublisherDuplicate(pbl) => write!(f, "Duplicate publisher '{pbl}'"),
Error::RepositoryServerNotInitialized => write!(f, "Publication Server not initialized, see 'krillc pubserver server init --help'"),
Error::RepositoryServerHasPublishers => write!(f, "Publication Server cannot be removed, still has publishers"),
Error::RepositoryServerAlreadyInitialized => write!(f, "Publication Server already initialized"),
Error::Rfc8181Validation(req) => write!(f, "Issue with RFC8181 request: {req}"),
Error::Rfc8181Decode(req) => write!(f, "Issue with decoding RFC8181 request: {req}"),
Error::Rfc8181(e) => e.fmt(f),
Error::Rfc8181Delta(e) => e.fmt(f),
Error::PublishingObjects(msg) => write!(f, "Issue generating repository objects: '{msg}'"),
Error::CaDuplicate(ca) => write!(f, "CA '{ca}' was already initialized"),
Error::CaUnknown(ca) => write!(f, "CA '{ca}' is unknown"),
Error::CaRepoInUse(ca) => write!(f, "CA '{ca}' already uses this repository"),
Error::CaRepoIssue(ca, e) => write!(f, "CA '{ca}' cannot get response from repository '{e}'. Is the 'service_uri' in the XML reachable? Note that when upgrading Krill you should re-use existing configuration and data. For a fresh \
re-install of Krill you will need to send XML to all other parties again: parent(s), children, and repository"),
Error::CaRepoResponseInvalid(ca, e) => write!(f, "CA '{ca}' got invalid repository response: {e}"),
Error::CaRepoResponseWrongXml(ca) => write!(f, "CA '{ca}' got parent instead of repository response"),
Error::CaParentDuplicateName(ca, parent) => write!(f, "CA '{ca}' already has a parent named '{parent}'"),
Error::CaParentDuplicateInfo(ca, parent) => write!(f, "CA '{ca}' already has a parent named '{parent}' for this XML"),
Error::CaParentUnknown(ca, parent) => write!(f, "CA '{ca}' does not have a parent named '{parent}'"),
Error::CaParentIssue(ca, parent, e) => write!(f, "CA '{ca}' got error from parent '{parent}': {e}"),
Error::CaParentResponseInvalid(ca, e) => write!(f, "CA '{ca}' got invalid parent response: {e}"),
Error::CaParentResponseWrongXml(ca) => write!(f, "CA '{ca}' got repository response when adding parent"),
Error::CaParentAddNotResponsive(ca, parent) => write!(f, "CA '{ca}' cannot get response from parent '{parent}'. Is the 'service_uri' in the XML reachable? Note that when upgrading Krill you should re-use existing configuration and data. For a fresh re-install of Krill you will need to send XML to all other parties again: parent(s), children, and repository"),
Error::CaParentSyncError(ca, parent, rcn, error_msg) => {
write!(
f,
"CA '{ca}' could not sync with parent '{parent}', for resource class '{rcn}', error: {error_msg}"
)
}
Error::Rfc8183(e) => write!(f, "RFC 8183 XML issue: {e}"),
Error::Rfc6492(e) => write!(f, "RFC 6492 Issue: {e}"),
Error::Rfc6492NotPerformed(not) => write!(f, "RFC 6492 Not Performed: {not}"),
Error::Rfc6492InvalidCsrSent(e) => write!(f, "Invalid CSR received: {e}"),
Error::CaChildImportHandleMismatch { path, body } => {
write!(f,
"mismatch between child handles: \
'{path}' in path, '{body}' in body"
)
},
Error::CaChildDuplicate(ca, child) => write!(f, "CA '{ca}' already has a child named '{child}'"),
Error::CaChildUnknown(ca, child) => write!(f, "CA '{ca}' does not have a child named '{child}'"),
Error::CaChildMustHaveResources(ca, child) => write!(f, "Child '{child}' for CA '{ca}' MUST have resources specified"),
Error::CaChildExtraResources(ca, child) => write!(f, "Child '{child}' cannot have resources not held by CA '{ca}'"),
Error::CaChildUnauthorized(ca, child) => write!(f, "CA '{ca}' does not know id certificate for child '{child}'"),
Error::CaAuthorizationUnknown(_ca, roa) => write!(f, "Cannot remove unknown ROA '{roa}'"),
Error::CaAuthorizationDuplicate(_ca, roa) => write!(f, "ROA '{roa}' already present"),
Error::CaAuthorizationInvalidMaxLength(_ca, roa) => write!(f, "Invalid max length in ROA: '{roa}'"),
Error::CaAuthorizationNotEntitled(_ca, roa) => write!(f, "Prefix in ROA '{roa}' not held by you"),
Error::RoaDeltaError(_ca, e) => write!(f, "ROA delta rejected:\n\n'{e}' "),
Error::AspaCustomerAsNotEntitled(_ca, asn) => write!(f, "Customer AS '{asn}' is not held by you"),
Error::AspaCustomerAlreadyPresent(_ca, asn) => write!(f, "ASPA already exists for customer AS '{asn}'"),
Error::AspaProvidersEmpty(_ca, asn) => write!(f, "ASPA for customer AS '{asn}' requires at least one provider"),
Error::AspaCustomerAsProvider(_ca, asn) => write!(f, "ASPA for customer AS '{asn}' cannot have that AS as provider"),
Error::AspaProvidersDuplicates(_ca, asn) => write!(f, "ASPA for customer AS '{asn}' cannot have duplicate providers"),
Error::AspaCustomerUnknown(_ca, asn) => write!(f, "No current ASPA exists for customer AS '{asn}'"),
Error::BgpSecDefinitionUnknown(_ca, key) => {
write!(f,
"Cannot remove BGPSec CSR for unknown combination of \
ASN '{}' and key '{}'",
key.asn, key.key
)
}
Error::BgpSecDefinitionInvalidlySigned(_ca, def, msg) => {
write!(f,
"Invalidly signed BGPsec CSR for ASN '{}' and key '{}': \
{}",
def.asn, def.csr.public_key().key_identifier(), msg
)
}
Error::BgpSecDefinitionNotEntitled(_ca, key) => {
write!(f, "AS '{}' is not held by you", key.asn)
}
Error::KeyUseAttemptReuse => write!(f, "Attempt at re-using keys"),
Error::KeyUseNoNewKey => write!(f, "No new key in resource class"),
Error::KeyUseNoCurrentKey => write!(f, "No current key in resource class"),
Error::KeyUseNoOldKey => write!(f, "No old key in resource class"),
Error::KeyUseNoIssuedCert => write!(f, "No issued cert matching pub key"),
Error::KeyUseNoMatch(ki) => write!(f, "No key found matching key identifier: '{ki}'"),
Error::KeyRollInProgress => write!(f, "Key roll in progress"),
Error::KeyRollActivatePendingRequests => write!(f, "Cannot activate key while there are still pending requests."),
Error::ResourceClassUnknown(rcn) => write!(f, "Unknown resource class: '{rcn}'"),
Error::ResourceSetError(e) => e.fmt(f),
Error::MissingResources => write!(f, "Requester is not entitled to all requested resources"),
Error::TaNotAllowed => write!(f, "Functionality not supported for Trust Anchor"),
Error::TaNameReserved => write!(f, "Name reserved for embedded Trust Anchor"),
Error::TaNotInitialized => write!(f, "TrustAnchor was not initialized"),
Error::TaAlreadyInitialized => write!(f, "TrustAnchor was already initialized"),
Error::TaProxyAlreadyHasRepository => write!(f, "Trust Anchor Proxy already has repository"),
Error::TaProxyHasNoRepository => write!(f, "Trust Anchor Proxy has no repository"),
Error::TaProxyHasNoSigner => write!(f, "Trust Anchor Proxy has no associated signer"),
Error::TaProxyAlreadyHasSigner => write!(f, "Trust Anchor Proxy already has associated signer"),
Error::TaProxyHasDifferentSigner => write!(f, "Trust Anchor Proxy already has a different associated signer"),
Error::TaProxyHasNoRequest => write!(f, "Trust Anchor Proxy has no signer request"),
Error::TaProxyHasRequest => write!(f, "Trust Anchor Proxy already has signer request"),
Error::TaProxyRequestNonceMismatch(rcvd, expected) => write!(f, "Trust Anchor Response nonce '{rcvd}' does not match open Request nonce '{expected}'"),
Error::RtaResourcesNotHeld => write!(f, "Your CA does not hold the requested resources"),
Error::Custom(s) => s.fmt(f),
Error::Multiple(errors) => {
let error_strings: Vec<String> = errors.iter().map(|e| e.to_string()).collect();
write!(f, "Multiple errors: {}", error_strings.join(", "))
}
}
}
}
impl From<KrillIoError> for Error {
fn from(e: KrillIoError) -> Self {
Error::IoError(e)
}
}
impl From<KeyValueError> for Error {
fn from(e: KeyValueError) -> Self {
Error::KeyValueError(e)
}
}
impl From<storage::Error> for Error {
fn from(e: storage::Error) -> Self {
Error::KeyValueError(KeyValueError::Inner(e))
}
}
impl From<AggregateStoreError> for Error {
fn from(e: AggregateStoreError) -> Self {
Error::AggregateStoreError(e)
}
}
impl From<queue::Error> for Error {
fn from(e: queue::Error) -> Self {
Error::QueueError(e)
}
}
impl From<WalStoreError> for Error {
fn from(e: WalStoreError) -> Self {
Error::WalStoreError(e)
}
}
impl From<SignerError> for Error {
fn from(e: SignerError) -> Self {
Error::SignerError(e.to_string())
}
}
impl From<provisioning::Error> for Error {
fn from(e: provisioning::Error) -> Self {
Error::Rfc6492(e)
}
}
impl From<publication::Error> for Error {
fn from(e: publication::Error) -> Self {
Error::Rfc8181(e)
}
}
impl From<tls_keys::Error> for Error {
fn from(e: tls_keys::Error) -> Self {
Error::HttpsSetup(e.to_string())
}
}
impl From<crate::commons::crypto::Error> for Error {
fn from(e: crate::commons::crypto::Error) -> Self {
Error::signer(e)
}
}
impl From<ApiAuthError> for Error {
fn from(e: ApiAuthError) -> Self {
match e {
ApiAuthError::ApiAuthPermanentError(e) => {
Error::ApiAuthPermanentError(e)
}
ApiAuthError::ApiLoginError(e) => Error::ApiLoginError(e),
ApiAuthError::ApiInsufficientRights(e) => {
Error::ApiInsufficientRights(e)
}
ApiAuthError::ApiAuthTransientError(e) => {
Error::ApiAuthTransientError(e)
}
ApiAuthError::ApiAuthSessionExpired(e) => {
Error::ApiAuthSessionExpired(e)
}
ApiAuthError::ApiInvalidCredentials(e) => {
Error::ApiInvalidCredentials(e)
}
}
}
}
impl From<PublicationDeltaError> for Error {
fn from(e: PublicationDeltaError) -> Self {
Error::Rfc8181Delta(e)
}
}
impl From<UpgradeError> for Error {
fn from(e: UpgradeError) -> Self {
Error::UpgradeError(e)
}
}
impl Error {
pub fn signer(e: impl Display) -> Self {
Error::SignerError(e.to_string())
}
pub fn invalid_csr(msg: impl fmt::Display) -> Self {
Error::Rfc6492InvalidCsrSent(msg.to_string())
}
pub fn publishing_outside_jail(
uri: &uri::Rsync,
jail: &uri::Rsync,
) -> Self {
Error::PublisherUriOutsideBase(uri.to_string(), jail.to_string())
}
pub fn publishing(msg: impl fmt::Display) -> Self {
Error::PublishingObjects(msg.to_string())
}
pub fn rfc8183(e: impl Display) -> Self {
Error::Rfc8183(e.to_string())
}
pub fn custom(msg: impl fmt::Display) -> Self {
Error::Custom(msg.to_string())
}
pub fn io_error_with_context(context: String, cause: io::Error) -> Self {
Error::IoError(KrillIoError::new(context, cause))
}
}
impl std::error::Error for Error {}
impl Error {
pub fn status(&self) -> StatusCode {
match self {
Error::IoError(_)
| Error::SignerError(_)
| Error::AggregateStoreError(_)
| Error::WalStoreError(_)
| Error::PublishingObjects(_) => {
StatusCode::INTERNAL_SERVER_ERROR
}
Error::PublisherUnknown(_)
| Error::CaUnknown(_)
| Error::CaChildUnknown(_, _)
| Error::CaParentUnknown(_, _)
| Error::ApiUnknownResource => StatusCode::NOT_FOUND,
Error::ApiInvalidCredentials(_)
| Error::ApiAuthPermanentError(_)
| Error::ApiAuthTransientError(_)
| Error::ApiAuthSessionExpired(_)
| Error::ApiLoginError(_) => StatusCode::UNAUTHORIZED,
Error::ApiInsufficientRights(_) => StatusCode::FORBIDDEN,
Error::NotImplemented => StatusCode::NOT_IMPLEMENTED,
_ => StatusCode::BAD_REQUEST,
}
}
pub fn to_error_response(&self) -> ErrorResponse {
match self {
Error::IoError(e) => {
ErrorResponse::new("sys-io", self).with_cause(e)
}
Error::KeyValueError(e) => {
ErrorResponse::new("sys-kv", self).with_cause(e)
}
Error::AggregateStoreError(e) => {
ErrorResponse::new("sys-store", self).with_cause(e)
}
Error::QueueError(e) => {
ErrorResponse::new("sys-store", self).with_cause(e)
}
Error::WalStoreError(e) => {
ErrorResponse::new("sys-wal-store", self).with_cause(e)
}
Error::SignerError(e) => {
ErrorResponse::new("sys-signer", self).with_cause(e)
}
Error::HttpsSetup(e) => {
ErrorResponse::new("sys-https", self).with_cause(e)
}
Error::HttpClientError(e) => {
ErrorResponse::new("sys-http-client", self).with_cause(e)
}
Error::ConfigError(e) => {
ErrorResponse::new("sys-config", self).with_cause(e)
}
Error::UpgradeError(e) => {
ErrorResponse::new("sys-upgrade", self).with_cause(e)
}
Error::NotImplemented => {
ErrorResponse::new("sys-not-implemented", self)
}
Error::UnexpectedBody => {
ErrorResponse::new("api-unexpected-body", self)
}
Error::JsonError(e) => {
ErrorResponse::new("api-json", self).with_cause(e)
}
Error::InvalidUtf8Input => {
ErrorResponse::new("api-invalid-utf8", self)
}
Error::ApiUnknownMethod => {
ErrorResponse::new("api-unknown-method", self)
}
Error::ApiUnknownResource => {
ErrorResponse::new("api-unknown-resource", self)
}
Error::ApiInvalidHandle => {
ErrorResponse::new("api-invalid-path-handle", self)
}
Error::ApiInvalidSeconds => {
ErrorResponse::new("api-invalid-path-seconds", self)
}
Error::PostTooBig => {
ErrorResponse::new("api-post-body-exceeds-limit", self)
}
Error::PostCannotRead => {
ErrorResponse::new("api-post-body-cannot-read", self)
}
Error::ApiInvalidCredentials(e) => {
ErrorResponse::new("api-invalid-credentials", self)
.with_cause(e)
}
Error::ApiLoginError(e) => {
ErrorResponse::new("api-login-error", self).with_cause(e)
}
Error::ApiAuthPermanentError(e) => {
ErrorResponse::new("api-auth-permanent-error", self)
.with_cause(e)
}
Error::ApiAuthTransientError(e) => {
ErrorResponse::new("api-auth-transient-error", self)
.with_cause(e)
}
Error::ApiAuthSessionExpired(e) => {
ErrorResponse::new("api-auth-session-expired", self)
.with_cause(e)
}
Error::ApiInsufficientRights(e) => {
ErrorResponse::new("api-insufficient-rights", self)
.with_cause(e)
}
Error::RepoNotSet => ErrorResponse::new("repo-not-set", self),
Error::PublisherUnknown(p) => {
ErrorResponse::new("pub-unknown", self).with_publisher(p)
}
Error::PublisherDuplicate(p) => {
ErrorResponse::new("pub-duplicate", self).with_publisher(p)
}
Error::PublisherUriOutsideBase(uri, base) => {
ErrorResponse::new("pub-outside-jail", self)
.with_uri(uri)
.with_base_uri(base)
}
Error::PublisherBaseUriNoSlash(uri) => {
ErrorResponse::new("pub-uri-no-slash", self).with_uri(uri)
}
Error::RepositoryServerNotInitialized => {
ErrorResponse::new("pub-repo-not-initialized", self)
}
Error::RepositoryServerHasPublishers => {
ErrorResponse::new("pub-repo-has-publishers", self)
}
Error::RepositoryServerAlreadyInitialized => {
ErrorResponse::new("pub-repo-initialized", self)
}
Error::Rfc8181Validation(e) => {
ErrorResponse::new("rfc8181-validation", self).with_cause(e)
}
Error::Rfc8181Decode(e) => {
ErrorResponse::new("rfc8181-decode", self).with_cause(e)
}
Error::Rfc8181(e) => {
ErrorResponse::new("rfc8181-protocol-message", self)
.with_cause(e)
}
Error::Rfc8181Delta(e) => {
ErrorResponse::new("rfc8181-delta", self).with_cause(e)
}
Error::PublishingObjects(msg) => ErrorResponse::new(
"publishing-generate-repository-objects",
self,
)
.with_cause(msg),
Error::CaDuplicate(ca) => {
ErrorResponse::new("ca-duplicate", self).with_ca(ca)
}
Error::CaUnknown(ca) => {
ErrorResponse::new("ca-unknown", self).with_ca(ca)
}
Error::CaRepoInUse(ca) => {
ErrorResponse::new("ca-repo-same", self).with_ca(ca)
}
Error::CaRepoIssue(ca, err) => {
ErrorResponse::new("ca-repo-issue", self)
.with_ca(ca)
.with_cause(err)
}
Error::CaRepoResponseInvalid(ca, err) => {
ErrorResponse::new("ca-repo-response-invalid-xml", self)
.with_ca(ca)
.with_cause(err)
}
Error::CaRepoResponseWrongXml(ca) => {
ErrorResponse::new("ca-repo-response-wrong-xml", self)
.with_ca(ca)
}
Error::CaParentDuplicateName(ca, parent) => {
ErrorResponse::new("ca-parent-duplicate", self)
.with_ca(ca)
.with_parent(parent)
}
Error::CaParentDuplicateInfo(ca, parent) => {
ErrorResponse::new("ca-parent-xml-duplicate", self)
.with_ca(ca)
.with_parent(parent)
}
Error::CaParentUnknown(ca, parent) => {
ErrorResponse::new("ca-parent-unknown", self)
.with_ca(ca)
.with_parent(parent)
}
Error::CaParentIssue(ca, parent, err) => {
ErrorResponse::new("ca-parent-issue", self)
.with_ca(ca)
.with_parent(parent)
.with_cause(err)
}
Error::CaParentResponseInvalid(ca, err) => {
ErrorResponse::new("ca-parent-response-invalid-xml", self)
.with_ca(ca)
.with_cause(err)
}
Error::CaParentResponseWrongXml(ca) => {
ErrorResponse::new("ca-parent-response-wrong-xml", self)
.with_ca(ca)
}
Error::CaParentAddNotResponsive(ca, parent) => {
ErrorResponse::new("ca-parent-add-unresponsive", self)
.with_ca(ca)
.with_parent(parent)
}
Error::CaParentSyncError(ca, parent, rcn, _errors) => {
ErrorResponse::new("ca-parent-sync", self)
.with_ca(ca)
.with_parent(parent)
.with_resource_class(rcn)
}
Error::Rfc8183(e) => {
ErrorResponse::new("rfc-8183-xml", self).with_cause(e)
}
Error::Rfc6492(e) => {
ErrorResponse::new("rfc6492-protocol", self).with_cause(e)
}
Error::Rfc6492NotPerformed(e) => {
ErrorResponse::new("rfc6492-not-performed-response", self)
.with_cause(e)
}
Error::Rfc6492InvalidCsrSent(e) => {
ErrorResponse::new("rfc6492-invalid-csr", self).with_cause(e)
}
Error::CaChildImportHandleMismatch { path, body } => {
ErrorResponse::new("ca-child-import-handle-mismatch", self)
.with_arg("path", path)
.with_arg("body", body)
}
Error::CaChildDuplicate(ca, child) => {
ErrorResponse::new("ca-child-duplicate", self)
.with_ca(ca)
.with_child(child)
}
Error::CaChildUnknown(ca, child) => {
ErrorResponse::new("ca-child-unknown", self)
.with_ca(ca)
.with_child(child)
}
Error::CaChildMustHaveResources(ca, child) => {
ErrorResponse::new("ca-child-resources-required", self)
.with_ca(ca)
.with_child(child)
}
Error::CaChildExtraResources(ca, child) => {
ErrorResponse::new("ca-child-resources-extra", self)
.with_ca(ca)
.with_child(child)
}
Error::CaChildUnauthorized(ca, child) => {
ErrorResponse::new("ca-child-unauthorized", self)
.with_ca(ca)
.with_child(child)
}
Error::CaAuthorizationUnknown(ca, auth) => {
ErrorResponse::new("ca-roa-unknown", self)
.with_ca(ca)
.with_auth(*auth)
}
Error::CaAuthorizationDuplicate(ca, auth) => {
ErrorResponse::new("ca-roa-duplicate", self)
.with_ca(ca)
.with_auth(*auth)
}
Error::CaAuthorizationInvalidMaxLength(ca, auth) => {
ErrorResponse::new("ca-roa-invalid-max-length", self)
.with_ca(ca)
.with_auth(*auth)
}
Error::CaAuthorizationNotEntitled(ca, auth) => {
ErrorResponse::new("ca-roa-not-entitled", self)
.with_ca(ca)
.with_auth(*auth)
}
Error::RoaDeltaError(ca, roa_delta_error) => {
ErrorResponse::new("ca-roa-delta-error", self)
.with_ca(ca)
.with_roa_delta_error(roa_delta_error)
}
Error::AspaCustomerAsNotEntitled(ca, asn) => {
ErrorResponse::new("ca-aspa-not-entitled", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::AspaCustomerAlreadyPresent(ca, asn) => {
ErrorResponse::new("ca-aspa-customer-as-duplicate", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::AspaProvidersEmpty(ca, asn) => {
ErrorResponse::new("ca-aspa-provider-as-empty", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::AspaCustomerAsProvider(ca, asn) => {
ErrorResponse::new("ca-aspa-customer-as-provider", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::AspaProvidersDuplicates(ca, asn) => {
ErrorResponse::new("ca-aspa-provider-duplicates", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::AspaCustomerUnknown(ca, asn) => {
ErrorResponse::new("ca-aspa-unknown-customer-as", self)
.with_ca(ca)
.with_asn(*asn)
}
Error::BgpSecDefinitionUnknown(ca, key) => {
ErrorResponse::new("ca-bgpsec-unknown", self)
.with_ca(ca)
.with_asn(key.asn)
.with_key_identifier(&key.key)
}
Error::BgpSecDefinitionInvalidlySigned(ca, def, msg) => {
ErrorResponse::new("ca-bgpsec-invalidly-signed", self)
.with_ca(ca)
.with_asn(def.asn)
.with_key_identifier(
&def.csr.public_key().key_identifier(),
)
.with_bgpsec_csr(&def.csr)
.with_cause(msg)
}
Error::BgpSecDefinitionNotEntitled(ca, key) => {
ErrorResponse::new("ca-bgpsec-not-entitled", self)
.with_ca(ca)
.with_asn(key.asn)
}
Error::KeyUseAttemptReuse => {
ErrorResponse::new("key-re-use", self)
}
Error::KeyUseNoNewKey => ErrorResponse::new("key-no-new", self),
Error::KeyUseNoCurrentKey => {
ErrorResponse::new("key-no-current", self)
}
Error::KeyUseNoOldKey => ErrorResponse::new("key-no-old", self),
Error::KeyUseNoIssuedCert => {
ErrorResponse::new("key-no-cert", self)
}
Error::KeyUseNoMatch(ki) => {
ErrorResponse::new("key-no-match", self)
.with_key_identifier(ki)
}
Error::KeyRollInProgress => {
ErrorResponse::new("key-roll-disallowed", self)
}
Error::KeyRollActivatePendingRequests => {
ErrorResponse::new("key-roll-pending-requests", self)
}
Error::ResourceClassUnknown(name) => {
ErrorResponse::new("rc-unknown", self)
.with_resource_class(name)
}
Error::ResourceSetError(e) => {
ErrorResponse::new("rc-resources", self).with_cause(e)
}
Error::MissingResources => {
ErrorResponse::new("rc-missing-resources", self)
}
Error::TaNotAllowed => ErrorResponse::new("ta-not-allowed", self),
Error::TaNameReserved => {
ErrorResponse::new("ta-name-reserved", self)
}
Error::TaNotInitialized => {
ErrorResponse::new("ta-not-initialized", self)
}
Error::TaAlreadyInitialized => {
ErrorResponse::new("ta-initialized", self)
}
Error::TaProxyAlreadyHasRepository => {
ErrorResponse::new("ta-has-repository", self)
}
Error::TaProxyHasNoRepository => {
ErrorResponse::new("ta-has-no-repository", self)
}
Error::TaProxyHasNoSigner => {
ErrorResponse::new("ta-has-no-signer", self)
}
Error::TaProxyAlreadyHasSigner => {
ErrorResponse::new("ta-has-signer", self)
}
Error::TaProxyHasDifferentSigner => {
ErrorResponse::new("ta-has-different-signer", self)
}
Error::TaProxyHasNoRequest => {
ErrorResponse::new("ta-has-no-signer-req", self)
}
Error::TaProxyHasRequest => {
ErrorResponse::new("ta-has-signer-req", self)
}
Error::TaProxyRequestNonceMismatch(_rcvd, _expected) => {
ErrorResponse::new("ta-proxy-response-nonce", self)
}
Error::RtaResourcesNotHeld => {
ErrorResponse::new("rta-resources-not-held", self)
}
Error::Custom(_msg) => ErrorResponse::new("general-error", self),
Error::Multiple(_errors) => {
ErrorResponse::new("multiple-errors", self)
}
}
}
pub fn to_rfc8181_error_code(&self) -> publication::ReportErrorCode {
match self {
Error::Rfc8181Validation(_) | Error::PublisherUnknown(_) => {
publication::ReportErrorCode::PermissionFailure
}
Error::Rfc8181(_) => publication::ReportErrorCode::XmlError,
Error::Rfc8181Delta(e) => match e {
PublicationDeltaError::UriOutsideJail(_, _) => {
publication::ReportErrorCode::PermissionFailure
}
PublicationDeltaError::NoObjectForHashAndOrUri(_) => {
publication::ReportErrorCode::NoObjectPresent
}
PublicationDeltaError::ObjectAlreadyPresent(_) => {
publication::ReportErrorCode::ObjectAlreadyPresent
}
},
_ => publication::ReportErrorCode::OtherError,
}
}
}
#[derive(Debug)]
pub struct KrillIoError {
context: String,
cause: io::Error,
}
impl KrillIoError {
pub fn new(context: String, cause: io::Error) -> Self {
KrillIoError { context, cause }
}
}
impl fmt::Display for KrillIoError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"context: {}, underlying io::Error: {}",
self.context, self.cause
)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::api::roa::RoaPayload;
use crate::commons::test::roa_configuration;
use crate::commons::test::roa_payload;
use crate::commons::test::test_id_certificate;
use super::*;
fn verify(expected_json: &str, e: Error) {
let actual = e.to_error_response();
let expected: ErrorResponse =
serde_json::from_str(expected_json).unwrap();
assert_eq!(actual, expected);
let serialized = serde_json::to_string(&actual).unwrap();
let des = serde_json::from_str(&serialized).unwrap();
assert_eq!(actual, des);
}
#[test]
fn error_response_json_regression() {
let ca = CaHandle::from_str("ca").unwrap();
let parent = ParentHandle::from_str("parent").unwrap();
let child = ChildHandle::from_str("child").unwrap();
let publisher = PublisherHandle::from_str("publisher").unwrap();
let auth = RoaPayloadJsonMapKey::from(
RoaPayload::from_str("192.168.0.0/16-24 => 64496").unwrap(),
);
let krill_io_err = KrillIoError::new(
"Trouble reading 'foo'".to_string(),
io::Error::other("can't read file"),
);
verify(
include_str!("../../test-resources/errors/sys-io.json"),
Error::IoError(krill_io_err),
);
verify(
include_str!("../../test-resources/errors/sys-store.json"),
Error::AggregateStoreError(AggregateStoreError::InitError(
ca.clone(),
)),
);
verify(
include_str!("../../test-resources/errors/sys-signer.json"),
Error::SignerError("signer issue".to_string()),
);
verify(
include_str!("../../test-resources/errors/sys-https.json"),
Error::HttpsSetup("can't find pem file".to_string()),
);
verify(
include_str!("../../test-resources/errors/sys-http-client.json"),
Error::HttpClientError(httpclient::Error::forbidden(
"https://example.com/",
)),
);
let invalid_rsync_json = "\"https://host/module/folder\"";
let json_err = serde_json::from_str::<uri::Rsync>(invalid_rsync_json)
.err()
.unwrap();
verify(
include_str!("../../test-resources/errors/api-json.json"),
Error::JsonError(json_err),
);
verify(
include_str!(
"../../test-resources/errors/api-unknown-method.json"
),
Error::ApiUnknownMethod,
);
verify(
include_str!(
"../../test-resources/errors/api-unknown-resource.json"
),
Error::ApiUnknownResource,
);
verify(
include_str!("../../test-resources/errors/repo-not-set.json"),
Error::RepoNotSet,
);
verify(
include_str!("../../test-resources/errors/pub-unknown.json"),
Error::PublisherUnknown(publisher.clone()),
);
verify(
include_str!("../../test-resources/errors/pub-duplicate.json"),
Error::PublisherDuplicate(publisher),
);
verify(
include_str!("../../test-resources/errors/pub-outside-jail.json"),
Error::PublisherUriOutsideBase(
"rsync://somehost/module/folder".to_string(),
"rsync://otherhost/module/folder".to_string(),
),
);
verify(
include_str!("../../test-resources/errors/pub-uri-no-slash.json"),
Error::PublisherBaseUriNoSlash(
"rsync://host/module/folder".to_string(),
),
);
verify(
include_str!("../../test-resources/errors/rfc8181-decode.json"),
Error::Rfc8181Decode("could not parse CMS".to_string()),
);
verify(
include_str!(
"../../test-resources/errors/rfc8181-protocol-message.json"
),
Error::Rfc8181(publication::Error::InvalidVersion),
);
verify(
include_str!("../../test-resources/errors/rfc8181-delta.json"),
Error::Rfc8181Delta(PublicationDeltaError::ObjectAlreadyPresent(
uri::Rsync::from_str("rsync://host/module/file.cer").unwrap(),
)),
);
verify(
include_str!("../../test-resources/errors/ca-duplicate.json"),
Error::CaDuplicate(ca.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-unknown.json"),
Error::CaUnknown(ca.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-repo-same.json"),
Error::CaRepoInUse(ca.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-repo-issue.json"),
Error::CaRepoIssue(ca.clone(), "cannot connect".to_string()),
);
verify(
include_str!("../../test-resources/errors/ca-repo-response-invalid-xml.json"),
Error::CaRepoResponseInvalid(ca.clone(), "expected some tag".to_string()),
);
verify(
include_str!(
"../../test-resources/errors/ca-repo-response-wrong-xml.json"
),
Error::CaRepoResponseWrongXml(ca.clone()),
);
verify(
include_str!(
"../../test-resources/errors/ca-parent-duplicate.json"
),
Error::CaParentDuplicateName(ca.clone(), parent.clone()),
);
verify(
include_str!(
"../../test-resources/errors/ca-parent-unknown.json"
),
Error::CaParentUnknown(ca.clone(), parent.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-parent-issue.json"),
Error::CaParentIssue(
ca.clone(),
parent,
"connection refused".to_string(),
),
);
verify(
include_str!("../../test-resources/errors/ca-parent-response-invalid-xml.json"),
Error::CaParentResponseInvalid(ca.clone(), "expected something".to_string()),
);
verify(
include_str!("../../test-resources/errors/ca-parent-response-wrong-xml.json"),
Error::CaParentResponseWrongXml(ca.clone()),
);
verify(
include_str!("../../test-resources/errors/rfc6492-protocol.json"),
Error::Rfc6492(provisioning::Error::InvalidVersion),
);
verify(
include_str!(
"../../test-resources/errors/rfc6492-invalid-csr.json"
),
Error::Rfc6492InvalidCsrSent("invalid signature".to_string()),
);
verify(
include_str!(
"../../test-resources/errors/ca-child-duplicate.json"
),
Error::CaChildDuplicate(ca.clone(), child.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-child-unknown.json"),
Error::CaChildUnknown(ca.clone(), child.clone()),
);
verify(
include_str!("../../test-resources/errors/ca-child-resources-required.json"),
Error::CaChildMustHaveResources(ca.clone(), child.clone()),
);
verify(
include_str!(
"../../test-resources/errors/ca-child-resources-extra.json"
),
Error::CaChildExtraResources(ca.clone(), child.clone()),
);
verify(
include_str!(
"../../test-resources/errors/ca-child-unauthorized.json"
),
Error::CaChildUnauthorized(ca.clone(), child),
);
verify(
include_str!("../../test-resources/errors/ca-roa-unknown.json"),
Error::CaAuthorizationUnknown(ca.clone(), auth),
);
verify(
include_str!("../../test-resources/errors/ca-roa-duplicate.json"),
Error::CaAuthorizationDuplicate(ca.clone(), auth),
);
verify(
include_str!(
"../../test-resources/errors/ca-roa-invalid-max-length.json"
),
Error::CaAuthorizationInvalidMaxLength(ca.clone(), auth),
);
verify(
include_str!(
"../../test-resources/errors/ca-roa-not-entitled.json"
),
Error::CaAuthorizationNotEntitled(ca, auth),
);
verify(
include_str!("../../test-resources/errors/key-re-use.json"),
Error::KeyUseAttemptReuse,
);
verify(
include_str!("../../test-resources/errors/key-no-new.json"),
Error::KeyUseNoNewKey,
);
verify(
include_str!("../../test-resources/errors/key-no-current.json"),
Error::KeyUseNoCurrentKey,
);
verify(
include_str!("../../test-resources/errors/key-no-old.json"),
Error::KeyUseNoOldKey,
);
verify(
include_str!("../../test-resources/errors/key-no-cert.json"),
Error::KeyUseNoIssuedCert,
);
let ki = test_id_certificate()
.subject_public_key_info()
.key_identifier();
verify(
include_str!("../../test-resources/errors/key-no-match.json"),
Error::KeyUseNoMatch(ki),
);
verify(
include_str!("../../test-resources/errors/rc-unknown.json"),
Error::ResourceClassUnknown(ResourceClassName::from("RC0")),
);
verify(
include_str!(
"../../test-resources/errors/rc-missing-resources.json"
),
Error::MissingResources,
);
verify(
include_str!("../../test-resources/errors/ta-not-allowed.json"),
Error::TaNotAllowed,
);
verify(
include_str!("../../test-resources/errors/ta-name-reserved.json"),
Error::TaNameReserved,
);
verify(
include_str!("../../test-resources/errors/ta-initialized.json"),
Error::TaAlreadyInitialized,
);
verify(
include_str!("../../test-resources/errors/general-error.json"),
Error::custom("some unlikely corner case"),
);
}
#[test]
fn roa_delta_json() {
let mut error = RoaDeltaError::default();
let duplicate = roa_configuration("10.0.0.0/20-24 => 1");
let not_held = roa_configuration("10.128.0.0/9 => 1");
let invalid_length = roa_configuration("10.0.1.0/25 => 1");
let unknown = roa_payload("192.168.0.0/16 => 1");
error.add_duplicate(duplicate);
error.add_notheld(not_held);
error.add_invalid_length(invalid_length);
error.add_unknown(unknown);
let ca = CaHandle::from_str("ca").unwrap();
let error = Error::RoaDeltaError(ca, error);
verify(
include_str!(
"../../test-resources/errors/ca-roa-delta-error.json"
),
error,
);
}
}