Redesign the HTTP service. (#1264)

Initially, this PR was intended to more clearly separate code between Krill
“business logic” – now bundled in a KrillManager –, and the HTTP server code
that serves the API. The former now lives in the server module, the latter
in the daemon module together with all the code to spin up a Krill daemon
driving the HTTP server.

However, along the way it turned into a complete redesign of how the HTTP
server code works. Request handling has been split into three stages that
forces implementers to check for permissions (or actively choose to not
check), and read the body (or check that there isn’t one). Dispatching of
the request has been restructured which should make it easier to follow what
goes on where.

This PR increases the minimum Rust version to 1.81.
This commit is contained in:
Martin Hoffmann
2025-04-14 11:17:08 +02:00
committed by GitHub
parent 4f5c340eb6
commit 35cd5d0bb2
87 changed files with 4211 additions and 3471 deletions
+8 -5
View File
@@ -1,5 +1,3 @@
As part of this PR:
* command details history output has changed. Check and document.
* Krill internally always stores ROA payload with an explicit max length.
Enforce this through a special type. (Make sure to be lenient when
@@ -8,9 +6,6 @@ As part of this PR:
* `BgpAnalysisEntry` contains a cloned `ConfiguredRoa`. Maybe it can
contain a ref or a cow? Also, this should probably be switched into an
enum to avoid `configured_roa? and `announcement` to panic.
Follow-up:
* Fix `impl Hash for crate::commons::api::admin::RepositoryContact`.
(This will require a few changes in `crate::deamon::ca`.)
* `commons::api::ca::ParentStatuses::sync_candidates` can re-use the
@@ -31,8 +26,16 @@ Follow-up:
* Use Cows in API structs to avoid cloning on the server side. This will
also allow removing quite a few temporary vecs and replace them with
iterators.
* /metrics and /stats are completely open. That sounds a bit dangerous.
* Change HTTP responses so we can stream a file, then change the /rrdp
endpoint to stream the file.
Notes
* I think we should apply API calls that create multiple commands
atomically.
Tests
* Check the cache headers on RRDP responses.
+175
View File
@@ -0,0 +1,175 @@
//! Build script.
//!
//! This script collects the assets for serving the Krill UI and creates
//! a module for them in `$OUT_DIR/ui_assets.rs`.
use std::{env, fmt, fs, io, process};
use std::path::{PathBuf, Path};
const UI_DIR: &str = "ui";
const INDEX_PATH: &str = "ui/index.html";
const ASSETS_DIR: &str = "ui/assets";
const RS_FILE: &str = "ui_assets.rs";
const TYPES: &[(&str, &str)] = &[
("css", "text/css"),
("html", "text/html"),
("ico", "image/x-icon"),
("js", "text/javascript"),
("svg", "image/svg+xml"),
("woff", "font/woff"),
("woff2", "font/woff2"),
];
struct Asset {
path: PathBuf,
media_type: &'static str,
content: Vec<u8>,
}
impl Asset {
fn load(path: PathBuf, asset: bool) -> Result<Self, String> {
let path_ext = match path.extension().and_then(|s| s.to_str()) {
Some(ext) => ext,
None => {
return Err(format!(
"Asset without extension: '{}'", path.display()
))
}
};
let media_type = match TYPES.iter().find_map(|(ext, media_type)| {
(path_ext == *ext).then_some(*media_type)
}) {
Some(media) => media,
None => {
return Err(format!(
"Asset with unknown extension '{}'", path_ext
))
}
};
Ok(Self {
path: if asset {
path.strip_prefix(ASSETS_DIR).map_err(|_| {
format!("Asset path {} not under {}",
path.display(), ASSETS_DIR
)
})?.into()
}
else {
INDEX_PATH.into()
},
media_type,
content: fs::read(&path).map_err(|err| {
format!(
"Failed to read UI asset file {}: {}.",
path.display(), err
)
})?
})
}
}
impl fmt::Display for Asset {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"
Asset {{
path: r#\"{}\"#,
media_type: \"{}\",
content: &{:?},
}}
",
self.path.display(),
self.media_type,
self.content.as_slice(),
)
}
}
#[derive(Default)]
struct Assets(Vec<Asset>);
impl Assets {
fn load_dir(&mut self, path: PathBuf) -> Result<(), String> {
let dir = fs::read_dir(&path).map_err(|err| {
format!("Failed to open directory {}: {}", path.display(), err)
})?;
for entry in dir {
let entry = entry.map_err(|err| {
format!("Failed to read directory {}: {}", path.display(), err)
})?;
let path = entry.path();
if path.is_dir() {
self.load_dir(path)?;
}
else {
self.0.push(Asset::load(path, true)?)
}
}
Ok(())
}
}
fn write_mod(
index: Asset, assets: Assets, dest: &mut impl io::Write
) -> Result<(), io::Error> {
write!(dest,
r#"
pub struct Asset {{
pub path: &'static str,
pub media_type: &'static str,
pub content: &'static [u8],
}}
pub static INDEX: Asset = {index};
pub static ASSETS: &[Asset] = &[
"#
)?;
for item in assets.0 {
write!(dest, "{},", item)?;
}
writeln!(dest, "];")
}
fn main() {
let out_dir = env::var_os("OUT_DIR").unwrap_or_default();
let target_path = Path::new(&out_dir).join(RS_FILE);
let mut target = match fs::File::create(&target_path) {
Ok(target) => io::BufWriter::new(target),
Err(err) => {
eprintln!("Failed to open assets module file {}: {}",
target_path.display(), err
);
process::exit(1);
}
};
let index = match Asset::load(INDEX_PATH.into(), false) {
Ok(index) => index,
Err(err) => {
eprintln!("{}", err);
process::exit(1);
}
};
let mut assets = Assets::default();
if let Err(err) = assets.load_dir(ASSETS_DIR.into()) {
eprintln!("{}", err);
process::exit(1);
}
if let Err(err) = write_mod(index, assets, &mut target) {
eprintln!("Failed to write to assets module file {}: {}",
target_path.display(), err
);
process::exit(1)
}
println!("cargo:rerun-if-changed={}", UI_DIR);
}
+1 -1
View File
@@ -72,7 +72,7 @@ pub struct PublisherSummary {
}
impl PublisherSummary {
fn from_handle(handle: PublisherHandle) -> Self {
pub fn from_handle(handle: PublisherHandle) -> Self {
PublisherSummary { handle }
}
}
+10 -7
View File
@@ -20,20 +20,23 @@ pub struct RepoStats {
}
impl RepoStats {
pub fn stale_publishers(&self, seconds: i64) -> Vec<PublisherHandle> {
let mut res = vec![];
for (publisher, stats) in self.publishers.iter() {
pub fn stale_publishers(
self, seconds: i64
) -> impl Iterator<Item = PublisherHandle> {
self.publishers.into_iter().filter_map(move |(publisher, stats)| {
if let Some(update_time) = stats.last_update() {
if Time::now().timestamp() - update_time.timestamp()
>= seconds
{
res.push(publisher.clone())
Some(publisher)
}
else {
None
}
} else {
res.push(publisher.clone())
Some(publisher)
}
}
res
})
}
}
+1 -1
View File
@@ -71,7 +71,7 @@ impl ErrorResponse {
}
}
fn with_arg(mut self, key: &str, value: impl fmt::Display) -> Self {
pub fn with_arg(mut self, key: &str, value: impl fmt::Display) -> Self {
self.args.insert(key.to_string(), value.to_string());
self
}
+3 -32
View File
@@ -274,39 +274,10 @@ impl fmt::Display for TrustAnchorObjects {
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct TaCertDetails {
cert: ReceivedCert,
tal: TrustAnchorLocator,
pub cert: ReceivedCert,
pub tal: TrustAnchorLocator,
}
impl TaCertDetails {
pub fn new(cert: ReceivedCert, tal: TrustAnchorLocator) -> Self {
TaCertDetails { cert, tal }
}
pub fn cert(&self) -> &ReceivedCert {
&self.cert
}
pub fn resources(&self) -> &ResourceSet {
&self.cert.resources
}
pub fn tal(&self) -> &TrustAnchorLocator {
&self.tal
}
}
impl From<TaCertDetails> for ReceivedCert {
fn from(details: TaCertDetails) -> Self {
details.cert
}
}
impl From<TaCertDetails> for TrustAnchorLocator {
fn from(details: TaCertDetails) -> Self {
details.tal
}
}
//------------ TrustAnchorLocator --------------------------------------------
@@ -411,7 +382,7 @@ impl fmt::Display for TrustAnchorSignerInfo {
"-------------------------------------------------------"
)?;
writeln!(f)?;
writeln!(f, "{}", self.ta_cert_details.tal())?;
writeln!(f, "{}", self.ta_cert_details.tal)?;
writeln!(f)?;
writeln!(
f,
+3 -3
View File
@@ -6,8 +6,8 @@ use clap::Parser;
use clap::crate_version;
use log::error;
use krill::constants::{KRILL_DEFAULT_CONFIG_FILE, KRILL_SERVER_APP};
use krill::server::config::Config;
use krill::server::http::server;
use krill::config::Config;
use krill::daemon::start::start_krill_daemon;
//------------ main ----------------------------------------------------------
@@ -18,7 +18,7 @@ async fn main() {
match Config::create(&args.config, false) {
Ok(config) => {
if let Err(e) = server::start_krill_daemon(
if let Err(e) = start_krill_daemon(
Arc::new(config), None
).await {
error!("Krill failed to start: {}", e);
+1 -1
View File
@@ -7,7 +7,7 @@ use log::info;
use log::LevelFilter;
use url::Url;
use krill::constants;
use krill::server::config::{Config, LogType};
use krill::config::{Config, LogType};
use krill::server::properties::PropertiesManager;
use krill::upgrades::{prepare_upgrade_data_migrations, UpgradeMode};
use krill::upgrades::data_migration::migrate;
@@ -43,7 +43,7 @@ use crate::{
KrillResult,
},
constants::ID_CERTIFICATE_VALIDITY_YEARS,
server::config::{SignerConfig, SignerType},
config::{SignerConfig, SignerType},
};
use crate::api::ca::ObjectName;
@@ -517,7 +517,7 @@ pub mod tests {
MockSigner, MockSignerCallCounts,
},
commons::test,
server::config::Config,
config::Config,
};
use super::*;
+32 -3
View File
@@ -1,6 +1,6 @@
//! Defines all Krill server side errors
use std::{fmt, fmt::Display, io};
use std::{error, fmt, fmt::Display, io};
use hyper::StatusCode;
@@ -29,8 +29,8 @@ use crate::{
storage,
storage::KeyValueError,
},
server::http::tls_keys,
server::http::auth::Permission,
daemon::http::tls_keys,
daemon::http::auth::Permission,
server::pubd::PublicationDeltaError,
upgrades::UpgradeError,
};
@@ -212,6 +212,8 @@ impl fmt::Display for FatalError {
}
}
impl error::Error for FatalError { }
//------------ Error -------------------------------------------------------
// Transitional type alias.
@@ -233,10 +235,12 @@ pub enum Error {
HttpClientError(httpclient::Error),
ConfigError(String),
UpgradeError(UpgradeError),
NotImplemented,
//-----------------------------------------------------------------
// General API Client Issues
//-----------------------------------------------------------------
UnexpectedBody,
JsonError(serde_json::Error),
InvalidUtf8Input,
ApiUnknownMethod,
@@ -318,6 +322,7 @@ pub enum Error {
//-----------------------------------------------------------------
// CA Child Issues
//-----------------------------------------------------------------
CaChildImportHandleMismatch { path: ChildHandle, body: ChildHandle },
CaChildDuplicate(CaHandle, ChildHandle),
CaChildUnknown(CaHandle, ChildHandle),
CaChildMustHaveResources(CaHandle, ChildHandle),
@@ -413,10 +418,12 @@ impl fmt::Display for Error {
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"),
//-----------------------------------------------------------------
// General API Client Issues
//-----------------------------------------------------------------
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"),
@@ -510,6 +517,12 @@ impl fmt::Display for Error {
//-----------------------------------------------------------------
// CA Child Issues
//-----------------------------------------------------------------
Error::CaChildImportHandleMismatch { path, body } => {
write!(f,
"mismatch between child handles: \
'{path}' in path, '{body}' in body"
)
},
Error::CaChildDuplicate(ca, child) => write!(f, "CA '{}' already has a child named '{}'", ca, child),
Error::CaChildUnknown(ca, child) => write!(f, "CA '{}' does not have a child named '{}'", ca, child),
Error::CaChildMustHaveResources(ca, child) => write!(f, "Child '{}' for CA '{}' MUST have resources specified", child, ca),
@@ -773,6 +786,8 @@ impl Error {
| Error::ApiLoginError(_) => StatusCode::UNAUTHORIZED,
Error::ApiInsufficientRights(_) => StatusCode::FORBIDDEN,
Error::NotImplemented => StatusCode::NOT_IMPLEMENTED,
_ => StatusCode::BAD_REQUEST,
}
}
@@ -833,9 +848,18 @@ impl Error {
ErrorResponse::new("sys-upgrade", self).with_cause(e)
}
// not yet implemented error
Error::NotImplemented => {
ErrorResponse::new("sys-not-implemented", self)
}
//-----------------------------------------------------------------
// General API Client Issues (label: api-*)
//-----------------------------------------------------------------
Error::UnexpectedBody => {
ErrorResponse::new("api-unexpected-body", self)
}
Error::JsonError(e) => {
ErrorResponse::new("api-json", self).with_cause(e)
}
@@ -1064,6 +1088,11 @@ impl Error {
}
// CA Child Issues
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)
+3 -3
View File
@@ -31,17 +31,17 @@ use crate::{
KrillResult,
},
constants::*,
server::{
daemon::{
http::auth::{Role, RoleMap},
http::tls_keys::{self, HTTPS_SUB_DIR},
mq::{in_seconds, Priority},
},
server::mq::{in_seconds, Priority},
tasigner::TaTimingConfig,
};
use crate::api::admin::{PublicationServerUris, Token};
#[cfg(feature = "multi-user")]
use crate::server::http::auth::providers::{
use crate::daemon::http::auth::providers::{
config_file::ConfigAuthUsers,
openid_connect::ConfigAuthOpenIDConnect,
};
+12
View File
@@ -277,6 +277,18 @@ pub fn ta_resource_class_name() -> rpki::ca::provisioning::ResourceClassName {
}
//------------ Testbed -------------------------------------------------------
/// The handle of the CA used by the testbed.
pub const TESTBED_CA_NAME: &str = "testbed";
/// Returns the CA handle for the testbed.
pub fn testbed_ca_handle() -> CaHandle {
use std::str::FromStr;
CaHandle::from_str(TESTBED_CA_NAME).unwrap()
}
//------------ Config File Auth Provider Defaults ----------------------------
//
// Note: These must match the values used by Lagosta.
@@ -9,9 +9,9 @@ use crate::api::admin::Token;
use crate::commons::KrillResult;
use crate::commons::actor::Actor;
use crate::commons::error::ApiAuthError;
use crate::server::config::{AuthType, Config};
use crate::server::http::request::HyperRequest;
use crate::server::http::response::HttpResponse;
use crate::config::{AuthType, Config};
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
use super::{Permission, Role};
use super::providers::admin_token;
#[cfg(feature = "multi-user")]
@@ -80,7 +80,7 @@ impl AuthProvider {
pub async fn authenticate(
&self,
request: &HyperRequest,
) -> Result<Option<AuthInfo>, ApiAuthError> {
) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
match &self {
AuthProvider::Token(provider) => provider.authenticate(request),
#[cfg(feature = "multi-user")]
@@ -159,6 +159,7 @@ impl AuthProvider {
}
/// If necessary, spawns a Tokio task sweeping the session cache.
#[allow(unused_variables)]
pub fn spawn_sweep(&self, runtime: &runtime::Handle) {
match self {
AuthProvider::Token(_) => { }
@@ -225,7 +226,9 @@ impl Authorizer {
/// Authenticates an HTTP request.
///
/// The method will always return authentication information.
/// The method will always return authentication information. It will also
/// return an optional token that should be added to a response as a
/// Bearer token.
///
/// If there was no authentiation information in the request, the returned
/// auth info will indicate an anonymous user which will fail all
@@ -236,7 +239,7 @@ impl Authorizer {
/// error information.
pub async fn authenticate_request(
&self, request: &HyperRequest
) -> AuthInfo {
) -> (AuthInfo, Option<Token>) {
trace!("Determining actor for request {:?}", &request);
// Try the legacy provider first, if any.
@@ -259,13 +262,13 @@ impl Authorizer {
Ok(Some(res)) => res,
// authentication failure
Ok(None) => AuthInfo::anonymous(),
Ok(None) => (AuthInfo::anonymous(), None),
// error during authentication
Err(err) => AuthInfo::error(err),
Err(err) => (AuthInfo::error(err), None),
};
trace!("Actor determination result: {:?}", res);
trace!("AuthInfo determination result: {:?}", res);
res
}
@@ -372,9 +375,6 @@ pub struct AuthInfo {
/// The actor for the authenticated user.
actor: Actor,
/// Optional updated bearer token.
new_token: Option<Token>,
/// Access permissions.
///
/// This is either a role which we consult to determine access
@@ -390,7 +390,6 @@ impl AuthInfo {
) -> Self {
Self {
actor: Actor::user(user_id),
new_token: None,
permissions: Ok(role),
}
}
@@ -406,7 +405,6 @@ impl AuthInfo {
fn anonymous() -> Self {
Self {
actor: Actor::anonymous(),
new_token: None,
permissions: Ok(Role::anonymous().into()),
}
}
@@ -415,28 +413,29 @@ impl AuthInfo {
fn error(err: ApiAuthError) -> Self {
Self {
actor: Actor::anonymous(),
new_token: None,
permissions: Err(err)
}
}
/// Sets the updated bearer token.
///
/// If set, this new token needs to be included in an HTTP response.
pub fn set_new_token(&mut self, new_token: Token) {
self.new_token = Some(new_token);
}
/// Takes out an updated bearer token if presnet
pub fn take_new_token(&mut self) -> Option<Token> {
self.new_token.take()
}
/// Returns a reference to the actor.
pub fn actor(&self) -> &Actor {
&self.actor
}
/// Converts the auth info into the actor.
pub fn into_actor(self) -> Actor {
self.actor
}
/// Returns for permissions.
pub fn has_permission(
&self,
permission: Permission,
resource: Option<&MyHandle>
) -> bool {
self.check_permission(permission, resource).is_ok()
}
/// Checks permissions for an operation.
///
/// Returns an authentication error if either the request was not
@@ -27,7 +27,7 @@ use crate::commons::ext_serde;
use crate::commons::KrillResult;
use crate::commons::error::{ApiAuthError, Error};
use crate::commons::storage::{Key, Namespace, Segment};
use crate::server::config::Config;
use crate::config::Config;
const CHACHA20_KEY_BIT_LEN: usize = 256;
const CHACHA20_KEY_BYTE_LEN: usize = CHACHA20_KEY_BIT_LEN / 8;
@@ -6,10 +6,10 @@ use crate::api::admin::Token;
use crate::commons::httpclient;
use crate::commons::KrillResult;
use crate::commons::error::{ApiAuthError, Error};
use crate::server::config::Config;
use crate::server::http::auth::{AuthInfo, LoggedInUser, Role};
use crate::server::http::request::HyperRequest;
use crate::server::http::response::HttpResponse;
use crate::config::Config;
use crate::daemon::http::auth::{AuthInfo, LoggedInUser, Role};
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
//------------ Constants -----------------------------------------------------
@@ -56,15 +56,16 @@ impl AuthProvider {
/// bearer token, returns `Ok(None)`.
pub fn authenticate(
&self, request: &HyperRequest,
) -> Result<Option<AuthInfo>, ApiAuthError> {
) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
if log_enabled!(log::Level::Trace) {
trace!("Attempting to authenticate the request..");
}
let res = match httpclient::get_bearer_token(request) {
Some(token) if token == self.required_token => {
Ok(Some(AuthInfo::user(
self.user_id.clone(), self.role.clone()
Ok(Some((
AuthInfo::user(self.user_id.clone(), self.role.clone()),
None
)))
}
Some(_) => Err(ApiAuthError::ApiInvalidCredentials(
@@ -105,7 +106,7 @@ impl AuthProvider {
&self,
request: &HyperRequest,
) -> KrillResult<HttpResponse> {
if let Ok(Some(info)) = self.authenticate(request) {
if let Ok(Some((info, _))) = self.authenticate(request) {
info!("User logged out: {}", info.actor().name());
}
@@ -13,12 +13,12 @@ use crate::commons::httpclient;
use crate::commons::KrillResult;
use crate::commons::error::{ApiAuthError, Error};
use crate::constants::{PW_HASH_LOG_N, PW_HASH_P, PW_HASH_R};
use crate::server::config::Config;
use crate::server::http::auth::crypt;
use crate::server::http::auth::{AuthInfo, LoggedInUser, Permission, RoleMap};
use crate::server::http::auth::session::{ClientSession, LoginSessionCache};
use crate::server::http::request::HyperRequest;
use crate::server::http::response::HttpResponse;
use crate::config::Config;
use crate::daemon::http::auth::crypt;
use crate::daemon::http::auth::{AuthInfo, LoggedInUser, Permission, RoleMap};
use crate::daemon::http::auth::session::{ClientSession, LoginSessionCache};
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
//------------ Constants -----------------------------------------------------
@@ -119,7 +119,7 @@ impl AuthProvider {
pub async fn authenticate(
&self,
request: &HyperRequest,
) -> Result<Option<AuthInfo>, ApiAuthError> {
) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
if log_enabled!(log::Level::Trace) {
trace!("Attempting to authenticate the request..");
}
@@ -136,7 +136,7 @@ impl AuthProvider {
trace!("user_id={}", session.user_id);
Ok(Some(self.auth_from_session(&session)?))
Ok(Some((self.auth_from_session(&session)?, None)))
}
_ => Ok(None),
};
@@ -280,7 +280,7 @@ impl AuthProvider {
Some(token) => {
self.session_cache.remove(&token).await;
if let Ok(Some(info)) = self.authenticate(request).await {
if let Ok(Some((info, _))) = self.authenticate(request).await {
info!("User logged out: {}", info.actor().name());
}
}
@@ -55,8 +55,9 @@ use serde::{Deserialize, Serialize};
use tokio::runtime;
use urlparse::{urlparse, GetQuery};
use crate::server::http::request::HyperRequest;
use crate::server::http::response::HttpResponse;
use crate::daemon::http::dispatch::AUTH_CALLBACK_ENDPOINT;
use crate::daemon::http::request::HyperRequest;
use crate::daemon::http::response::HttpResponse;
use crate::{
api::admin::Token,
commons::{
@@ -65,8 +66,8 @@ use crate::{
util::sha256,
KrillResult,
},
server::{
config::Config,
config::Config,
daemon::{
http::auth::{
crypt::{self, CryptState},
providers::openid_connect::{
@@ -80,7 +81,7 @@ use crate::{
session::*,
AuthInfo, LoggedInUser, Permission,
},
http::server::{url_encode, AUTH_CALLBACK_ENDPOINT},
http::util::url_encode,
},
};
use super::claims::Claims;
@@ -502,9 +503,7 @@ impl AuthProvider {
let redirect_uri = RedirectUrl::new(
self.config
.service_uri()
.join(
AUTH_CALLBACK_ENDPOINT.trim_start_matches('/').as_bytes(),
)
.join(AUTH_CALLBACK_ENDPOINT.as_bytes())
.unwrap()
.to_string(),
)?;
@@ -1143,7 +1142,7 @@ impl AuthProvider {
pub async fn authenticate(
&self,
request: &HyperRequest,
) -> Result<Option<AuthInfo>, ApiAuthError> {
) -> Result<Option<(AuthInfo, Option<Token>)>, ApiAuthError> {
trace!("Attempting to authenticate the request..");
self.initialize_connection_if_needed().await.map_err(|err| {
@@ -1168,14 +1167,18 @@ impl AuthProvider {
// return
match status {
SessionStatus::Active => {
return Ok(Some(self.auth_from_session(&session)?))
return Ok(Some(
(self.auth_from_session(&session)?, None)
))
}
SessionStatus::NeedsRefresh => {
// If we have a refresh token try and extend the
// session. Otherwise return the cached token
// and continue the login session until it expires.
if session.secrets.refresh_token.is_none() {
return Ok(Some(self.auth_from_session(&session)?))
return Ok(Some(
(self.auth_from_session(&session)?, None)
))
}
}
SessionStatus::Expired => {
@@ -1278,9 +1281,10 @@ impl AuthProvider {
}
};
let mut auth = self.auth_from_session(&session)?;
auth.set_new_token(new_token);
Ok(Some(auth))
Ok(Some((
self.auth_from_session(&session)?,
Some(new_token)
)))
}
_ => Ok(None),
};
+48
View File
@@ -0,0 +1,48 @@
//! `/api`
use super::super::auth::Permission;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("v1") => api_v1(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
async fn api_v1(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("authorized") => authorized(request, path),
other => {
request.check_permission(Permission::Login, None)?;
match other {
Some("bulk") => super::bulk::dispatch(request, path).await,
Some("cas") => super::cas::dispatch(request, path).await,
Some("pubd") => super::pubd::dispatch(request, path).await,
Some("ta") => super::ta::dispatch(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
}
}
fn authorized(
request: Request, path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
request.check_permission(Permission::Login, None).map_err(|err| {
err.with_benign(true)
})?;
Ok(HttpResponse::ok())
}
+131
View File
@@ -0,0 +1,131 @@
//! `/auth`
use hyper::Method;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /auth ---------------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("login") => login(request, path).await,
Some("logout") => logout(request, path).await,
#[cfg(feature = "multi-user")]
Some("callback") => multi_user::callback(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
async fn login(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
path.check_exhausted()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(server.authorizer().get_login_url().await?)
}
Method::POST => {
path.check_exhausted()?;
let (server, request) = request.proceed_raw();
Ok(HttpResponse::json(
&server.authorizer().login(&request).await?
))
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
async fn logout(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (server, request) = request.proceed_raw();
Ok(server.authorizer().logout(&request).await?)
}
#[cfg(feature = "multi-user")]
mod multi_user {
use base64::engine::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_ENGINE;
use log::trace;
use crate::commons::error::Error;
use crate::daemon::http::auth::LoggedInUser;
use crate::daemon::http::util::url_encode;
use super::*;
pub async fn callback(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
trace!(
"Authentication callback invoked: {:?}", &request.hyper()
);
let (server, request) = request.proceed_raw();
server.authorizer().login(&request).await.and_then(|user| {
build_auth_redirect_location(user).map_err(|err| {
Error::custom(format!(
"Unable to build redirect with logged in user details: \
{:?}",
err
))
})
}).map(|location| {
HttpResponse::found(&location)
}).or_else(|err| {
// HTTP redirects cannot have a response body and so we cannot
// render the error to be displayed in Lagosta as a JSON body,
// instead we must package the JSON as a query parameter.
let location = match serde_json::to_string(
&err.to_error_response()
) {
Ok(json) => {
format!("/ui/login?error={}", BASE64_ENGINE.encode(json))
}
Err(_) => String::from("/ui/login")
};
Ok(HttpResponse::found(&location))
})
}
fn build_auth_redirect_location(
user: LoggedInUser
) -> Result<String, Error> {
fn b64_encode_attributes_with_mapped_error(
a: &impl serde::Serialize,
) -> Result<String, Error> {
Ok(BASE64_ENGINE.encode(
serde_json::to_string(a)
.map_err(|err| Error::custom(err.to_string()))?,
))
}
let attributes = b64_encode_attributes_with_mapped_error(
user.attributes()
)?;
Ok(format!(
"/ui/login?token={}&id={}&attributes={}",
&url_encode(user.token())?,
&url_encode(user.id())?,
&url_encode(attributes)?,
))
}
}
+139
View File
@@ -0,0 +1,139 @@
//! `/api/v1/bulk`
use crate::api::ca::AllCertAuthIssues;
use super::super::auth::Permission;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("cas") => cas(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
async fn cas(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("import") => cas_import(request, path).await,
Some("issues") => cas_issues(request, path),
Some("sync") => cas_sync(request, path),
Some("publish") => cas_publish(request, path),
Some("force_publish") => cas_force_publish(request, path),
Some("suspend") => cas_suspend(request, path),
_ => Ok(HttpResponse::not_found())
}
}
async fn cas_import(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let (server, structure) = request.read_json().await?;
server.krill().cas_import(structure).await?;
Ok(HttpResponse::ok())
}
fn cas_issues(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, auth) = request.proceed_unchecked();
let server = request.empty()?;
let mut all_issues = AllCertAuthIssues::default();
for ca in server.krill().ca_handles()? {
if auth.has_permission(Permission::CaRead, Some(&ca)) {
let issues = server.krill().ca_issues(&ca)?;
if !issues.is_empty() {
all_issues.cas.insert(ca, issues);
}
}
}
Ok(HttpResponse::json(&all_issues))
}
fn cas_sync(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("parent") => cas_sync_parent(request, path),
Some("repo") => cas_sync_repo(request, path),
_ => Ok(HttpResponse::not_found())
}
}
fn cas_sync_parent(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
server.krill().cas_refresh_all()?;
Ok(HttpResponse::ok())
}
fn cas_sync_repo(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
server.krill().cas_repo_sync_all()?;
Ok(HttpResponse::ok())
}
fn cas_publish(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
server.krill().republish_all(false)?;
Ok(HttpResponse::ok())
}
fn cas_force_publish(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
server.krill().republish_all(true)?;
Ok(HttpResponse::ok())
}
fn cas_suspend(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
server.krill().cas_schedule_suspend_all()?;
Ok(HttpResponse::ok())
}
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
//! Dispatch error handling.
use crate::commons::error::{Error, FatalError};
use super::super::response::HttpResponse;
//------------ DispatchError -------------------------------------------------
/// An error occured during dispatch.
///
/// This error type exists so you can use the question mark operator for all
/// sorts of things during dispatch to minimize clutter.
///
/// The error can either be a response sent back to the client or a fatal
/// error ending the server. Various `From<_>` impls are provided to correctly
/// translate errors into one of the two cases.
#[derive(Debug)]
pub enum DispatchError {
/// A response should be sent to the client.
Response(HttpResponse),
/// A fatal error happened that should terminate the server.
#[allow(dead_code)]
Fatal(FatalError),
}
impl From<HttpResponse> for DispatchError {
fn from(src: HttpResponse) -> Self {
Self::Response(src)
}
}
impl From<Error> for DispatchError {
fn from(src: Error) -> Self {
Self::Response(HttpResponse::response_from_error(src))
}
}
@@ -4,16 +4,20 @@ use std::fmt;
use std::collections::HashMap;
use std::fmt::Write;
use crate::constants::TA_NAME;
use super::request::Request;
use super::response::HttpResponse;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
if !req.is_get() || !req.path().segment().starts_with("metrics") {
return Err(req)
}
pub async fn dispatch(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
let server = req.state();
let mut target = Target::default();
target.single(
@@ -52,21 +56,21 @@ pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
"auth_session_cache_size",
"total number of cached login session tokens",
),
server.login_session_cache_size().await,
server.authorizer().login_session_cache_size().await,
);
if let Ok(cas_stats) = server.cas_stats().await {
if let Ok(cas_stats) = server.krill().cas_stats().await {
target.single(
Metric::gauge("cas", "number of CAs in Krill"),
cas_stats.len()
);
if !server.config.metrics.metrics_hide_ca_details {
if !server.config().metrics.metrics_hide_ca_details {
let mut ca_status_map = HashMap::new();
for ca in cas_stats.keys() {
if let Ok(ca_status) = server.ca_status(ca) {
if let Ok(ca_status) = server.krill().ca_status(ca) {
ca_status_map.insert(ca.clone(), ca_status);
}
}
@@ -155,7 +159,7 @@ pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
});
if any_children
&& !server.config.metrics.metrics_hide_child_details
&& !server.config().metrics.metrics_hide_child_details
{
let metric = Metric::gauge(
"cas_children",
@@ -279,7 +283,7 @@ pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
}
}
if !server.config.metrics.metrics_hide_roa_details {
if !server.config().metrics.metrics_hide_roa_details {
let metric = Metric::gauge(
"cas_bgp_announcements_valid",
"number of announcements seen for CA resources \
@@ -382,7 +386,7 @@ pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
}
}
if let Ok(stats) = server.repo_stats() {
if let Ok(stats) = server.krill().repo_stats() {
target.single(
Metric::gauge(
"repo_publisher",
@@ -409,7 +413,7 @@ pub async fn metrics(req: Request) -> Result<HttpResponse, Request> {
stats.serial
);
if !server.config.metrics.metrics_hide_publisher_details {
if !server.config().metrics.metrics_hide_publisher_details {
let metric = Metric::gauge(
"repo_objects",
"number of objects in repository for publisher"
+25
View File
@@ -0,0 +1,25 @@
//! Dispatching of HTTP requests.
pub use self::error::DispatchError;
pub use self::root::dispatch_request;
/// The authentication callback path used by the OpenID provider.
///
/// This must not start with a slash.
///
/// It must also resolve to be dispatched to `self::auth::callback`.
#[cfg(feature = "multi-user")]
pub const AUTH_CALLBACK_ENDPOINT: &str = "auth/callback";
mod api;
mod auth;
mod cas;
mod bulk;
mod error;
mod metrics;
mod pubd;
mod root;
mod stats;
mod ta;
mod testbed;
+231
View File
@@ -0,0 +1,231 @@
//! `/api/v1/pubd`
use hyper::Method;
use rpki::ca::idexchange::PublisherHandle;
use crate::api::admin::{PublisherList, PublisherSummary};
use super::super::auth::Permission;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /api/v1/pubd --------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
request.check_permission(Permission::PubAdmin, None)?;
match path.next() {
Some("delete") => delete(request, path).await,
Some("init") => init(request, path).await,
Some("publishers") => publishers(request, path).await,
Some("session_reset") => session_reset(request, path),
Some("stale") => stale(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
//------------ /api/v1/pubd/delete -------------------------------------------
async fn delete(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(
Permission::PubAdmin, None
)?;
let (server, criteria) = request.read_json().await?;
server.krill().delete_matching_files(criteria)?;
Ok(HttpResponse::ok())
}
//------------ /api/v1/pubd/init ---------------------------------------------
async fn init(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
match *request.method() {
Method::POST => {
let (request, _) = request.proceed_permitted(
Permission::PubAdmin, None
)?;
let (server, uris) = request.read_json().await?;
server.krill().repository_init(uris)?;
Ok(HttpResponse::ok())
}
Method::DELETE => {
let (request, _) = request.proceed_permitted(
Permission::PubAdmin, None
)?;
let server = request.empty()?;
server.krill().repository_clear()?;
Ok(HttpResponse::ok())
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
//------------ /api/v1/pubd/publishers ---------------------------------------
async fn publishers(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.parse_opt_next()? {
None => publishers_index(request).await,
Some(publisher) => {
publishers_publisher(request, path, publisher)
}
}
}
async fn publishers_index(
request: Request<'_>,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
let (request, _) = request.proceed_permitted(
Permission::PubList, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&PublisherList {
publishers: {
server.krill().publishers()?.into_iter().map(
PublisherSummary::from_handle
).collect()
}
}
))
}
Method::POST => {
let (request, auth) = request.proceed_permitted(
Permission::PubCreate, None
)?;
let (server, pbl) = request.read_json().await?;
Ok(HttpResponse::json(
&server.krill().add_publisher(pbl, auth.actor())?
))
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
fn publishers_publisher(
request: Request<'_>,
mut path: PathIter<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => publishers_publisher_index(request, publisher),
Some("response.json") => {
publishers_publisher_response(request, path, publisher)
}
Some("response.xml") => {
publishers_publisher_response_xml(request, path, publisher)
}
_ => Ok(HttpResponse::not_found())
}
}
fn publishers_publisher_index(
request: Request<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
let (request, _) = request.proceed_permitted(
Permission::PubRead, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().get_publisher(publisher)?
))
}
Method::DELETE => {
let (request, auth) = request.proceed_permitted(
Permission::PubDelete, None
)?;
let server = request.empty()?;
server.krill().remove_publisher(publisher, auth.actor())?;
Ok(HttpResponse::ok())
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
fn publishers_publisher_response(
request: Request<'_>,
path: PathIter<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(Permission::PubRead, None)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().repository_response(&publisher)?
))
}
fn publishers_publisher_response_xml(
request: Request<'_>,
path: PathIter<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(Permission::PubRead, None)?;
let server = request.empty()?;
Ok(HttpResponse::xml(
server.krill().repository_response(&publisher)?.to_xml_vec()
))
}
//------------ /api/v1/pubd/session_reset ------------------------------------
fn session_reset(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(Permission::PubAdmin, None)?;
let server = request.empty()?;
server.krill().repository_session_reset()?;
Ok(HttpResponse::ok())
}
//------------ /api/v1/pubd/stale --------------------------------------------
async fn stale(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
let seconds = path.parse_opt_next()?.unwrap_or(0);
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted( Permission::PubList, None)?;
let server = request.empty()?;
let stats = server.krill().repo_stats()?;
Ok(HttpResponse::json(
&PublisherList {
publishers: {
stats.stale_publishers(seconds).map(
PublisherSummary::from_handle
).collect()
}
}
))
}
+221
View File
@@ -0,0 +1,221 @@
//! Root level.
use std::fs;
use super::super::request::{Request, PathIter};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ / -------------------------------------------------------------
pub async fn dispatch_request(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => index(request),
Some("api") => super::api::dispatch(request, path).await,
Some("assets") => assets(request, path),
Some("auth") => super::auth::dispatch(request, path).await,
Some("health") => health(request, path),
Some("metrics") => super::metrics::dispatch(request, path).await,
Some("rfc8181") => rfc8181(request, path).await,
Some("rfc6492") => rfc6492(request, path).await,
Some("rrdp") => rrdp(request, path),
Some("stats") => super::stats::dispatch(request, path).await,
Some("ta") => ta(request, path),
Some("testbed.tal") => tal(request, path),
Some("testbed") => super::testbed::dispatch(request, path).await,
Some("ui") => ui(request, path),
// statics
//
_ => Ok(HttpResponse::not_found())
}
}
//------------ / -------------------------------------------------------------
fn index(
request: Request<'_>
) -> Result<HttpResponse, DispatchError> {
request.check_get()?;
let (request, _) = request.proceed_unchecked();
request.empty()?;
Ok(HttpResponse::found("/ui"))
}
//------------ /health -------------------------------------------------------
fn health(
request: Request<'_>, path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
request.empty()?;
Ok(HttpResponse::ok())
}
//------------ /rfc8181 ------------------------------------------------------
async fn rfc8181(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
// We need to allow trailing slashes for compatibility.
let mut path = path.strip_trailing_slash();
let publisher = path.parse_next()?;
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_unchecked();
let (server, bytes) = request.read_rfc8181_bytes().await?;
Ok(HttpResponse::rfc8181(
server.krill().rfc8181(publisher, bytes)?
))
}
//------------ /rfc6492 ------------------------------------------------------
async fn rfc6492(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
// We need to allow trailing slashes for compatibility.
let mut path = path.strip_trailing_slash();
let ca = path.parse_next()?;
path.check_exhausted()?;
request.check_post()?;
let user_agent = request.user_agent();
let (request, auth) = request.proceed_unchecked();
let (server, bytes) = request.read_rfc6492_bytes().await?;
// XXX Using auth.actor() here doesnt make much sense -- it likely will
// always be the anonymous actor. Maybe the CA manager should
// determine the actor when looking at the ID certificate?
Ok(HttpResponse::rfc6492(
server.krill().rfc6492(ca , bytes, user_agent, auth.actor())?
))
}
//------------ /ta -----------------------------------------------------------
fn ta(
request: Request<'_>, mut path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("ta.tal") => tal(request, path),
Some("ta.cer") => ta_cer(request, path),
_ => Ok(HttpResponse::not_found())
}
}
fn tal(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::text(
server.krill().ta_cert_details()?.tal.to_string()
))
}
fn ta_cer(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::cert(
server.krill().ta_cert_details()?.cert.to_bytes()
))
}
//------------ /rrdp ---------------------------------------------------------
fn rrdp(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
let Some(remaining) = path.remaining() else {
return Ok(HttpResponse::not_found())
};
let path = match server.krill().resolve_rrdp_request_path(remaining)? {
Some(path) => path,
None => {
return Ok(HttpResponse::not_found())
}
};
let cache_seconds = if remaining == "notification.xml" {
60
} else {
86400
};
let buffer = match fs::read(&path) {
Ok(file) => file,
Err(_) => {
return Ok(HttpResponse::not_found())
}
};
Ok(HttpResponse::xml_with_cache(buffer, cache_seconds))
}
//------------ /ui -----------------------------------------------------------
fn ui(
request: Request<'_>, path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let _server = request.empty()?;
Ok(HttpResponse::ok_with_body(
assets::INDEX.media_type,
assets::INDEX.content,
))
}
//------------ /assets -------------------------------------------------------
fn assets(
request: Request<'_>, mut path: PathIter<'_>
) -> Result<HttpResponse, DispatchError> {
let asset = {
let Some(next) = path.next() else {
return Ok(HttpResponse::not_found())
};
match assets::ASSETS.iter().find(|asset| {
asset.path == next
}) {
Some(asset) => asset,
None => return Ok(HttpResponse::not_found()),
}
};
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let _server = request.empty()?;
Ok(HttpResponse::ok_with_body(
asset.media_type,
asset.content,
))
}
mod assets {
include!(concat!(env!("OUT_DIR"), "/ui_assets.rs"));
}
+63
View File
@@ -0,0 +1,63 @@
//! `/api/v1/ta`
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /stats --------------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("info") => info(request, path),
Some("repo") => repo(request, path),
Some("cas") => cas(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
//------------ /stats/info ---------------------------------------------------
fn info(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::json(&server.server_info()))
}
//------------ /stats/repo ---------------------------------------------------
fn repo(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::json(&server.krill().repo_stats()?))
}
//------------ /stats/cas ----------------------------------------------------
async fn cas(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::json(&server.krill().cas_stats().await?))
}
+340
View File
@@ -0,0 +1,340 @@
//! `/api/v1/ta`
use hyper::Method;
use rpki::ca::idexchange::ChildHandle;
use crate::commons::error::Error;
use crate::constants::ta_handle;
use super::super::auth::Permission;
use super::super::request::{PathIter, Request};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /api/v1/ta ----------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("proxy") => proxy(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
//------------ /api/v1/proxy -------------------------------------------------
async fn proxy(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("children") => proxy_children(request, path).await,
Some("id") => proxy_id(request, path),
Some("init") => proxy_init(request, path),
Some("repo") => proxy_repo(request, path).await,
Some("signer") => proxy_signer(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
//------------ /api/v1/proxy/children ----------------------------------------
async fn proxy_children(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.parse_opt_next()? {
None => proxy_children_index(request).await,
Some(child) => proxy_children_child(request, path, child),
}
}
async fn proxy_children_index(
request: Request<'_>,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
request.empty()?;
Err(Error::NotImplemented.into())
}
Method::POST => {
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let (server, child) = request.read_json().await?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_children_add(child, auth.actor())?
))
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
fn proxy_children_child(
request: Request<'_>,
mut path: PathIter<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => proxy_children_child_index(request, child),
Some("parent_response.json") => {
proxy_children_child_response(request, path, child)
}
Some("parent_response.xml") => {
proxy_children_child_response_xml(request, path, child)
}
_ => Ok(HttpResponse::not_found())
}
}
fn proxy_children_child_index(
request: Request<'_>,
_child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::POST => {
request.proceed_permitted(Permission::CaAdmin, None)?;
// Ignore body for now.
Err(Error::NotImplemented.into())
}
Method::DELETE => {
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
request.empty()?;
Err(Error::NotImplemented.into())
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
fn proxy_children_child_response(
request: Request<'_>,
path: PathIter<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ca_parent_response(&ta_handle(), child)?
))
}
fn proxy_children_child_response_xml(
request: Request<'_>,
path: PathIter<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::xml(
server.krill().ca_parent_response(&ta_handle(), child)?.to_xml_vec()
))
}
//------------ /api/v1/proxy/init --------------------------------------------
fn proxy_init(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
server.krill().ta_proxy_init()?;
Ok(HttpResponse::ok())
}
//------------ /api/v1/proxy/id ----------------------------------------------
fn proxy_id(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_id()?
))
}
//------------ /api/v1/proxy/repo --------------------------------------------
async fn proxy_repo(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => proxy_repo_index(request).await,
Some("request.json") => proxy_repo_request(request, path),
Some("request.xml") => proxy_repo_request_xml(request, path),
_ => Ok(HttpResponse::not_found())
}
}
async fn proxy_repo_index(
request: Request<'_>,
) -> Result<HttpResponse, DispatchError> {
match *request.method() {
Method::GET => {
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_repository_contact()?
))
}
Method::POST => {
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let (server, update) = request.read_bytes().await?;
let update = super::cas::extract_repository_contact(
&ta_handle(), update
)?;
server.krill().ta_proxy_repository_update(update, auth.actor())?;
Ok(HttpResponse::ok())
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
fn proxy_repo_request(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_publisher_request()?
))
}
fn proxy_repo_request_xml(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_permitted(Permission::CaAdmin, None)?;
let server = request.empty()?;
Ok(HttpResponse::xml(
server.krill().ta_proxy_publisher_request()?.to_xml_vec()
))
}
//------------ /api/v1/proxy/signer ------------------------------------------
async fn proxy_signer(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
Some("add") => proxy_signer_add(request, path).await,
Some("request") => proxy_signer_request(request, path),
Some("response") => proxy_signer_response(request, path).await,
Some("update") => proxy_signer_update(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
async fn proxy_signer_add(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let (server, info) = request.read_json().await?;
server.krill().ta_proxy_signer_add(info, auth.actor())?;
Ok(HttpResponse::ok())
}
fn proxy_signer_request(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
match *request.method() {
Method::GET => {
let (request, _) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_signer_get_request()?
))
}
Method::POST => {
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let server = request.empty()?;
Ok(HttpResponse::json(
&server.krill().ta_proxy_signer_make_request(
auth.actor()
)?
))
}
_ => Ok(HttpResponse::method_not_allowed())
}
}
async fn proxy_signer_response(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let (server, response) = request.read_json().await?;
server.krill().ta_proxy_signer_process_response(response, auth.actor())?;
Ok(HttpResponse::ok())
}
async fn proxy_signer_update(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_post()?;
let (request, auth) = request.proceed_permitted(
Permission::CaAdmin, None
)?;
let (server, info) = request.read_json().await?;
server.krill().ta_proxy_signer_update(info, auth.actor())?;
Ok(HttpResponse::ok())
}
+199
View File
@@ -0,0 +1,199 @@
//! `/testbed`
//!
//! Testbed mode enables Krill to run as an open root of a test RPKI hierarchy
//! with web-UI based self-service ability for other RPKI certificate
//! authorities to integrate themselves into the test RPKI hierarchy, both as
//! children whose resources are delegated from the testbed and as publishers
//! into the testbed repository. This feature is very similar to existing
//! web-UI based self-service RPKI test hierarchies such as the RIPE NCC RPKI
//! Test Environment and the APNIC RPKI Testbed.
//!
//! Krill can already do this via a combination of use_ta=true and the
//! existing Krill API _but_ crucially the other RPKI certificate authorities
//! would need to know the Krill API token in order to register themselves
//! with the Krill testbed, giving them far too much power over the testbed.
//! Testbed mode exposes *open* `/testbed/xxx` wrapper API endpoints for
//! exchanging the RFC 8183 XMLs, e.g.:
//!
//! * `/testbed/enabled`: should the web-UI show the testbed UI page?
//! * `/testbed/children`: `<client_request/>` in, `<parent_response/>` out
//! * `/testbed/publishers`: `<publisher_request/>` in,
//! `<repository_response/>` out
//!
//! This feature assumes the existence of a built-in "testbed" CA and
//! publisher when testbed mode is enabled.
use rpki::ca::idexchange::{ChildHandle, PublisherHandle};
use crate::commons::actor::Actor;
use crate::constants::testbed_ca_handle;
use super::super::request::{Request, PathIter};
use super::super::response::HttpResponse;
use super::error::DispatchError;
//------------ /testbed ------------------------------------------------------
pub async fn dispatch(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
if !request.testbed_enabled() {
return Ok(HttpResponse::not_found())
}
match path.next() {
Some("enabled") => enabled(request, path),
Some("children") => children(request, path).await,
Some("publishers") => publishers(request, path).await,
_ => Ok(HttpResponse::not_found())
}
}
//------------ /testbed/enabled ----------------------------------------------
fn enabled(
request: Request<'_>,
path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let _server = request.empty()?;
Ok(HttpResponse::ok())
}
//------------ /testbed/children ---------------------------------------------
async fn children(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.parse_opt_next()? {
None => children_index(request).await,
Some(child) => children_child(request, path, child),
}
}
async fn children_index(
request: Request<'_>
) -> Result<HttpResponse, DispatchError> {
request.check_post()?;
let (request, _) = request.proceed_unchecked();
let (server, child) = request.read_json().await?;
Ok(HttpResponse::json(
&server.krill().ca_add_child(
&testbed_ca_handle(), child, &Actor::anonymous()
)?
))
}
fn children_child(
request: Request<'_>,
mut path: PathIter<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => children_child_index(request, child),
Some("parent_response.xml") => {
children_child_response(request, path, child)
}
_ => Ok(HttpResponse::not_found())
}
}
fn children_child_index(
request: Request<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
request.check_delete()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
server.krill().ca_child_remove(
&testbed_ca_handle(), child, &Actor::anonymous()
)?;
Ok(HttpResponse::ok())
}
fn children_child_response(
request: Request<'_>,
path: PathIter<'_>,
child: ChildHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::xml(
server.krill().ca_parent_response(
&testbed_ca_handle(), child
)?.to_xml_vec()
))
}
//------------ /testbed/publishers -------------------------------------------
async fn publishers(
request: Request<'_>,
mut path: PathIter<'_>,
) -> Result<HttpResponse, DispatchError> {
match path.parse_opt_next()? {
None => publishers_index(request).await,
Some(publisher) => publishers_publisher(request, path, publisher),
}
}
async fn publishers_index(
request: Request<'_>
) -> Result<HttpResponse, DispatchError> {
request.check_post()?;
let (request, _) = request.proceed_unchecked();
let (server, pbl) = request.read_json().await?;
Ok(HttpResponse::json(
&server.krill().add_publisher(pbl, &Actor::anonymous())?
))
}
fn publishers_publisher(
request: Request<'_>,
mut path: PathIter<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
match path.next() {
None => publishers_publisher_index(request, publisher),
Some("response.xml") => {
publishers_publisher_response(request, path, publisher)
}
_ => Ok(HttpResponse::not_found())
}
}
fn publishers_publisher_index(
request: Request<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
request.check_delete()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
server.krill().remove_publisher(
publisher, &Actor::anonymous()
)?;
Ok(HttpResponse::ok())
}
fn publishers_publisher_response(
request: Request<'_>,
path: PathIter<'_>,
publisher: PublisherHandle,
) -> Result<HttpResponse, DispatchError> {
path.check_exhausted()?;
request.check_get()?;
let (request, _) = request.proceed_unchecked();
let server = request.empty()?;
Ok(HttpResponse::xml(
server.krill().repository_response(&publisher)?.to_xml_vec()
))
}
+11
View File
@@ -0,0 +1,11 @@
pub mod auth;
mod request;
mod response;
pub mod server;
pub mod tls;
pub mod tls_keys;
mod dispatch;
mod util;
+527
View File
@@ -0,0 +1,527 @@
//! HTTP requests.
#![allow(dead_code)] // XXX
use std::{fmt, str};
use std::borrow::Cow;
use std::str::FromStr;
use bytes::Bytes;
use http_body_util::{BodyExt, Limited};
use hyper::Method;
use hyper::body::Body;
use hyper::header::USER_AGENT;
use hyper::http::uri::PathAndQuery;
use percent_encoding::percent_decode;
use rpki::ca::idexchange::MyHandle;
use serde::de::DeserializeOwned;
use crate::api::status::ErrorResponse;
use crate::commons::error::Error;
use crate::config::Config;
use crate::constants::HTTP_USER_AGENT_TRUNCATE;
use super::auth::{AuthInfo, Permission};
use super::response::HttpResponse;
use super::server::HttpServer;
//------------ HyperRequest --------------------------------------------------
/// A type alias for the request we receive from Hyper.
pub type HyperRequest = hyper::Request<hyper::body::Incoming>;
//------------ Request -------------------------------------------------------
/// An enriched request.
pub struct Request<'a> {
/// The underlying raw request.
request: HyperRequest,
/// The server providing access to Krill itself.
server: &'a HttpServer,
/// Authentication information for the request.
auth: AuthInfo,
/// The limits for reading the body of the request.
limits: BodyLimits,
}
impl<'a> Request<'a> {
/// Creates a request from the various necessary information.
pub fn new(
request: HyperRequest,
server: &'a HttpServer,
auth: AuthInfo,
limits: BodyLimits,
) ->Self {
Self { request, server, auth, limits }
}
/// Returns whether testbed mode is enabled.
pub fn testbed_enabled(&self) -> bool {
self.server.krill().testbed_enabled()
}
/// Returns the method of this request.
pub fn method(&self) -> &Method {
self.request.method()
}
/// Checks whether the request is a GET or returns an error response.
pub fn check_get(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::GET => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
/// Checks whether the request is a POST or returns an error response.
pub fn check_post(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::POST => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
/// Checks whether the request is a POST or returns an error response.
pub fn check_delete(&self) -> Result<(), HttpResponse> {
match *self.request.method() {
Method::DELETE => Ok(()),
_ => Err(HttpResponse::method_not_allowed()),
}
}
/*
/// Returns the full URI of the request.
pub fn uri(&self) -> &Uri {
self.request.uri()
}
*/
/// Returns the current request path.
pub fn path(&self) -> Result<RequestPath, InvalidPath> {
RequestPath::from_request(self)
}
/// Returns a reference to the Hyper request.
pub fn hyper(&self) -> &HyperRequest {
&self.request
}
/*
/// Returns the headers of the request.
pub fn headers(&self) -> &HeaderMap {
self.request.headers()
}
*/
/// Returns the user agent header if present.
pub fn user_agent(&self) -> Option<String> {
match self.request.headers().get(&USER_AGENT) {
None => None,
Some(value) => value.to_str().ok().map(|s| {
// Note: HeaderValue.to_str() only returns ok in case the
// value is plain ascii so it's safe to
// treat bytes as characters here.
if s.len() > HTTP_USER_AGENT_TRUNCATE {
s[..HTTP_USER_AGENT_TRUNCATE].to_string()
} else {
s.to_string()
}
}),
}
}
/// Checks for permissions.
///
/// Returns an appropriate error response if the permissions are not met.
pub fn check_permission(
&self, permission: Permission, resource: Option<&MyHandle>
) -> Result<(), HttpResponse> {
self.auth.check_permission(permission, resource).map_err(|err| {
HttpResponse::response_from_error(Error::from(err))
})
}
/// Checks the permissions and progresses to the next processing stage.
///
/// If the authentication information for the request has the given
/// permissions, returns an [`AuthedRequest`] and an [`Actor`] which
/// allow further processing.
///
/// Otherwise returns an appropriate error response.
pub fn proceed_permitted(
self,
permission: Permission,
resource: Option<&MyHandle>,
) -> Result<(AuthedRequest<'a>, AuthInfo), HttpResponse> {
self.check_permission(permission, resource)?;
Ok((
AuthedRequest {
request: self.request,
server: self.server,
limits: self.limits,
},
self.auth
))
}
/// Permits the request to the next processing stage.
///
/// Returns [`AuthedRequest`] and [`Actor`] without requiring any
/// permissions whatsoever.
pub fn proceed_unchecked(
self
) -> (AuthedRequest<'a>, AuthInfo) {
(
AuthedRequest {
request: self.request,
server: self.server,
limits: self.limits,
},
self.auth
)
}
/// Splits the request into the server and raw Hyper request.
pub fn proceed_raw(self) -> (&'a HttpServer, HyperRequest) {
(self.server, self.request)
}
}
//------------ AuthedRequest -------------------------------------------------
/// A request that has been checked for the correct access permissions.
///
/// This type allows access to the requests body and, by way of reading the
/// body or forcing it to be empty, to the server.
pub struct AuthedRequest<'a> {
/// The underlying raw request.
request: HyperRequest,
/// The server providing access to Krill itself.
server: &'a HttpServer,
/// The limits for reading the body of the request.
limits: BodyLimits,
}
impl<'a> AuthedRequest<'a> {
/// Ensures the body is empty.
pub fn empty(self) -> Result<&'a HttpServer, Error> {
if self.request.body().size_hint().upper() != Some(0) {
return Err(Error::UnexpectedBody)
}
Ok(self.server)
}
/// Returns the raw bytes of the request body.
pub async fn read_bytes(self) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_api;
self.read_body(limit).await
}
/// Get a json object from a post body
pub async fn read_json<T: DeserializeOwned>(
self
) -> Result<(&'a HttpServer, T), Error> {
let (server, bytes) = self.read_bytes().await?;
let json = serde_json::from_slice(&bytes).map_err(Error::JsonError)?;
Ok((server, json))
}
/// Returns the raw bytes of a provisioning protocol request.
pub async fn read_rfc6492_bytes(
self
) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_rfc6492;
self.read_body(limit).await
}
pub async fn read_rfc8181_bytes(
self
) -> Result<(&'a HttpServer, Bytes), Error> {
let limit = self.limits.post_limit_rfc8181;
self.read_body(limit).await
}
async fn read_body(
self, limit: u64
) -> Result<(&'a HttpServer, Bytes), Error> {
// Were going to cheat a bit. If we know the body is too big from
// the Content-Length header, we return Error::PostTooBig. But if
// we dont -- which means there are multiple chunks or somesuch --
// we just use http_body_utils::Limited and return PostCannotRead
// on any error.
if self.request.body().size_hint().lower() > limit {
return Err(Error::PostTooBig);
}
Ok((
self.server,
Limited::new(
self.request.into_body(),
limit.try_into().unwrap_or(usize::MAX),
).collect().await.map_err(|_| {
Error::PostCannotRead
})?.to_bytes()
))
}
}
//------------ RequestPath ---------------------------------------------------
/// The path of a requests URI.
///
/// It primarily allows iterating over the path segments. Note that because it
/// needs to be a “borrowing iterator,” it cannot implement the normal
/// `Iterator` trait.
#[derive(Debug, Clone)]
pub struct RequestPath {
path: Result<PathAndQuery, String>,
}
impl RequestPath {
fn from_request(request: &Request) -> Result<Self, InvalidPath> {
let path = if let Cow::Owned(some) = percent_decode(
request.request.uri().path().as_bytes()
).decode_utf8().map_err(|_| InvalidPath)? {
Err(some)
}
else {
Ok(
request.request.uri().path_and_query()
.ok_or(InvalidPath)?.clone()
)
};
Ok(Self { path })
}
pub fn as_str(&self) -> &str {
match self.path.as_ref() {
Ok(path) => path.path(),
Err(path) => path.as_str()
}
}
pub fn iter(&self) -> PathIter {
PathIter::new(self.as_str())
}
}
impl AsRef<str> for RequestPath {
fn as_ref(&self) -> &str {
self.as_str()
}
}
//------------ PathIter -------------------------------------------------
#[derive(Debug)]
pub struct PathIter<'a> {
full: &'a str,
remaining: Option<&'a str>,
}
impl<'a> PathIter<'a> {
fn new(path: &'a str) -> Self {
Self {
full: path,
remaining: Some(path.strip_prefix('/').unwrap_or(path))
}
}
/// Returns a copy with a possible trailing slash removed.
pub fn strip_trailing_slash(&self) -> Self {
// Some("") means there _was_ a trailing slash and we are now just
// past it. So we need to transform this case into an exhausted path.
let remaining = match self.remaining {
Some("") | None => None,
Some(remaining) => {
Some(remaining.strip_suffix('/').unwrap_or(remaining))
}
};
Self {
full: self.full.strip_suffix('/').unwrap_or(self.full),
remaining
}
}
pub fn full(&self) -> &str {
self.full
}
pub fn remaining(&self) -> Option<&str> {
self.remaining
}
/// Checks that the path has been exhausted.
///
/// Returns a 404 error response if it isnt.
pub fn check_exhausted(&self) -> Result<(), HttpResponse> {
if self.remaining.is_some() {
Err(HttpResponse::not_found())
}
else {
Ok(())
}
}
/// Parses the next segment as the given type or returns a Not Found.
pub fn parse_next<T: FromStr>(&mut self) -> Result<T, HttpResponse> {
T::from_str(
self.next().ok_or_else(HttpResponse::not_found)?
).map_err(|_| {
HttpResponse::not_found()
})
}
/// Parses the next optional segment.
///
/// Returns `Ok(None)` if we reached the end of the path. Returns a
/// Not Found error response if parsing failed.
pub fn parse_opt_next<T: FromStr>(
&mut self
) -> Result<Option<T>, HttpResponse> {
self.next().map(|s| {
T::from_str(s).map_err(|_| HttpResponse::not_found())
}).transpose()
}
/// Parses the next optional segment, allowing for a trailing slash.
///
/// Returns `Ok(None)` if we reached the end of the path or if there was
/// a trailing slash. Returns a/ Not Found error response if parsing
/// failed.
pub fn parse_opt_next_trailing_slash<T: FromStr>(
&mut self
) -> Result<Option<T>, HttpResponse> {
self.next().map(|s| {
T::from_str(s).map_err(|_| HttpResponse::not_found())
}).transpose()
}
}
impl<'a> Iterator for PathIter<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let remaining = self.remaining?;
let slash = match remaining.find('/') {
Some(pos) => pos,
None => {
let res = remaining;
self.remaining = None;
return Some(res)
}
};
let res = &remaining[..slash];
self.remaining = Some(&remaining[slash + 1..]);
Some(res)
}
}
//------------ BodyLimits ----------------------------------------------------
/// The size limits of a request body.
#[derive(Clone, Copy, Debug)]
pub struct BodyLimits {
/// The POST limit for API data.
post_limit_api: u64,
/// The POST limit for provisioning protocol data.
post_limit_rfc6492: u64,
/// The POST limit for publication protocol data.
post_limit_rfc8181: u64,
}
impl BodyLimits {
/// Creates the limits from the config.
pub fn from_config(config: &Config) -> Self {
Self {
post_limit_api: config.post_limit_api,
post_limit_rfc6492: config.post_limit_rfc6492,
post_limit_rfc8181: config.post_limit_rfc8181,
}
}
}
//------------ InvalidPath ---------------------------------------------------
/// An error happened while preparing the request path.
#[derive(Clone, Copy, Debug)]
pub struct InvalidPath;
impl From<InvalidPath> for ErrorResponse {
fn from(_: InvalidPath) -> Self {
Self::new("invalid-path", "The request path was invalid.")
}
}
impl fmt::Display for InvalidPath {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("invalid request path")
}
}
//============ Tests =========================================================
#[cfg(test)]
mod test {
use super::*;
impl RequestPath {
fn test_str(s: &str) -> Self {
Self {
path: Err(
percent_decode(
s.as_bytes()
).decode_utf8().unwrap().into_owned()
)
}
}
}
#[test]
fn request_path_next() {
let path = RequestPath::test_str("/foo/bar/baz/");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), Some(""));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foo/bar/baz");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foo/b%61%72%2fbaz/");
let mut path = path.iter();
assert_eq!(path.next(), Some("foo"));
assert_eq!(path.next(), Some("bar"));
assert_eq!(path.next(), Some("baz"));
assert_eq!(path.next(), Some(""));
assert_eq!(path.next(), None);
let path = RequestPath::test_str("/foö/bär/baß");
let mut path = path.iter();
assert_eq!(path.next(), Some("foö"));
assert_eq!(path.next(), Some("bär"));
assert_eq!(path.next(), Some("baß"));
assert_eq!(path.next(), None);
}
}
@@ -1,15 +1,13 @@
use std::io;
use bytes::Bytes;
use http_body_util::{Either, Empty, Full};
use hyper::{HeaderMap, StatusCode};
use hyper::header::{HeaderName, HeaderValue};
use log::warn;
use rpki::ca::{provisioning, publication};
use serde::Serialize;
use crate::{
commons::{
error::Error,
},
};
use crate::api::admin::Token;
use crate::api::status::ErrorResponse;
use crate::commons::error::Error;
//----------- ContentType ----------------------------------------------------
@@ -32,8 +30,8 @@ enum ContentType {
Woff2,
}
impl AsRef<str> for ContentType {
fn as_ref(&self) -> &str {
impl ContentType {
fn as_str(&self) -> &'static str {
match self {
ContentType::Cert => "application/x-x509-ca-cert",
ContentType::Json => "application/json",
@@ -63,9 +61,9 @@ pub type HyperResponse = hyper::Response<HyperResponseBody>;
struct Response {
status: StatusCode,
content_type: ContentType,
content_type: &'static str,
max_age: Option<usize>,
body: Vec<u8>,
body: Bytes,
cause: Option<Error>,
}
@@ -73,9 +71,9 @@ impl Response {
fn new(status: StatusCode) -> Self {
Response {
status,
content_type: ContentType::Text,
content_type: ContentType::Text.as_str(),
max_age: None,
body: Vec::new(),
body: Bytes::default(),
cause: None,
}
}
@@ -83,7 +81,7 @@ impl Response {
fn finalize(self) -> HttpResponse {
let mut builder = hyper::Response::builder()
.status(self.status)
.header("Content-Type", self.content_type.as_ref());
.header("Content-Type", self.content_type);
if let Some(max_age) = self.max_age {
builder = builder
@@ -97,7 +95,7 @@ impl Response {
let body = if self.body.is_empty() {
Either::Left(Empty::new())
} else {
Either::Right(Full::new(self.body.into()))
Either::Right(Full::new(self.body))
};
let response = builder.body(body).unwrap();
@@ -115,18 +113,10 @@ impl From<Response> for HttpResponse {
}
}
impl io::Write for Response {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.body.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.body.flush()
}
}
//------------ HttpResponse --------------------------------------------------
#[derive(Debug)]
pub struct HttpResponse {
response: HyperResponse,
cause: Option<Error>,
@@ -148,6 +138,10 @@ impl HttpResponse {
self.response
}
pub fn into_hyper(self) -> HyperResponse {
self.response
}
pub fn loggable(&self) -> bool {
self.loggable
}
@@ -194,27 +188,38 @@ impl HttpResponse {
self.response.headers()
}
fn ok_response(content_type: ContentType, body: Vec<u8>) -> Self {
pub fn ok_with_body(
content_type: &'static str,
body: impl Into<Bytes>
) -> Self {
Response {
status: StatusCode::OK,
content_type,
max_age: None,
body,
body: body.into(),
cause: None,
}
.finalize()
}
fn ok_response(
content_type: ContentType,
body: impl Into<Bytes>
) -> Self {
Self::ok_with_body(content_type.as_str(), body)
}
pub fn json<O: Serialize>(object: &O) -> Self {
match serde_json::to_string(object) {
Ok(json) => {
Self::ok_response(ContentType::Json, json.into_bytes())
Self::ok_response(ContentType::Json, json)
}
Err(e) => Self::response_from_error(Error::JsonError(e)),
}
}
pub fn text(body: Vec<u8>) -> Self {
pub fn text(body: impl Into<Bytes>) -> Self {
Self::ok_response(ContentType::Text, body)
}
@@ -222,7 +227,7 @@ impl HttpResponse {
HttpResponse::new(
hyper::Response::builder()
.status(StatusCode::OK)
.header("Content-Type", ContentType::Text.as_ref())
.header("Content-Type", ContentType::Text.as_str())
.header("Cache-Control", "no-cache")
.body(Either::Right(Full::new(body.into())))
.unwrap(),
@@ -240,23 +245,23 @@ impl HttpResponse {
pub fn xml_with_cache(body: Vec<u8>, seconds: usize) -> Self {
Response {
status: StatusCode::OK,
content_type: ContentType::Xml,
content_type: ContentType::Xml.as_str(),
max_age: Some(seconds),
body,
body: body.into(),
cause: None,
}
.finalize()
}
pub fn rfc8181(body: Vec<u8>) -> Self {
pub fn rfc8181(body: Bytes) -> Self {
Self::ok_response(ContentType::Rfc8181, body)
}
pub fn rfc6492(body: Vec<u8>) -> Self {
pub fn rfc6492(body: Bytes) -> Self {
Self::ok_response(ContentType::Rfc6492, body)
}
pub fn cert(body: Vec<u8>) -> Self {
pub fn cert(body: Bytes) -> Self {
Self::ok_response(ContentType::Cert, body)
}
@@ -288,18 +293,31 @@ impl HttpResponse {
Self::ok_response(ContentType::Woff2, content.to_vec())
}
pub fn error(
status: StatusCode, error: impl Into<ErrorResponse>
) -> Self {
let error = error.into();
let body = serde_json::to_string(&error).unwrap().into();
Response {
status,
content_type: ContentType::Json.as_str(),
max_age: None,
body,
cause: None,
}.finalize()
}
pub fn response_from_error(error: Error) -> Self {
let status = error.status();
let response = error.to_error_response();
let body = serde_json::to_string(&response).unwrap();
let body = serde_json::to_string(&response).unwrap().into();
Response {
status,
content_type: ContentType::Json,
content_type: ContentType::Json.as_str(),
max_age: None,
body: body.into_bytes(),
body,
cause: Some(error),
}
.finalize()
}.finalize()
}
pub fn ok() -> Self {
@@ -327,5 +345,33 @@ impl HttpResponse {
pub fn forbidden(err: String) -> Self {
Self::response_from_error(Error::ApiInsufficientRights(err))
}
pub fn method_not_allowed() -> Self {
Response::new(StatusCode::METHOD_NOT_ALLOWED).finalize()
}
// Suppress any error in the unlikely event that we fail to inject the
// Authorization header into the HTTP response as this is an internal error
// that we should shield the user from, but log a warning as this is very
// unexpected.
pub fn add_authorization_token(
&mut self, token: Token,
) {
let header_name = const { HeaderName::from_static("authorization") };
let header_value = match HeaderValue::from_maybe_shared(
Bytes::from(format!("Bearer {}", &token))
) {
Ok(value) => value,
Err(_) => {
warn!(
"Internal error: unable to add refreshed auth token \
'{token}' to the response."
);
return
}
};
self.response.headers_mut().insert(header_name, header_value);
}
}
+185
View File
@@ -0,0 +1,185 @@
use std::env;
use std::sync::Arc;
use clap::crate_version;
use hyper::StatusCode;
use log::{error, info, warn, trace};
use crate::api::admin::ServerInfo;
use crate::api::ca::Timestamp;
use crate::commons::KrillResult;
use crate::commons::error::FatalError;
use crate::config::Config;
use crate::constants::KRILL_ENV_HTTP_LOG_INFO;
use crate::server::manager::KrillManager;
use super::auth::Authorizer;
use super::dispatch::{DispatchError, dispatch_request};
use super::request::{BodyLimits, HyperRequest, Request};
use super::response::{HyperResponse, HttpResponse};
//------------ HttpServer ----------------------------------------------------
/// The Krill HTTP server.
pub struct HttpServer {
/// The Krill “business logic.”
krill: KrillManager,
/// The component responsible for API authorization checks
authorizer: Authorizer,
/// A copy of the configuration.
config: Arc<Config>,
/// Time this server was started
started: Timestamp,
}
impl HttpServer {
/// Creates a new server from a Krill manager and the configuration.
pub fn new(
krill: KrillManager,
config: Arc<Config>
) -> KrillResult<Arc<Self>> {
Ok(Self {
krill,
authorizer: Authorizer::new(config.clone())?,
config,
started: Timestamp::now(),
}.into())
}
/// Processes an HTTP request.
pub async fn process_request(
&self, request: HyperRequest
) -> Result<HyperResponse, FatalError> {
let logger = RequestLogger::begin(&request);
let (auth, new_token) = self.authorizer.authenticate_request(
&request
).await;
let request = Request::new(
request, self, auth, BodyLimits::from_config(&self.config)
);
let path = match request.path() {
Ok(path) => path,
Err(err) => {
return Ok(
HttpResponse::error(
StatusCode::BAD_REQUEST, err
).into_hyper()
);
}
};
let mut response = match dispatch_request(
request, path.iter(),
).await {
Ok(response) => Ok(response),
Err(DispatchError::Response(response)) => Ok(response),
Err(DispatchError::Fatal(err)) => Err(err),
};
// Augment the response with any updated auth details that were
// determined above.
if let (Ok(response), Some(token)) = (response.as_mut(), new_token) {
response.add_authorization_token(token);
}
logger.end(response.as_ref());
response.map(HttpResponse::into_hyper)
}
}
impl HttpServer {
/// Returns a reference to the Krill manager.
pub(super) fn krill(&self) -> &KrillManager {
&self.krill
}
/// Returns a reference to the authorizer.
pub(super) fn authorizer(&self) -> &Authorizer {
&self.authorizer
}
/// Returns a reference to the configuration.
pub(super) fn config(&self) -> &Config {
&self.config
}
pub(super) fn server_info(&self) -> ServerInfo {
ServerInfo { version: crate_version!().into(), started: self.started }
}
}
//------------ RequestLogger -------------------------------------------------
struct RequestLogger {
req_method: hyper::Method,
req_path: String,
}
impl RequestLogger {
fn begin(req: &HyperRequest) -> Self {
let req_method = req.method().clone();
let req_path = req.uri().path().into();
trace!(
"Request: method={} path={} headers={:?}",
&req_method,
&req_path,
&req.headers()
);
RequestLogger {
req_method,
req_path,
}
}
fn end(&self, res: Result<&HttpResponse, &FatalError>) {
match res {
Ok(response) => {
match (response.status(), response.benign(), response.cause())
{
(s, false, Some(cause)) if s.is_client_error() => {
warn!("HTTP {}: {}", s.as_u16(), cause)
}
(s, false, Some(cause)) if s.is_server_error() => {
error!("HTTP {}: {}", s.as_u16(), cause)
}
_ => {}
}
if env::var(KRILL_ENV_HTTP_LOG_INFO).is_ok() {
info!(
"{} {} {}",
self.req_method,
self.req_path,
response.status()
);
}
if response.loggable() {
trace!(
"{} {} {}",
self.req_method,
self.req_path,
response.status()
);
trace!(
"Response: headers={:?} body={:?}",
response.headers(),
response.body()
);
}
}
Err(err) => {
error!(
"{} {} Fatal error: {}",
self.req_method, self.req_path, err
);
}
}
}
}
+14
View File
@@ -0,0 +1,14 @@
//! Utils for the HTTP service.
//!
//! This is a temporary place to park some things until we find a better home
//! for them.
#[cfg(feature = "multi-user")]
pub fn url_encode<S: AsRef<str>>(
s: S
) -> Result<String, crate::commons::error::Error> {
urlparse::quote(s, b"").map_err(|err| {
crate::commons::error::Error::custom(err.to_string())
})
}
+8
View File
@@ -0,0 +1,8 @@
//! The Krill daemon.
//!
//! This module contains the code actually driving the daemon including
//! processing HTTP requests for the API.
pub mod http;
pub mod start;
+247
View File
@@ -0,0 +1,247 @@
use std::{env, process};
use std::net::SocketAddr;
use std::path::Path;
use std::sync::Arc;
use log::error;
use hyper::service::service_fn;
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::select;
use tokio::net::TcpListener;
use tokio::sync::oneshot;
use tokio_rustls::TlsAcceptor;
use crate::commons::file;
use crate::commons::error::Error;
use crate::config::Config;
use crate::constants::KRILL_ENV_UPGRADE_ONLY;
use crate::server::properties::PropertiesManager;
use crate::server::manager::KrillManager;
use crate::upgrades::{
finalise_data_migration, post_start_upgrade,
prepare_upgrade_data_migrations, UpgradeError, UpgradeMode,
};
use super::http::{tls, tls_keys};
use super::http::server::HttpServer;
pub async fn start_krill_daemon(
config: Arc<Config>,
mut signal_running: Option<oneshot::Sender<()>>,
) -> Result<(), Error> {
write_pid_file_or_die(&config);
test_data_dirs_or_die(&config);
// Set up the runtime properties manager, so that we can check
// the version used for the current data in storage
let properties_manager = PropertiesManager::create(
&config.storage_uri,
config.use_history_cache,
)?;
// Call upgrade, this will only do actual work if needed.
let upgrade_report = prepare_upgrade_data_migrations(
UpgradeMode::PrepareToFinalise, &config, &properties_manager
).map_err(|e| {
match e {
UpgradeError::CodeOlderThanData(_,_) => {
Error::Custom(e.to_string())
},
_ => {
Error::Custom(format!(
"Upgrade data migration failed with error: {}\n\n\
NOTE: your data was not changed. Please downgrade \
your krill instance to your previous version.",
e
))
}
}
})?;
if let Some(report) = &upgrade_report {
finalise_data_migration(
report.versions(), &config, &properties_manager
).map_err(|e| {
Error::Custom(format!(
"Finishing prepared migration failed unexpectedly. Please \
check your data {}. If you find folders named \
'arch-cas-{}' or 'arch-pubd-{}' there, then rename them \
to 'cas' and 'pubd' respectively and re-install krill \
version {}. Underlying error was: {}",
config.storage_uri,
report.versions().from(),
report.versions().from(),
report.versions().from(),
e
))
})?;
}
// Create the Krill manager, this will create the necessary data
// sub-directories if needed
let krill = KrillManager::build(config.clone()).await?;
// Call post-start upgrades to trigger any upgrade related runtime
// actions, such as re-issuing ROAs because subject name strategy has
// changed.
if let Some(report) = upgrade_report {
post_start_upgrade(report, &krill).await?;
}
// If the operator wanted to do the upgrade only, now is a good time to
// report success and stop
if env::var(KRILL_ENV_UPGRADE_ONLY).is_ok() {
println!("Krill upgrade successful");
std::process::exit(0);
}
// Build the scheduler which will be responsible for executing
// planned/triggered tasks
let scheduler = krill.build_scheduler();
let scheduler_future = scheduler.run();
// Create the HTTP server.
let server = HttpServer::new(krill, config.clone())?;
// Create self-signed HTTPS cert if configured and not generated earlier.
if config.https_mode().is_generate_https_cert() {
tls_keys::create_key_cert_if_needed(config.tls_keys_dir())
.map_err(|e| Error::HttpsSetup(format!("{}", e)))?;
}
// Start a hyper server for the configured socket.
let server_futures = futures_util::future::select_all(
config.socket_addresses().into_iter().map(|socket_addr| {
tokio::spawn(single_http_listener(
server.clone(),
socket_addr,
config.clone(),
signal_running.take(),
))
}),
);
select!(
_ = server_futures => error!("http server stopped unexpectedly"),
_ = scheduler_future => error!("scheduler stopped unexpectedly"),
);
Err(Error::custom("stopping krill process"))
}
/// Runs an HTTP listener on a single socket.
async fn single_http_listener(
server: Arc<HttpServer>,
addr: SocketAddr,
config: Arc<Config>,
signal_running: Option<oneshot::Sender<()>>,
) {
let listener = match TcpListener::bind(addr).await {
Ok(listener) => listener,
Err(err) => {
error!("Could not bind to {}: {}", addr, err);
return;
}
};
let tls = if config.https_mode().is_disable_https() {
None
} else {
match tls::create_server_config(
&tls_keys::key_file_path(config.tls_keys_dir()),
&tls_keys::cert_file_path(config.tls_keys_dir()),
) {
Ok(config) => Some(TlsAcceptor::from(Arc::new(config))),
Err(err) => {
error!("{}", err);
return;
}
}
};
if let Some(tx) = signal_running {
let _ = tx.send(());
}
loop {
let stream = match listener.accept().await {
Ok((stream, _addr)) => {
tls::MaybeTlsTcpStream::new(stream, tls.as_ref())
}
Err(err) => {
error!("Fatal error in HTTP server {}: {}", addr, err);
return;
}
};
let server = server.clone();
tokio::task::spawn(async move {
let _ = hyper_util::server::conn::auto::Builder::new(
TokioExecutor::new(),
)
.serve_connection(
TokioIo::new(stream),
service_fn(move |req| {
let server = server.clone();
async move { server.process_request(req).await }
}),
)
.await;
});
}
}
fn write_pid_file_or_die(config: &Config) {
if let Err(e) = file::save(
process::id().to_string().as_bytes(), config.pid_file()
) {
print_write_error_hint_and_die(format!(
"Could not write PID file: {}",
e
));
}
}
fn test_data_dirs_or_die(config: &Config) {
test_data_dir_or_die("tls_keys_dir", config.tls_keys_dir());
test_data_dir_or_die("repo_dir", config.repo_dir());
if let Some(rfc8181_log_dir) = &config.rfc8181_log_dir {
test_data_dir_or_die("rfc8181_log_dir", rfc8181_log_dir);
}
if let Some(rfc6492_log_dir) = &config.rfc6492_log_dir {
test_data_dir_or_die("rfc6492_log_dir", rfc6492_log_dir);
}
}
fn test_data_dir_or_die(config_item: &str, dir: &Path) {
let test_file = dir.join("test");
if let Err(e) = file::save(b"test", &test_file) {
print_write_error_hint_and_die(format!(
"Cannot write to dir '{}' for configuration setting '{}', \
Error: {}",
dir.to_string_lossy(),
config_item,
e
));
}
else if let Err(e) = file::delete_file(&test_file) {
print_write_error_hint_and_die(format!(
"Cannot delete test file '{}' in dir for configuration setting \
'{}', Error: {}",
test_file.to_string_lossy(),
config_item,
e
));
}
}
fn print_write_error_hint_and_die(error_msg: String) {
eprintln!("{}", error_msg);
eprintln!();
eprintln!("Hint: if you use systemd you may need to override the allowed");
eprintln!("ReadWritePaths, the easiest way may be by doing ");
eprintln!("'systemctl edit krill' and add a section like:");
eprintln!();
eprintln!("[Service]");
eprintln!("ReadWritePaths=/local/path1 /local/path2 ...");
}
+2
View File
@@ -3,7 +3,9 @@
pub mod api;
pub mod cli;
pub mod commons;
pub mod config;
pub mod constants;
pub mod daemon;
pub mod server;
pub mod tasigner;
pub mod upgrades;
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::api::ca::ObjectName;
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use crate::server::config::{Config, IssuanceTimingConfig};
use crate::config::{Config, IssuanceTimingConfig};
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;
+1 -1
View File
@@ -19,7 +19,7 @@ use crate::api::ca::ObjectName;
use crate::commons::KrillResult;
use crate::commons::error::Error;
use crate::commons::crypto::KrillSigner;
use crate::server::config::{Config, IssuanceTimingConfig};
use crate::config::{Config, IssuanceTimingConfig};
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;
+2 -2
View File
@@ -47,7 +47,7 @@ use crate::commons::crypto::{CsrInfo, KrillSigner};
use crate::commons::error::Error;
use crate::commons::eventsourcing::Aggregate;
use crate::constants::test_mode_enabled;
use crate::server::config::{Config, IssuanceTimingConfig};
use crate::config::{Config, IssuanceTimingConfig};
use super::aspa::AspaDefinitions;
use super::bgpsec::BgpSecDefinitions;
use super::child::{ChildDetails, ChildCertificateUpdates, UsedKeyState};
@@ -2744,7 +2744,7 @@ impl Rfc8183Id {
mod tests {
use crate::commons::crypto::KrillSignerBuilder;
use crate::commons::test;
use crate::server::config::ConfigDefaults;
use crate::config::ConfigDefaults;
use std::time::Duration;
use super::*;
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::api::ca::{
use crate::commons::KrillResult;
use crate::commons::crypto::{KrillSigner, SignSupport};
use crate::commons::error::Error;
use crate::server::config::IssuanceTimingConfig;
use crate::config::IssuanceTimingConfig;
//------------ UsedKeyState --------------------------------------------------
+1 -1
View File
@@ -34,7 +34,7 @@ use crate::commons::eventsourcing::{
self, InitCommandDetails, SentCommand, SentInitCommand,
WithStorableDetails,
};
use crate::server::config::Config;
use crate::config::Config;
use super::events::CertAuthEvent;
use super::rc::DropReason;
+12 -26
View File
@@ -58,8 +58,8 @@ use crate::constants::{
CASERVER_NS, STATUS_NS, TA_PROXY_SERVER_NS, TA_SIGNER_SERVER_NS, TA_NAME,
ta_handle,
};
use crate::server::http::auth::{AuthInfo, Permission}; // XXX remove
use crate::server::config::Config;
use crate::daemon::http::auth::{AuthInfo, Permission}; // XXX remove
use crate::config::Config;
use crate::server::mq::{now, Task, TaskQueue};
use crate::server::pubd::RepositoryManager;
use crate::server::taproxy::{
@@ -77,18 +77,6 @@ use super::publishing::{CaObjectsStore, DeprecatedRepository};
use super::status::{CaStatus, CaStatusStore};
//------------ testbed_ca_handle ---------------------------------------------
/// The handle of the CA used by the testbed.
pub const TESTBED_CA_NAME: &str = "testbed";
/// Returns the CA handle for the testbed.
pub fn testbed_ca_handle() -> CaHandle {
use std::str::FromStr;
CaHandle::from_str(TESTBED_CA_NAME).unwrap()
}
//------------ CaManager -----------------------------------------------------
/// Manages access to all CAs.
@@ -304,7 +292,7 @@ impl CaManager {
/// those objects that are close to expiring.
///
/// Returns all CAs for which objects were republished.
pub async fn republish_all(
pub fn republish_all(
&self,
force: bool,
) -> KrillResult<Vec<CaHandle>> {
@@ -657,7 +645,7 @@ impl CaManager {
actor: &Actor,
) -> KrillResult<()> {
self.process_ca_command(
handle.clone(), actor,
handle, actor,
CertAuthCommandDetails::GenerateNewIdKey(
self.signer.clone(),
)
@@ -671,8 +659,6 @@ impl CaManager {
}
/// Returns the CAs that the given policy allows read access to.
//
// XXX This should probably not live here but in krillserver.
pub fn ca_list(
&self, auth: &AuthInfo,
) -> KrillResult<CertAuthList> {
@@ -2531,10 +2517,10 @@ impl CaManager {
impl CaManager {
/// Schedules synchronizing all CAs with their repositories.
pub fn cas_schedule_repo_sync_all(
&self, auth: &AuthInfo,
&self,
) -> KrillResult<()> {
for ca in &self.ca_list(auth)?.cas {
self.cas_schedule_repo_sync(ca.handle.clone())?;
for ca in self.ca_handles()? {
self.cas_schedule_repo_sync(ca)?;
}
Ok(())
}
@@ -3011,9 +2997,9 @@ impl CaManager {
/// Returns the current ASPA definitions for this CA.
pub fn ca_aspas_definitions_show(
&self,
ca: CaHandle,
ca: &CaHandle,
) -> KrillResult<AspaDefinitionList> {
Ok(self.get_ca(&ca)?.aspas_definitions_show())
Ok(self.get_ca(ca)?.aspas_definitions_show())
}
/// Adds a new ASPA definition for this CA.
@@ -3024,7 +3010,7 @@ impl CaManager {
actor: &Actor,
) -> KrillResult<()> {
self.process_ca_command(
ca.clone(), actor,
ca, actor,
CertAuthCommandDetails::AspasUpdate(
updates,
self.config.clone(),
@@ -3060,9 +3046,9 @@ impl CaManager {
/// Returns the BGPsec definitions for a CA.
pub fn ca_bgpsec_definitions_show(
&self,
ca: CaHandle,
ca: &CaHandle,
) -> KrillResult<BgpSecCsrInfoList> {
Ok(self.get_ca(&ca)?.bgpsec_definitions_show())
Ok(self.get_ca(ca)?.bgpsec_definitions_show())
}
/// Updates the BGPsec definitions for a CA.
-1
View File
@@ -16,7 +16,6 @@ mod status;
pub mod upgrades;
pub use self::manager::CaManager;
pub use self::manager::testbed_ca_handle;
pub use self::status::CaStatusStore;
pub use self::status::CaStatus;
+1 -1
View File
@@ -28,7 +28,7 @@ use crate::commons::error::Error;
use crate::commons::eventsourcing::PreSaveEventListener;
use crate::commons::storage::{Key, KeyValueStore, Scope, Segment};
use crate::constants::CA_OBJECTS_NS;
use crate::server::config::IssuanceTimingConfig;
use crate::config::IssuanceTimingConfig;
use super::aspa::{AspaInfo, AspaObjectsUpdates};
use super::bgpsec::{BgpSecCertInfo, BgpSecCertificateUpdates};
use super::certauth::CertAuth;
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::api::roa::{RoaConfiguration, RoaInfo};
use crate::commons::KrillResult;
use crate::commons::crypto::{CsrInfo, KrillSigner, SignSupport};
use crate::commons::error::{Error, KrillError};
use crate::server::config::{Config, IssuanceTimingConfig};
use crate::config::{Config, IssuanceTimingConfig};
use super::aspa::{AspaDefinitions, AspaObjects, AspaObjectsUpdates};
use super::bgpsec::{
BgpSecCertificates, BgpSecCertificateUpdates, BgpSecDefinitions
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::api::roa::{
use crate::commons::KrillResult;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::{Error, RoaDeltaError};
use crate::server::config::{Config, IssuanceTimingConfig};
use crate::config::{Config, IssuanceTimingConfig};
use super::events::CertAuthEvent;
use super::keys::CertifiedKey;
+10
View File
@@ -569,11 +569,21 @@ impl CaStatus {
&self.repo
}
/// Converts the status into the repository status.
pub fn into_repo(self) -> RepoStatus {
self.repo
}
/// Returns a reference to the parent statuses.
pub fn parents(&self) -> &ParentStatuses {
&self.parents
}
/// Converts the status into the parent statuses.
pub fn into_parents(self) -> ParentStatuses {
self.parents
}
/// Returns a reference to the child statuses.
pub fn children(&self) -> &HashMap<ChildHandle, ChildStatus> {
&self.children
+1 -1
View File
@@ -4,7 +4,7 @@ use crate::commons::crypto::KrillSignerBuilder;
use crate::constants::CASERVER_NS;
use crate::server::ca::certauth::CertAuth;
use crate::server::ca::publishing::CaObjectsStore;
use crate::server::config::Config;
use crate::config::Config;
use crate::upgrades::UpgradeResult;
use crate::upgrades::data_migration::check_agg_store;
@@ -13,7 +13,7 @@ use crate::server::ca::certauth::CertAuth;
use crate::server::ca::commands::CertAuthStorableCommand;
use crate::server::ca::events::{CertAuthEvent, CertAuthInitEvent};
use crate::server::ca::publishing::CaObjects;
use crate::server::config::Config;
use crate::config::Config;
use crate::upgrades::{
AspaMigrationConfigUpdates, AspaMigrationConfigs, CommandMigrationEffect,
UpgradeAggregateStorePre0_14, UpgradeError, UpgradeMode, UpgradeResult,
@@ -136,7 +136,7 @@ impl TryFrom<OldTaCertDetails> for TaCertDetails {
let tal = TrustAnchorLocator::new(tal.uris, rsync_uri, &public_key);
Ok(TaCertDetails::new(rvcd_cert, tal))
Ok(TaCertDetails { cert: rvcd_cert, tal })
}
}
@@ -13,9 +13,7 @@ use crate::{
storage::KeyValueStore,
},
constants::CASERVER_NS,
server::{
config::Config,
},
config::Config,
upgrades::UpgradeResult,
};
-12
View File
@@ -1,12 +0,0 @@
pub mod auth;
pub mod metrics;
pub mod request;
pub mod response;
pub mod server;
pub mod statics;
pub mod testbed;
pub mod tls;
pub mod tls_keys;
-267
View File
@@ -1,267 +0,0 @@
use std::str::FromStr;
use std::str::from_utf8;
use bytes::Bytes;
use http_body_util::{BodyExt, Limited};
use hyper::{HeaderMap, Method};
use hyper::body::Body;
use hyper::header::USER_AGENT;
use hyper::http::uri::PathAndQuery;
use log::info;
use rpki::ca::idexchange::MyHandle;
use serde::de::DeserializeOwned;
use crate::commons::KrillResult;
use crate::commons::actor::Actor;
use crate::commons::error::{ApiAuthError, Error};
use crate::constants::HTTP_USER_AGENT_TRUNCATE;
use super::auth::{AuthInfo, LoggedInUser, Permission};
use super::response::HttpResponse;
use super::server::State;
//------------ HyperRequest --------------------------------------------------
/// A type alias for the request we receive from Hyper.
pub type HyperRequest = hyper::Request<hyper::body::Incoming>;
//------------ Request -------------------------------------------------------
pub struct Request {
request: HyperRequest,
path: RequestPath,
state: State,
auth: AuthInfo,
}
impl Request {
pub async fn new(request: HyperRequest, state: State) -> Self {
let path = RequestPath::from_request(&request);
let auth = state.authenticate_request(&request).await;
Request {
request,
path,
state,
auth,
}
}
pub fn headers(&self) -> &HeaderMap {
self.request.headers()
}
pub fn user_agent(&self) -> Option<String> {
match self.headers().get(&USER_AGENT) {
None => None,
Some(value) => value.to_str().ok().map(|s| {
// Note: HeaderValue.to_str() only returns ok in case the
// value is plain ascii so it's safe to
// treat bytes as characters here.
if s.len() > HTTP_USER_AGENT_TRUNCATE {
s[..HTTP_USER_AGENT_TRUNCATE].to_string()
} else {
s.to_string()
}
}),
}
}
pub async fn upgrade_from_anonymous(&mut self, auth: AuthInfo) {
if self.auth.actor().is_anonymous() {
self.auth = auth;
info!(
"Permitted anonymous actor to become actor '{}' \
for the duration of this request",
self.auth.actor().name()
);
}
}
pub fn check_permission(
&self,
permission: Permission,
resource: Option<&MyHandle>
) -> Result<(), ApiAuthError> {
self.auth.check_permission(permission, resource)
}
pub fn actor(&self) -> Actor {
self.auth.actor().clone()
}
pub fn auth_info(&self) -> &AuthInfo {
&self.auth
}
pub fn auth_info_mut(&mut self) -> &mut AuthInfo {
&mut self.auth
}
pub fn request(&self) -> &HyperRequest {
&self.request
}
/// Returns the complete path.
pub fn path(&self) -> &RequestPath {
&self.path
}
/// Get the application State
pub fn state(&self) -> &State {
&self.state
}
/// Returns the method of this request.
pub fn method(&self) -> &Method {
self.request.method()
}
/// Returns whether the request is a GET request.
pub fn is_get(&self) -> bool {
self.request.method() == Method::GET
}
/// Returns whether the request is a GET request.
pub fn is_post(&self) -> bool {
self.request.method() == Method::POST
}
/// Returns whether the request is a DELETE request.
pub fn is_delete(&self) -> bool {
self.request.method() == Method::DELETE
}
/// Get a json object from a post body
pub async fn json<O: DeserializeOwned>(self) -> Result<O, Error> {
let bytes = self.api_bytes().await?;
let string =
from_utf8(&bytes).map_err(|_| Error::InvalidUtf8Input)?;
serde_json::from_str(string).map_err(Error::JsonError)
}
pub async fn api_bytes(self) -> Result<Bytes, Error> {
let limit = self.state().config.post_limit_api;
self.read_bytes(limit).await
}
pub async fn rfc6492_bytes(self) -> Result<Bytes, Error> {
let limit = self.state().config.post_limit_rfc6492;
self.read_bytes(limit).await
}
pub async fn rfc8181_bytes(self) -> Result<Bytes, Error> {
let limit = self.state().config.post_limit_rfc8181;
self.read_bytes(limit).await
}
pub async fn read_bytes(self, limit: u64) -> Result<Bytes, Error> {
// Were going to cheat a bit. If we know the body is too big from
// the Content-Length header, we return Error::PostTooBig. But if
// we dont -- which means there are multiple chunks or somesuch --
// we just use http_body_utils::Limited and return PostCannotRead
// on any error.
if self.request.body().size_hint().lower() > limit {
return Err(Error::PostTooBig);
}
Ok(Limited::new(
self.request.into_body(),
limit.try_into().unwrap_or(usize::MAX),
)
.collect()
.await
.map_err(|_| Error::PostCannotRead)?
.to_bytes())
}
pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
self.state.get_login_url().await
}
pub async fn login(&self) -> KrillResult<LoggedInUser> {
self.state.login(&self.request).await
}
pub async fn logout(&self) -> KrillResult<HttpResponse> {
self.state.logout(&self.request).await
}
}
//------------ RequestPath ---------------------------------------------------
#[derive(Clone, Debug)]
pub struct RequestPath {
path: PathAndQuery,
segment: (usize, usize),
}
impl std::fmt::Display for RequestPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.full())
}
}
impl RequestPath {
pub fn from_request<B>(request: &hyper::Request<B>) -> Self {
let path = request.uri().path_and_query().unwrap().clone();
let mut res = RequestPath {
path,
segment: (0, 0),
};
res.next_segment();
res
}
pub fn full(&self) -> &str {
self.path.path()
}
pub fn remaining(&self) -> &str {
&self.full()[self.segment.1..]
}
pub fn segment(&self) -> &str {
&self.full()[self.segment.0..self.segment.1]
}
fn next_segment(&mut self) -> bool {
let mut start = self.segment.1;
let path = self.full();
// Start beyond the length of the path signals the end.
if start >= path.len() {
return false;
}
// Skip any leading slashes. There may be multiple which should be
// folded into one (or at least thats what we do).
while path.split_at(start).1.starts_with('/') {
start += 1
}
// Find the next slash. If we have one, thats the end of
// our segment, otherwise, we go all the way to the end of the path.
let end = path[start..]
.find('/')
.map(|x| x + start)
.unwrap_or_else(|| path.len());
self.segment = (start, end);
true
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<&str> {
if self.next_segment() {
Some(self.segment())
} else {
None
}
}
pub fn path_arg<T>(&mut self) -> Option<T>
where
T: FromStr,
{
self.next().and_then(|s| T::from_str(s).ok())
}
}
File diff suppressed because it is too large Load Diff
-181
View File
@@ -1,181 +0,0 @@
use http_body_util::{Either, Empty};
use hyper::{Method, StatusCode};
use crate::server::http::request::Request;
use crate::server::http::response::HttpResponse;
pub async fn statics(req: Request) -> Result<HttpResponse, Request> {
let res = match *req.method() {
Method::GET => match req.path().full() {
"/" => Ok(HttpResponse::new(
hyper::Response::builder()
.status(StatusCode::FOUND)
.header("location", "/ui")
.body(Either::Left(Empty::new()))
.unwrap(),
)),
"/ui" => Ok(HttpResponse::html(INDEX)),
"/assets/favicon-f84116cb.ico" => Ok(HttpResponse::fav(FAVICON)),
"/assets/index-16c05fa1.js" => Ok(HttpResponse::js(JS_INDEX)),
"/assets/en-d3d88bc8.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_ENGLISH))
}
"/assets/de-faf2935a.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_GERMAN))
}
"/assets/es-52cbfc21.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_SPANISH))
}
"/assets/fr-ac1aafd8.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_FRENCH))
}
"/assets/gr-5a66c94a.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_GREEK))
}
"/assets/nl-f2dd1189.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_DUTCH))
}
"/assets/pt-e9bf4047.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_PORTUGUESE))
}
"/assets/zh-Hans-f7c709f7.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_SIMPLIFIED_CHINESE))
}
"/assets/zh-Hant-0d86c694.js" => {
Ok(HttpResponse::js(JS_TRANSLATIONS_TRADITIONAL_CHINESE))
}
"/assets/index-3c0611ee.css" => Ok(HttpResponse::css(CSS)),
"/assets/check-3e734f78.svg" => Ok(HttpResponse::svg(SVG_CHECK)),
"/assets/check-green-4525c79c.svg" => {
Ok(HttpResponse::svg(SVG_CHECK_GREEN))
}
"/assets/clipboard-4659ffea.svg" => {
Ok(HttpResponse::svg(SVG_CLIPBOARD))
}
"/assets/download-2dfead4c.svg" => {
Ok(HttpResponse::svg(SVG_DOWNLOAD))
}
"/assets/edit-776bf3c3.svg" => Ok(HttpResponse::svg(SVG_EDIT)),
"/assets/error-fd1fc7e1.svg" => Ok(HttpResponse::svg(SVG_ERROR)),
"/assets/krill_logo_white-05224433.svg" => {
Ok(HttpResponse::svg(SVG_KRILL_LOGO))
}
"/assets/logout-c725fd2c.svg" => {
Ok(HttpResponse::svg(SVG_LOGOUT))
}
"/assets/plus-e8f1d182.svg" => Ok(HttpResponse::svg(SVG_PLUS)),
"/assets/route-left-c88b44cb.svg" => {
Ok(HttpResponse::svg(SVG_ROUTE_LEFT))
}
"/assets/route-right-17b0c46a.svg" => {
Ok(HttpResponse::svg(SVG_ROUTE_RIGHT))
}
"/assets/search-4a30d812.svg" => {
Ok(HttpResponse::svg(SVG_SEARCH))
}
"/assets/trash-red-65027383.svg" => {
Ok(HttpResponse::svg(SVG_TRASH_RED))
}
"/assets/trash-d9c6ee55.svg" => Ok(HttpResponse::svg(SVG_TRASH)),
"/assets/upload-87e6fdfd.svg" => {
Ok(HttpResponse::svg(SVG_UPLOAD))
}
"/assets/user-5d1f1b14.svg" => Ok(HttpResponse::svg(SVG_USER)),
"/assets/welcome-9fadc7f2.svg" => {
Ok(HttpResponse::svg(SVG_WELCOME))
}
"/assets/Inter-italic.var-d1401419.woff2" => {
Ok(HttpResponse::woff2(FONTS_ITALIC))
}
"/assets/Inter-roman.var-17fe38ab.woff2" => {
Ok(HttpResponse::woff2(FONTS_ROMAN))
}
_ => Err(req),
},
_ => Err(req),
};
// Do not log static responses even at TRACE level because by definition
// static responses are often of little diagnostic value and their large
// size makes it harder to see other potentially more useful log messages.
res.map(|mut res| {
res.do_not_log();
res
})
}
pub static INDEX: &[u8] = include_bytes!("../../../ui/index.html");
static FAVICON: &[u8] =
include_bytes!("../../../ui/assets/favicon-f84116cb.ico");
static JS_INDEX: &[u8] =
include_bytes!("../../../ui/assets/index-16c05fa1.js");
static JS_TRANSLATIONS_GERMAN: &[u8] =
include_bytes!("../../../ui/assets/de-faf2935a.js");
static JS_TRANSLATIONS_ENGLISH: &[u8] =
include_bytes!("../../../ui/assets/en-d3d88bc8.js");
static JS_TRANSLATIONS_SPANISH: &[u8] =
include_bytes!("../../../ui/assets/es-52cbfc21.js");
static JS_TRANSLATIONS_FRENCH: &[u8] =
include_bytes!("../../../ui/assets/fr-ac1aafd8.js");
static JS_TRANSLATIONS_GREEK: &[u8] =
include_bytes!("../../../ui/assets/gr-5a66c94a.js");
static JS_TRANSLATIONS_DUTCH: &[u8] =
include_bytes!("../../../ui/assets/nl-f2dd1189.js");
static JS_TRANSLATIONS_PORTUGUESE: &[u8] =
include_bytes!("../../../ui/assets/pt-e9bf4047.js");
static JS_TRANSLATIONS_SIMPLIFIED_CHINESE: &[u8] =
include_bytes!("../../../ui/assets/zh-Hans-f7c709f7.js");
static JS_TRANSLATIONS_TRADITIONAL_CHINESE: &[u8] =
include_bytes!("../../../ui/assets/zh-Hant-0d86c694.js");
static CSS: &[u8] = include_bytes!("../../../ui/assets/index-3c0611ee.css");
static SVG_CHECK: &[u8] =
include_bytes!("../../../ui/assets/check-3e734f78.svg");
static SVG_CHECK_GREEN: &[u8] =
include_bytes!("../../../ui/assets/check-green-4525c79c.svg");
static SVG_CLIPBOARD: &[u8] =
include_bytes!("../../../ui/assets/clipboard-4659ffea.svg");
static SVG_DOWNLOAD: &[u8] =
include_bytes!("../../../ui/assets/download-2dfead4c.svg");
static SVG_EDIT: &[u8] =
include_bytes!("../../../ui/assets/edit-776bf3c3.svg");
static SVG_ERROR: &[u8] =
include_bytes!("../../../ui/assets/error-fd1fc7e1.svg");
static SVG_KRILL_LOGO: &[u8] =
include_bytes!("../../../ui/assets/krill_logo_white-05224433.svg");
static SVG_LOGOUT: &[u8] =
include_bytes!("../../../ui/assets/logout-c725fd2c.svg");
static SVG_PLUS: &[u8] =
include_bytes!("../../../ui/assets/plus-e8f1d182.svg");
static SVG_ROUTE_LEFT: &[u8] =
include_bytes!("../../../ui/assets/route-left-c88b44cb.svg");
static SVG_ROUTE_RIGHT: &[u8] =
include_bytes!("../../../ui/assets/route-right-17b0c46a.svg");
static SVG_SEARCH: &[u8] =
include_bytes!("../../../ui/assets/search-4a30d812.svg");
static SVG_TRASH_RED: &[u8] =
include_bytes!("../../../ui/assets/trash-red-65027383.svg");
static SVG_TRASH: &[u8] =
include_bytes!("../../../ui/assets/trash-d9c6ee55.svg");
static SVG_UPLOAD: &[u8] =
include_bytes!("../../../ui/assets/upload-87e6fdfd.svg");
static SVG_USER: &[u8] =
include_bytes!("../../../ui/assets/user-5d1f1b14.svg");
static SVG_WELCOME: &[u8] =
include_bytes!("../../../ui/assets/welcome-9fadc7f2.svg");
static FONTS_ITALIC: &[u8] =
include_bytes!("../../../ui/assets/Inter-italic.var-d1401419.woff2");
static FONTS_ROMAN: &[u8] =
include_bytes!("../../../ui/assets/Inter-roman.var-17fe38ab.woff2");
-142
View File
@@ -1,142 +0,0 @@
use hyper::Method;
use rpki::ca::idexchange::PublisherHandle;
use crate::{
constants::ta_handle,
server::{
ca::testbed_ca_handle,
http::{
server::{
api_add_pbl, api_ca_add_child, api_ca_child_remove,
api_ca_parent_res_xml, api_remove_pbl,
api_repository_response_xml, render_ok,
render_unknown_method,
},
},
http::auth::AuthInfo,
http::request::{Request, RequestPath},
http::response::HttpResponse,
},
};
//------------ Support acting as a testbed
//------------ -------------------------------------
//
// Testbed mode enables Krill to run as an open root of a test RPKI hierarchy
// with web-UI based self-service ability for other RPKI certificate
// authorities to integrate themselves into the test RPKI hierarchy, both as
// children whose resources are delegated from the testbed and as publishers
// into the testbed repository. This feature is very similar to existing
// web-UI based self-service RPKI test hierarchies such as the RIPE NCC RPKI
// Test Environment and the APNIC RPKI Testbed.
//
// Krill can already do this via a combination of use_ta=true and the existing
// Krill API _but_ crucially the other RPKI certificate authorities would need
// to know the Krill API token in order to register themselves with the Krill
// testbed, giving them far too much power over the testbed. Testbed mode
// exposes *open* /testbed/xxx wrapper API endpoints for exchanging the RFC
// 8183 XMLs, e.g.:
//
// /testbed/enabled: should the web-UI show the testbed UI page?
// /testbed/children: <client_request/> in, <parent_response/> out
// /testbed/publishers: <publisher_request/> in, <repository_response/> out
//
// This feature assumes the existence of a built-in "testbed" CA and publisher
// when testbed mode is enabled.
pub async fn testbed(mut req: Request) -> Result<HttpResponse, Request> {
if !req.path().full().starts_with("/testbed") {
Err(req) // Not for us
} else if !req.state().testbed_enabled() {
render_unknown_method()
} else {
// The testbed is intended to be used without being logged in but
// anonymous users don't have the necessary rights to manipulate
// Krill CAs and publishers. Upgrade anonymous users with testbed
// rights ready for the next call in the chain to the testbed()
// API call handler functions.
req.upgrade_from_anonymous(AuthInfo::testbed()).await;
let mut path = req.path().clone();
match path.next() {
Some("enabled") => testbed_enabled(req).await,
Some("children") => testbed_children(req, &mut path).await,
Some("publishers") => testbed_publishers(req, &mut path).await,
_ => render_unknown_method(),
}
}
}
// Is the testbed feature enabled or not? used by the web-UI to conditionally
// enable the testbed web-UI.
async fn testbed_enabled(req: Request) -> Result<HttpResponse, Request> {
match *req.method() {
Method::GET => render_ok(),
_ => render_unknown_method(),
}
}
// Open (token-less) addition/removal of child CAs under the testbed CA.
// Note: Anyone can request any resources irrespective of the resources they
// have the rights to in the real global RPKI hierarchy and anyone can
// un-register any child CA even if not "owned" by them.
async fn testbed_children(
req: Request,
path: &mut RequestPath,
) -> Result<HttpResponse, Request> {
match (req.method().clone(), path.path_arg()) {
(Method::GET, Some(child)) => match path.next() {
Some("parent_response.xml") => {
api_ca_parent_res_xml(req, testbed_ca_handle(), child).await
}
_ => render_unknown_method(),
},
(Method::DELETE, Some(child)) => {
api_ca_child_remove(req, testbed_ca_handle(), child).await
}
(Method::POST, None) => {
api_ca_add_child(req, testbed_ca_handle()).await
}
_ => render_unknown_method(),
}
}
// Open (token-less) addition/removal of publishers to the testbed repository.
// Note: Anyone can become a publisher and anyone can un-register a publisher
// even if not "owned" by them.
async fn testbed_publishers(
req: Request,
path: &mut RequestPath,
) -> Result<HttpResponse, Request> {
match (req.method().clone(), path.path_arg()) {
(Method::GET, Some(publisher)) => match path.next() {
Some("response.xml") => {
api_repository_response_xml(req, publisher).await
}
_ => render_unknown_method(),
},
(Method::DELETE, Some(publisher)) => {
testbed_remove_pbl(req, publisher).await
}
(Method::POST, None) => api_add_pbl(req).await,
_ => render_unknown_method(),
}
}
// Prevent deletion of the built-in TA and testbed repositories.
async fn testbed_remove_pbl(
req: Request,
publisher: PublisherHandle,
) -> Result<HttpResponse, Request> {
if publisher.as_str() == ta_handle().as_str()
|| publisher.as_str() == testbed_ca_handle().as_str()
{
Ok(HttpResponse::forbidden(format!(
"Publisher '{}' cannot be removed",
publisher
)))
} else {
api_remove_pbl(req, publisher).await
}
}
@@ -18,7 +18,7 @@ use rpki::{
uri,
};
use crate::server::http::auth::AuthInfo;
use crate::daemon::http::auth::AuthInfo;
use crate::{
commons::{
actor::Actor,
@@ -27,14 +27,11 @@ use crate::{
KrillEmptyResult, KrillResult,
},
constants::*,
config::Config,
server::{
ca::{
self, testbed_ca_handle, CaManager, CaStatus,
self, CaManager, CaStatus,
},
config::Config,
http::auth::{Authorizer, LoggedInUser},
http::request::HyperRequest,
http::response::HttpResponse,
mq::{now, Task, TaskQueue},
pubd::RepositoryManager,
scheduler::Scheduler,
@@ -44,7 +41,7 @@ use crate::api;
use crate::api::admin::{
AddChildRequest, CertAuthInit, ParentCaContact, ParentCaReq,
PublicationServerUris, PublisherDetails, RepoFileDeleteCriteria,
RepositoryContact, ServerInfo, UpdateChildRequest,
RepositoryContact, UpdateChildRequest,
};
use crate::api::aspa::{
AspaDefinitionList, AspaDefinitionUpdates, AspaProvidersUpdate,
@@ -53,10 +50,10 @@ use crate::api::aspa::{
use crate::api::bgp::{BgpAnalysisReport, BgpAnalysisSuggestion};
use crate::api::bgpsec::{BgpSecCsrInfoList, BgpSecDefinitionUpdates};
use crate::api::ca::{
AllCertAuthIssues, CaRepoDetails, CertAuthInfo, CertAuthIssues,
CaRepoDetails, CertAuthInfo, CertAuthIssues,
CertAuthList, CertAuthStats, ChildCaInfo, ChildrenConnectionStats,
IdCertInfo, ReceivedCert, RtaList, RtaName,
RtaPrepResponse, Timestamp,
IdCertInfo, RtaList, RtaName,
RtaPrepResponse,
};
use crate::api::history::{
CommandDetails, CommandHistory, CommandHistoryCriteria
@@ -77,17 +74,14 @@ use crate::constants::{TA_NAME, ta_handle};
use crate::server::bgp::BgpAnalyser;
//------------ KrillServer ---------------------------------------------------
//------------ KrillManager ---------------------------------------------------
/// This is the Krill server that is doing all the orchestration for all
/// components.
pub struct KrillServer {
pub struct KrillManager {
// The base URI for this service
service_uri: uri::Https,
// Component responsible for API authorization checks
authorizer: Arc<Authorizer>,
// Publication server, with configured publishers
repo_manager: Arc<RepositoryManager>,
@@ -100,9 +94,6 @@ pub struct KrillServer {
// Shared message queue
mq: Arc<TaskQueue>,
// Time this server was started
started: Timestamp,
// System actor
system_actor: Actor,
@@ -110,7 +101,7 @@ pub struct KrillServer {
}
/// # Set up and initialization
impl KrillServer {
impl KrillManager {
/// Creates a new publication server. Note that state is preserved
/// in the data storage.
pub async fn build(config: Arc<Config>) -> KrillResult<Self> {
@@ -135,9 +126,6 @@ impl KrillServer {
.build()?;
let signer = Arc::new(signer);
let authorizer = Arc::new(Authorizer::new(config.clone())?);
authorizer.spawn_sweep(&tokio::runtime::Handle::current());
let system_actor = ACTOR_DEF_KRILL;
// Task queue Arc is shared between ca_manager, repo_manager and the
@@ -175,14 +163,12 @@ impl KrillServer {
mq.schedule(Task::QueueStartTasks, now())?;
let server = KrillServer {
let server = KrillManager {
service_uri,
authorizer,
repo_manager,
ca_manager,
bgp_analyser,
mq,
started: Timestamp::now(),
system_actor,
config: config.clone(),
};
@@ -299,61 +285,40 @@ impl KrillServer {
pub fn service_base_uri(&self) -> &uri::Https {
&self.service_uri
}
pub fn server_info(&self) -> ServerInfo {
ServerInfo { version: crate_version!().into(), started: self.started }
}
}
/// # Authentication and Access
impl KrillServer {
/// # Access to components
impl KrillManager {
pub fn system_actor(&self) -> &Actor {
&self.system_actor
}
pub async fn authenticate_request(
&self, request: &HyperRequest
) -> AuthInfo {
self.authorizer.authenticate_request(request).await
}
pub async fn get_login_url(&self) -> KrillResult<HttpResponse> {
self.authorizer.get_login_url().await
}
pub async fn login(
&self,
request: &HyperRequest,
) -> KrillResult<LoggedInUser> {
self.authorizer.login(request).await
}
pub async fn logout(
&self,
request: &HyperRequest,
) -> KrillResult<HttpResponse> {
self.authorizer.logout(request).await
}
pub fn testbed_enabled(&self) -> bool {
self.ca_manager.testbed_enabled()
}
#[cfg(feature = "multi-user")]
pub async fn login_session_cache_size(&self) -> usize {
self.authorizer.login_session_cache_size().await
}
}
/// # Access to components
impl KrillServer {
pub fn ca_manager(&self) -> &CaManager {
&self.ca_manager
/// Converts the RRDP path portion of a HTTP request URI to a path.
///
/// The `path` should contain everything after the `/rrdp/` portion of
/// the URIs path. If the path is in principle valid, i.e., could
/// represent an RRDP resource generated by this RRDP sever, the method
/// will return a file system path representing this path. This does not
/// mean there will actually be a file there. The file may have been
/// deleted or may have never existed at all. This is necessary since
/// the RRDP server doesnt track past files, only the currently valid
/// set of resources.
///
/// If the path is definitely not valid, returns `Ok(None)`. This should
/// probably be translated into a 404 Not Found response.
pub fn resolve_rrdp_request_path(
&self, path: &str
) -> KrillResult<Option<PathBuf>> {
self.repo_manager.resolve_rrdp_request_path(path)
}
}
/// # Configure publishers
impl KrillServer {
impl KrillManager {
/// Returns the repository server stats
pub fn repo_stats(&self) -> KrillResult<RepoStats> {
self.repo_manager.repo_stats()
@@ -408,7 +373,7 @@ impl KrillServer {
}
/// # Manage RFC8181 clients
impl KrillServer {
impl KrillManager {
pub fn repository_response(
&self,
publisher: &PublisherHandle,
@@ -426,26 +391,26 @@ impl KrillServer {
}
/// # TA Support
impl KrillServer {
impl KrillManager {
pub fn ta_proxy_enabled(&self) -> bool {
self.config.ta_proxy_enabled()
}
pub async fn ta_proxy_init(&self) -> KrillResult<()> {
pub fn ta_proxy_init(&self) -> KrillResult<()> {
self.ca_manager.ta_proxy_init()
}
pub async fn ta_proxy_id(&self) -> KrillResult<IdCertInfo> {
pub fn ta_proxy_id(&self) -> KrillResult<IdCertInfo> {
self.ca_manager.ta_proxy_id()
}
pub async fn ta_proxy_publisher_request(
pub fn ta_proxy_publisher_request(
&self,
) -> KrillResult<idexchange::PublisherRequest> {
self.ca_manager.ta_proxy_publisher_request()
}
pub async fn ta_proxy_repository_update(
pub fn ta_proxy_repository_update(
&self,
contact: RepositoryContact,
actor: &Actor,
@@ -454,13 +419,13 @@ impl KrillServer {
.ta_proxy_repository_update(contact, actor)
}
pub async fn ta_proxy_repository_contact(
pub fn ta_proxy_repository_contact(
&self,
) -> KrillResult<RepositoryContact> {
self.ca_manager.ta_proxy_repository_contact()
}
pub async fn ta_proxy_signer_add(
pub fn ta_proxy_signer_add(
&self,
info: TrustAnchorSignerInfo,
actor: &Actor,
@@ -468,7 +433,7 @@ impl KrillServer {
self.ca_manager.ta_proxy_signer_add(info, actor)
}
pub async fn ta_proxy_signer_update(
pub fn ta_proxy_signer_update(
&self,
info: TrustAnchorSignerInfo,
actor: &Actor,
@@ -476,20 +441,20 @@ impl KrillServer {
self.ca_manager.ta_proxy_signer_update(info, actor)
}
pub async fn ta_proxy_signer_make_request(
pub fn ta_proxy_signer_make_request(
&self,
actor: &Actor,
) -> KrillResult<TrustAnchorSignedRequest> {
self.ca_manager.ta_proxy_signer_make_request(actor)
}
pub async fn ta_proxy_signer_get_request(
pub fn ta_proxy_signer_get_request(
&self,
) -> KrillResult<TrustAnchorSignedRequest> {
self.ca_manager.ta_proxy_signer_get_request()
}
pub async fn ta_proxy_signer_process_response(
pub fn ta_proxy_signer_process_response(
&self,
response: TrustAnchorSignedResponse,
actor: &Actor,
@@ -498,7 +463,7 @@ impl KrillServer {
.ta_proxy_signer_process_response(response, actor)
}
pub async fn ta_proxy_children_add(
pub fn ta_proxy_children_add(
&self,
child_request: AddChildRequest,
actor: &Actor,
@@ -512,24 +477,17 @@ impl KrillServer {
)
}
pub async fn ta_cert_details(&self) -> KrillResult<TaCertDetails> {
pub fn ta_cert_details(&self) -> KrillResult<TaCertDetails> {
let proxy = self.ca_manager.get_trust_anchor_proxy()?;
Ok(proxy.get_ta_details()?.clone())
}
pub async fn trust_anchor_cert(&self) -> Option<ReceivedCert> {
self.ta_cert_details()
.await
.ok()
.map(|details| details.into())
}
}
/// # Being a parent
impl KrillServer {
impl KrillManager {
/// Adds a child to a CA and returns the ParentCaInfo that the child
/// will need to contact this CA for resource requests.
pub async fn ca_add_child(
pub fn ca_add_child(
&self,
ca: &CaHandle,
req: AddChildRequest,
@@ -548,7 +506,7 @@ impl KrillServer {
}
/// Shows the parent contact for a child.
pub async fn ca_parent_response(
pub fn ca_parent_response(
&self,
ca: &CaHandle,
child: ChildHandle,
@@ -557,7 +515,7 @@ impl KrillServer {
}
/// Update IdCert or resources of a child.
pub async fn ca_child_update(
pub fn ca_child_update(
&self,
ca: &CaHandle,
child: ChildHandle,
@@ -568,7 +526,7 @@ impl KrillServer {
}
/// Update IdCert or resources of a child.
pub async fn ca_child_remove(
pub fn ca_child_remove(
&self,
ca: &CaHandle,
child: ChildHandle,
@@ -578,7 +536,7 @@ impl KrillServer {
}
/// Show details for a child under the CA.
pub async fn ca_child_show(
pub fn ca_child_show(
&self,
ca: &CaHandle,
child: &ChildHandle,
@@ -587,7 +545,7 @@ impl KrillServer {
}
/// Export a child under the CA.
pub async fn api_ca_child_export(
pub fn ca_child_export(
&self,
ca: &CaHandle,
child: &ChildHandle,
@@ -596,7 +554,7 @@ impl KrillServer {
}
/// Import a child under the CA.
pub async fn api_ca_child_import(
pub fn ca_child_import(
&self,
ca: &CaHandle,
child: ImportChild,
@@ -606,7 +564,7 @@ impl KrillServer {
}
/// Show children stats under the CA.
pub async fn ca_stats_child_connections(
pub fn ca_stats_child_connections(
&self,
ca: &CaHandle,
) -> KrillResult<ChildrenConnectionStats> {
@@ -617,9 +575,9 @@ impl KrillServer {
}
/// # Being a child
impl KrillServer {
impl KrillManager {
/// Returns the child request for a CA, or NONE if the CA cannot be found.
pub async fn ca_child_req(
pub fn ca_child_req(
&self,
ca: &CaHandle,
) -> KrillResult<idexchange::ChildRequest> {
@@ -664,7 +622,7 @@ impl KrillServer {
}
/// # Stats and status of CAS
impl KrillServer {
impl KrillManager {
pub async fn cas_stats(
&self,
) -> KrillResult<HashMap<CaHandle, CertAuthStats>> {
@@ -933,22 +891,7 @@ impl KrillServer {
Ok(())
}
pub async fn all_ca_issues(
&self,
auth: &AuthInfo,
) -> KrillResult<AllCertAuthIssues> {
let mut all_issues = AllCertAuthIssues::default();
for ca in &self.ca_list(auth)?.cas {
let issues = self.ca_issues(&ca.handle).await?;
if !issues.is_empty() {
all_issues.cas.insert(ca.handle.clone(), issues);
}
}
Ok(all_issues)
}
pub async fn ca_issues(
pub fn ca_issues(
&self,
ca: &CaHandle,
) -> KrillResult<CertAuthIssues> {
@@ -957,10 +900,10 @@ impl KrillServer {
}
/// # Synchronization operations for CAS
impl KrillServer {
impl KrillManager {
/// Republish all CAs that need it.
pub async fn republish_all(&self, force: bool) -> KrillEmptyResult {
let cas = self.ca_manager.republish_all(force).await?;
pub fn republish_all(&self, force: bool) -> KrillEmptyResult {
let cas = self.ca_manager.republish_all(force)?;
for ca in cas {
self.cas_repo_sync_single(&ca)?;
}
@@ -969,8 +912,8 @@ impl KrillServer {
}
/// Re-sync all CAs with their repositories
pub fn cas_repo_sync_all(&self, auth: &AuthInfo) -> KrillEmptyResult {
self.ca_manager.cas_schedule_repo_sync_all(auth)
pub fn cas_repo_sync_all(&self) -> KrillEmptyResult {
self.ca_manager.cas_schedule_repo_sync_all()
}
/// Re-sync a specific CA with its repository
@@ -979,12 +922,12 @@ impl KrillServer {
}
/// Refresh all CAs: ask for updates and shrink as needed.
pub async fn cas_refresh_all(&self) -> KrillEmptyResult {
pub fn cas_refresh_all(&self) -> KrillEmptyResult {
self.ca_manager.cas_schedule_refresh_all()
}
/// Refresh a specific CA with its parents
pub async fn cas_refresh_single(
pub fn cas_refresh_single(
&self,
ca_handle: CaHandle,
) -> KrillEmptyResult {
@@ -998,14 +941,18 @@ impl KrillServer {
}
/// # Admin CAS
impl KrillServer {
impl KrillManager {
pub fn ca_handles(&self) -> KrillResult<impl Iterator<Item = CaHandle>> {
self.ca_manager.ca_handles().map(Vec::into_iter)
}
pub fn ca_list(&self, auth: &AuthInfo) -> KrillResult<CertAuthList> {
self.ca_manager.ca_list(auth)
}
/// Returns the public CA info for a CA, or NONE if the CA cannot be
/// found.
pub async fn ca_info(&self, ca: &CaHandle) -> KrillResult<CertAuthInfo> {
pub fn ca_info(&self, ca: &CaHandle) -> KrillResult<CertAuthInfo> {
self.ca_manager.get_ca(ca).map(|ca| ca.as_ca_info())
}
@@ -1029,7 +976,7 @@ impl KrillServer {
/// Returns the parent contact for a CA and parent, or NONE if either the
/// CA or the parent cannot be found.
pub async fn ca_my_parent_contact(
pub fn ca_my_parent_contact(
&self,
ca: &CaHandle,
parent: &ParentHandle,
@@ -1039,7 +986,7 @@ impl KrillServer {
}
/// Returns the history for a CA.
pub async fn ca_history(
pub fn ca_history(
&self,
ca: &CaHandle,
crit: CommandHistoryCriteria,
@@ -1057,7 +1004,7 @@ impl KrillServer {
/// Returns the publisher request for a CA, or NONE of the CA cannot be
/// found.
pub async fn ca_publisher_req(
pub fn ca_publisher_req(
&self,
ca: &CaHandle,
) -> KrillResult<idexchange::PublisherRequest> {
@@ -1072,7 +1019,7 @@ impl KrillServer {
/// Return the info about the CONFIGured repository server for a given Ca.
/// and the actual objects published there, as reported by a list reply.
pub async fn ca_repo_details(
pub fn ca_repo_details(
&self,
ca_handle: &CaHandle,
) -> KrillResult<CaRepoDetails> {
@@ -1094,7 +1041,7 @@ impl KrillServer {
.await
}
pub async fn ca_update_id(
pub fn ca_update_id(
&self,
ca: CaHandle,
actor: &Actor,
@@ -1102,7 +1049,7 @@ impl KrillServer {
self.ca_manager.ca_update_id(ca, actor)
}
pub async fn ca_keyroll_init(
pub fn ca_keyroll_init(
&self,
ca: CaHandle,
actor: &Actor,
@@ -1110,7 +1057,7 @@ impl KrillServer {
self.ca_manager.ca_keyroll_init(ca, Duration::seconds(0), actor)
}
pub async fn ca_keyroll_activate(
pub fn ca_keyroll_activate(
&self,
ca: CaHandle,
actor: &Actor,
@@ -1118,7 +1065,7 @@ impl KrillServer {
self.ca_manager.ca_keyroll_activate(ca, Duration::seconds(0), actor)
}
pub async fn rfc6492(
pub fn rfc6492(
&self,
ca: CaHandle,
msg_bytes: Bytes,
@@ -1130,15 +1077,15 @@ impl KrillServer {
}
/// # Handle ASPA requests
impl KrillServer {
pub async fn ca_aspas_definitions_show(
impl KrillManager {
pub fn ca_aspas_definitions_show(
&self,
ca: CaHandle,
ca: &CaHandle,
) -> KrillResult<AspaDefinitionList> {
self.ca_manager.ca_aspas_definitions_show(ca)
}
pub async fn ca_aspas_definitions_update(
pub fn ca_aspas_definitions_update(
&self,
ca: CaHandle,
updates: AspaDefinitionUpdates,
@@ -1147,7 +1094,7 @@ impl KrillServer {
self.ca_manager.ca_aspas_definitions_update(ca, updates, actor)
}
pub async fn ca_aspas_update_aspa(
pub fn ca_aspas_update_aspa(
&self,
ca: CaHandle,
customer: CustomerAsn,
@@ -1161,15 +1108,15 @@ impl KrillServer {
}
/// # Handle BGPSec requests
impl KrillServer {
pub async fn ca_bgpsec_definitions_show(
impl KrillManager {
pub fn ca_bgpsec_definitions_show(
&self,
ca: CaHandle,
ca: &CaHandle,
) -> KrillResult<BgpSecCsrInfoList> {
self.ca_manager.ca_bgpsec_definitions_show(ca)
}
pub async fn ca_bgpsec_definitions_update(
pub fn ca_bgpsec_definitions_update(
&self,
ca: CaHandle,
updates: BgpSecDefinitionUpdates,
@@ -1180,8 +1127,8 @@ impl KrillServer {
}
/// # Handle route authorization requests
impl KrillServer {
pub async fn ca_routes_update(
impl KrillManager {
pub fn ca_routes_update(
&self,
ca: CaHandle,
updates: RoaConfigurationUpdates,
@@ -1190,7 +1137,7 @@ impl KrillServer {
self.ca_manager.ca_routes_update(ca, updates, actor)
}
pub async fn ca_routes_show(
pub fn ca_routes_show(
&self,
handle: &CaHandle,
) -> KrillResult<Vec<ConfiguredRoa>> {
@@ -1257,7 +1204,7 @@ impl KrillServer {
}
/// # Handle Repository Server requests
impl KrillServer {
impl KrillManager {
/// Create the publication server, will fail if it was already created.
pub fn repository_init(
&self,
@@ -1282,15 +1229,15 @@ impl KrillServer {
}
/// # Handle Resource Tagged Attestation requests
impl KrillServer {
impl KrillManager {
/// List all known RTAs
pub async fn rta_list(&self, ca: CaHandle) -> KrillResult<RtaList> {
pub fn rta_list(&self, ca: CaHandle) -> KrillResult<RtaList> {
let ca = self.ca_manager.get_ca(&ca)?;
Ok(ca.rta_list())
}
/// Show RTA
pub async fn rta_show(
pub fn rta_show(
&self,
ca: CaHandle,
name: RtaName,
+1 -3
View File
@@ -1,8 +1,6 @@
pub mod bgp;
pub mod ca;
pub mod config;
pub mod http;
pub mod krillserver;
pub mod manager;
pub mod mq;
pub mod properties;
pub mod pubd;
+1 -1
View File
@@ -28,7 +28,7 @@ use crate::commons::eventsourcing::{
use crate::constants::{
ACTOR_DEF_KRILL, PUBSERVER_DFLT, PUBSERVER_NS, TA_NAME
};
use crate::server::config::Config;
use crate::config::Config;
use super::publishers::Publisher;
+21 -2
View File
@@ -2,7 +2,7 @@
use std::fmt;
use std::borrow::Cow;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use log::{debug, info};
use rpki::uri;
@@ -17,7 +17,7 @@ use crate::commons::eventsourcing::{
WalChange, WalCommand, WalSet, WalStore, WalSupport,
};
use crate::constants::PUBSERVER_CONTENT_NS;
use crate::server::config::{Config, RrdpUpdatesConfig};
use crate::config::{Config, RrdpUpdatesConfig};
use super::rrdp::{
CurrentObjects, DeltaElements, RrdpServer,RrdpSession, RrdpSessionReset,
RrdpUpdated, RrdpUpdateNeeded,
@@ -83,6 +83,25 @@ impl RepositoryContentProxy {
self.store.get_latest(&self.default_handle)
}
/// Converts the RRDP path portion of a HTTP request URI to a path.
///
/// The `path` should contain everything after the `/rrdp/` portion of
/// the URIs path. If the path is in principle valid, i.e., could
/// represent an RRDP resource generated by this RRDP sever, the method
/// will return a file system path representing this path. This does not
/// mean there will actually be a file there. The file may have been
/// deleted or may have never existed at all. This is necessary since
/// the RRDP server doesnt track past files, only the currently valid
/// set of resources.
///
/// If the path is definitely not valid, returns `Ok(None)`. This should
/// probably be translated into a 404 Not Found response.
pub fn resolve_rrdp_request_path(
&self, path: &str
) -> KrillResult<Option<PathBuf>> {
Ok(self.read()?.rrdp.resolve_request_path(path))
}
/// Clears all content, so the aggregate can be re-initialized.
///
/// Only to be called after all publishers have been removed from the
+22 -2
View File
@@ -1,5 +1,6 @@
//! The manager for the publication server.
use std::path::PathBuf;
use std::sync::Arc;
use bytes::Bytes;
use log::{debug, info};
@@ -18,7 +19,7 @@ use crate::commons::actor::Actor;
use crate::commons::cmslogger::CmsLogger;
use crate::commons::crypto::KrillSigner;
use crate::commons::error::Error;
use crate::server::config::Config;
use crate::config::Config;
use crate::server::mq::{now, Task, TaskQueue};
use super::access::RepositoryAccessProxy;
use super::content::RepositoryContentProxy;
@@ -97,6 +98,25 @@ impl RepositoryManager {
pub fn publishers(&self) -> KrillResult<Vec<PublisherHandle>> {
self.access.publishers()
}
/// Converts the RRDP path portion of a HTTP request URI to a path.
///
/// The `path` should contain everything after the `/rrdp/` portion of
/// the URIs path. If the path is in principle valid, i.e., could
/// represent an RRDP resource generated by this RRDP sever, the method
/// will return a file system path representing this path. This does not
/// mean there will actually be a file there. The file may have been
/// deleted or may have never existed at all. This is necessary since
/// the RRDP server doesnt track past files, only the currently valid
/// set of resources.
///
/// If the path is definitely not valid, returns `Ok(None)`. This should
/// probably be translated into a 404 Not Found response.
pub fn resolve_rrdp_request_path(
&self, path: &str
) -> KrillResult<Option<PathBuf>> {
self.content.resolve_rrdp_request_path(path)
}
}
/// # Publication protocol support.
@@ -349,7 +369,7 @@ mod tests {
use crate::constants::{
ACTOR_DEF_TEST, RRDP_FIRST_SERIAL, enable_test_mode
};
use crate::server::config::{SignerConfig, SignerType};
use crate::config::{SignerConfig, SignerType};
use crate::server::pubd::Publisher;
use crate::server::pubd::rrdp::{PublicationDeltaError, RrdpServer};
use super::*;
+63 -2
View File
@@ -27,7 +27,7 @@ use crate::constants::{
REPOSITORY_RRDP_ARCHIVE_DIR, REPOSITORY_RRDP_DIR,
RRDP_FIRST_SERIAL,
};
use crate::server::config::RrdpUpdatesConfig;
use crate::config::RrdpUpdatesConfig;
//------------ RRDP name definitions -----------------------------------------
@@ -169,6 +169,67 @@ impl RrdpServer {
&self.snapshot
}
/// Converts the RRDP path portion of a HTTP request URI to a path.
///
/// The `path` should contain everything after the `/rrdp/` portion of
/// the URIs path. If the path is in principle valid, i.e., could
/// represent an RRDP resource generated by this RRDP sever, the method
/// will return a file system path representing this path. This does not
/// mean there will actually be a file there. The file may have been
/// deleted or may have never existed at all. This is necessary since
/// the RRDP server doesnt track past files, only the currently valid
/// set of resources.
///
/// If the path is definitely not valid, returns `None`. This should
/// probably be translated into a 404 Not Found response.
pub fn resolve_request_path(
&self, path: &str
) -> Option<PathBuf> {
Self::is_request_path_valid(path).map(|_| {
self.rrdp_base_dir.join(path)
})
}
/// Check if a request path is in principle valid.
///
/// This returns an option so we can use the question mark operator in
/// the implementation.
fn is_request_path_valid(path: &str) -> Option<()> {
// Literal "notification.xml" is fine.
if path == "notification.xml" {
return Some(())
}
// All other paths are session, serial, random, and then either
// "snapshot.xml" or "delta.xml"
let mut path = path.split('/');
// Check that the three interior items only contains letters, numbers,
// and hyphens. Technically we only do lower case letters, but since
// we are also using the Display impls of stuff, that may quietly
// change.
for item in [path.next()?, path.next()?, path.next()?] {
for &ch in item.as_bytes() {
if !ch.is_ascii_alphanumeric() && ch != b'-' {
return None
}
}
}
// Next is the file name.
match path.next()? {
"snapshot.xml" | "delta.xml" => { }
_ => return None,
}
// And then we need to be done.
if path.next().is_some() {
return None
}
Some(())
}
/// Lists all known publishers based on current objects and staged deltas.
pub fn publishers(&self) -> Vec<PublisherHandle> {
let publisher_current_objects =
@@ -208,7 +269,7 @@ impl RrdpServer {
}
}
/// Applies the data from an RRD session reset.
/// Applies the data from an RRDP session reset.
pub fn apply_session_reset(&mut self, reset: RrdpSessionReset) {
self.snapshot = reset.snapshot;
self.session = reset.session;
+1 -1
View File
@@ -11,7 +11,7 @@ use crate::commons::KrillResult;
use crate::commons::eventsourcing::WalStore;
use crate::commons::storage::{Key, KeyValueStore, Segment, Scope};
use crate::constants::PUBSERVER_CONTENT_NS;
use crate::server::config::Config;
use crate::config::Config;
use crate::server::pubd::content::RepositoryContent;
use self::pre_0_13_0::OldRepositoryContent;
@@ -4,7 +4,7 @@ use crate::commons::eventsourcing::{AggregateStore, StoredCommandBuilder};
use crate::commons::storage::{KeyValueStore, Scope, Segment};
use crate::commons::version::KrillVersion;
use crate::constants::PUBSERVER_NS;
use crate::server::config::Config;
use crate::config::Config;
use crate::server::pubd::access::{
RepositoryAccess, RepositoryAccessEvent, RepositoryAccessInitEvent,
StorableRepositoryCommand,
+1 -2
View File
@@ -28,9 +28,9 @@ use crate::{
SCHEDULER_RESYNC_REPO_CAS_THRESHOLD,
SCHEDULER_USE_JITTER_CAS_THRESHOLD, SIGNERS_NS,
},
config::Config,
server::{
ca::{CaManager, CertAuth},
config::Config,
mq::{
in_hours, in_minutes, in_seconds, in_weeks, now, Task, TaskQueue,
},
@@ -467,7 +467,6 @@ impl Scheduler {
let cas = self
.ca_manager
.republish_all(false)
.await
.map_err(FatalError)?;
for ca_handle in cas {
+6 -6
View File
@@ -783,8 +783,8 @@ impl TrustAnchorProxy {
signer: TrustAnchorSignerInfo,
) -> KrillResult<Vec<TrustAnchorProxyEvent>> {
if let Some(s) = &self.signer {
if s.ta_cert_details.cert().key_identifier() ==
signer.ta_cert_details.cert().key_identifier()
if s.ta_cert_details.cert.key_identifier() ==
signer.ta_cert_details.cert.key_identifier()
{
// It is not possible to add a signer that has a different
// public key
@@ -1054,7 +1054,7 @@ impl TrustAnchorProxy {
let child = self.get_child_details(child_handle)?;
let signing_cert = {
let received_cert = signer.ta_cert_details.cert();
let received_cert = &signer.ta_cert_details.cert;
let my_cert = received_cert.to_cert().map_err(|e| {
Error::Custom(format!(
"Issue with certificate held by TA: {} ",
@@ -1205,7 +1205,7 @@ mod tests {
storage::Namespace,
test,
},
server::config::ConfigDefaults,
config::ConfigDefaults,
};
use crate::tasigner::{
TrustAnchorSigner, TrustAnchorSignerInitCommand,
@@ -1332,8 +1332,8 @@ mod tests {
assert_eq!(ta_objects.revision().number(), 42);
let ta_cert_details = proxy.get_ta_details().unwrap();
assert_eq!(ta_cert_details.tal().uris(), &tal_https);
assert_eq!(ta_cert_details.tal().rsync_uri(), &tal_rsync);
assert_eq!(ta_cert_details.tal.uris(), &tal_https);
assert_eq!(ta_cert_details.tal.rsync_uri(), &tal_rsync);
// We can make a new signer request to make a new manifest and CRL
// even if we do not yet have any issued certificates to publish.
+8 -8
View File
@@ -12,7 +12,7 @@ use url::Url;
use crate::{
commons::crypto::{KrillSigner, KrillSignerBuilder, OpenSslSignerConfig},
constants::OPENSSL_ONE_OFF_SIGNER_NAME,
server::config::{LogType, SignerConfig, SignerReference, SignerType},
config::{LogType, SignerConfig, SignerReference, SignerType},
};
// TA timing defaults
@@ -92,20 +92,20 @@ impl TaTimingConfig {
pub struct Config {
#[serde(
alias = "data_dir",
deserialize_with = "crate::server::config::deserialize_storage_uri"
deserialize_with = "crate::config::deserialize_storage_uri"
)]
pub storage_uri: Url,
#[serde(default)]
pub use_history_cache: bool,
#[serde(default = "crate::server::config::ConfigDefaults::log_type")]
#[serde(default = "crate::config::ConfigDefaults::log_type")]
log_type: LogType,
log_file: Option<PathBuf>,
#[serde(
default = "crate::server::config::ConfigDefaults::log_level",
default = "crate::config::ConfigDefaults::log_level",
deserialize_with = "crate::commons::ext_serde::de_level_filter"
)]
pub log_level: LevelFilter,
@@ -113,22 +113,22 @@ pub struct Config {
// Signer support. Ported from main Krill.
#[serde(
default,
deserialize_with = "crate::server::config::deserialize_signer_ref"
deserialize_with = "crate::config::deserialize_signer_ref"
)]
pub default_signer: SignerReference,
#[serde(
default,
deserialize_with = "crate::server::config::deserialize_signer_ref"
deserialize_with = "crate::config::deserialize_signer_ref"
)]
pub one_off_signer: SignerReference,
#[serde(
default = "crate::server::config::ConfigDefaults::signer_probe_retry_seconds"
default = "crate::config::ConfigDefaults::signer_probe_retry_seconds"
)]
pub signer_probe_retry_seconds: u64,
#[serde(default = "crate::server::config::ConfigDefaults::signers")]
#[serde(default = "crate::config::ConfigDefaults::signers")]
pub signers: Vec<SignerConfig>,
#[serde(
+6 -6
View File
@@ -153,7 +153,7 @@ impl fmt::Display for TrustAnchorSignerEvent {
write!(
f,
"Signer reissue done with serial {}",
ta_cert_details.cert().serial
ta_cert_details.cert.serial
)
}
}
@@ -378,7 +378,7 @@ impl eventsourcing::Aggregate for TrustAnchorSigner {
&signer,
)?;
let objects = TrustAnchorObjects::create(
ta_cert_details.cert(),
&ta_cert_details.cert,
cmd.ta_mft_nr_override.unwrap_or(1),
timing.mft_next_update_weeks,
&signer,
@@ -555,7 +555,7 @@ impl TrustAnchorSigner {
)
.map_err(Error::custom)?;
Ok(TaCertDetails::new(rcvd_cert, tal))
Ok(TaCertDetails { cert: rcvd_cert, tal })
}
fn update_ta_cert_details(
@@ -568,7 +568,7 @@ impl TrustAnchorSigner {
) -> KrillResult<TaCertDetails> {
let resources = ResourceSet::all();
let key = self.ta_cert_details.cert().key_identifier();
let key = self.ta_cert_details.cert.key_identifier();
let cert = {
let serial: Serial = signer.random_serial()?;
@@ -624,7 +624,7 @@ impl TrustAnchorSigner {
)
.map_err(Error::custom)?;
Ok(TaCertDetails::new(rcvd_cert, tal))
Ok(TaCertDetails { cert: rcvd_cert, tal })
}
/// Process a request.
@@ -646,7 +646,7 @@ impl TrustAnchorSigner {
HashMap<KeyIdentifier, ProvisioningResponse>,
> = HashMap::new();
let signing_cert = self.ta_cert_details.cert();
let signing_cert = &self.ta_cert_details.cert;
let ta_rcn = ta_resource_class_name();
for child_request in &signed_request.content().child_requests {
+1 -1
View File
@@ -21,9 +21,9 @@ use crate::{
KEYS_NS, PROPERTIES_NS, PUBSERVER_CONTENT_NS,
PUBSERVER_NS, SIGNERS_NS, TA_PROXY_SERVER_NS, TA_SIGNER_SERVER_NS,
},
config::Config,
server::{
ca::upgrades::data_migration::check_ca_objects,
config::Config,
properties::{Properties, PropertiesManager},
pubd::{RepositoryAccess, RepositoryContent},
},
+10 -11
View File
@@ -32,8 +32,9 @@ use crate::{
PUBSERVER_CONTENT_NS, PUBSERVER_NS, SIGNERS_NS, STATUS_NS,
TA_PROXY_SERVER_NS, TA_SIGNER_SERVER_NS,
},
config::Config,
server::{
config::Config, krillserver::KrillServer,
manager::KrillManager,
properties::PropertiesManager,
},
upgrades::pre_0_14_0::{
@@ -1176,7 +1177,7 @@ fn record_preexisting_openssl_keys_in_signer_mapper(
/// server is started and operators can make changes.
pub async fn post_start_upgrade(
report: UpgradeReport,
server: &KrillServer,
server: &KrillManager,
) -> KrillResult<()> {
if report.versions().from() < &KrillVersion::candidate(0, 9, 3, 2) {
info!("Reissue ROAs on upgrade to force short EE certificate subjects in the objects");
@@ -1189,13 +1190,11 @@ pub async fn post_start_upgrade(
add_or_replace: configs,
remove: Vec::new()
};
server
.ca_aspas_definitions_update(
ca,
aspa_updates,
server.system_actor(),
)
.await?;
server.ca_aspas_definitions_update(
ca,
aspa_updates,
server.system_actor(),
)?;
}
Ok(())
@@ -1296,7 +1295,7 @@ mod tests {
fn test_upgrade(base_dir: &str, namespaces: &[&str]) {
let temp_dir = tempdir().unwrap();
copy_folder(&base_dir, &temp_dir);
copy_folder(base_dir, &temp_dir);
// Copy data for the given names spaces into memory for testing.
let mem_storage_base_uri = test::mem_storage();
@@ -1570,7 +1569,7 @@ mod tests {
let source_dir_path_str =
"test-resources/status_store/migration-0.9.5/";
let temp_dir = tempdir().unwrap();
copy_folder(&source_dir_path_str, &temp_dir);
copy_folder(source_dir_path_str, &temp_dir);
let source_dir_url = Url::parse(
&format!("local://{}", &temp_dir.path().to_str().unwrap()))
.unwrap();
+1 -1
View File
@@ -19,8 +19,8 @@ use crate::{
},
storage::{KeyValueStore, Namespace},
},
config::Config,
server::{
config::Config,
properties::Properties,
},
server::taproxy::{
+1 -1
View File
@@ -1,7 +1,7 @@
//! Perform functional tests on a Krill instance, using the API
use krill::cli::client::KrillClient;
use krill::server::config::Benchmark;
use krill::config::Benchmark;
use log::LevelFilter;
mod common;
+4 -4
View File
@@ -23,13 +23,13 @@ use krill::commons::httpclient;
use krill::commons::crypto::OpenSslSignerConfig;
use krill::cli::client::KrillClient;
use krill::constants::REPOSITORY_DIR;
use krill::server::config::{
use krill::config::{
AuthType, Config, ConfigDefaults, HttpsMode, IssuanceTimingConfig,
LogType, MetricsConfig, RrdpUpdatesConfig, SignerConfig,
SignerReference, SignerType, TestBed,
};
use krill::server::http::tls_keys::HTTPS_SUB_DIR;
use krill::server::http::server;
use krill::daemon::http::tls_keys::HTTPS_SUB_DIR;
use krill::daemon::start::start_krill_daemon;
use krill::tasigner::TaTimingConfig;
@@ -392,7 +392,7 @@ impl KrillServer {
let (tx, running) = oneshot::channel();
let mut res = Self {
join: tokio::spawn(async {
if let Err(err) = server::start_krill_daemon(
if let Err(err) = start_krill_daemon(
config.into(), Some(tx)
).await {
error!("Krill failed to start: {}", err);
+1 -1
View File
@@ -22,7 +22,7 @@ mod common;
///
/// [Krill as a Trust Anchor]: https://krill.docs.nlnetlabs.nl/en/stable/trust-anchor.html
#[tokio::test]
async fn functional_at() {
async fn functional_ta() {
let (mut config, _tempdir) = common::TestConfig::mem_storage()
.enable_second_signer().finalize();
let port = config.port;