mirror of
https://github.com/NLnetLabs/krill.git
synced 2026-10-01 05:44:52 +02:00
Handle publish/list requests. Still to do: integration testing, and exposing this in the http handler.
This commit is contained in:
@@ -21,5 +21,5 @@ rsync_base = "rsync://127.0.0.1/repo/"
|
||||
# in their certificates.
|
||||
notify_sia = "https://127.0.0.1/repo/notify.xml"
|
||||
|
||||
# Specify the service URI where publishers can connect.
|
||||
service_uri = "https://127.0.0.1/publish"
|
||||
# Specify the base service URI where publishers can connect.
|
||||
service_uri = "https://127.0.0.1/rfc8181/"
|
||||
+12
-6
@@ -2,6 +2,7 @@ extern crate hyper;
|
||||
extern crate futures;
|
||||
|
||||
use self::hyper::{Body, Method, Response, Server, StatusCode};
|
||||
use self::hyper::Request;
|
||||
use self::hyper::rt::Future;
|
||||
use self::hyper::service::service_fn_ok;
|
||||
use pubd::config::Config;
|
||||
@@ -34,20 +35,24 @@ pub fn serve(config: &Config) {
|
||||
|
||||
let pub_server = pub_server.clone();
|
||||
|
||||
service_fn_ok(move |req| {
|
||||
let path = req.uri().path();
|
||||
service_fn_ok(move |req: Request<Body>| {
|
||||
let (parts, _body) = req.into_parts();
|
||||
let path = parts.uri.path();
|
||||
|
||||
if path.starts_with("/static") {
|
||||
if path.starts_with("/rfc8181/") {
|
||||
let _handle = path.trim_left_matches("/publishers/");
|
||||
unimplemented!()
|
||||
} else if path.starts_with("/static") {
|
||||
render_static(path)
|
||||
} else if path.starts_with("/publishers/") {
|
||||
let handle = path.trim_left_matches("/publishers/");
|
||||
show_repository_response(handle, &pub_server)
|
||||
} else {
|
||||
match (req.method(), path) {
|
||||
(&Method::GET, "/health") => {
|
||||
match (parts.method, path) {
|
||||
(Method::GET, "/health") => {
|
||||
service_ok()
|
||||
},
|
||||
(&Method::GET, "/publishers") => {
|
||||
(Method::GET, "/publishers") => {
|
||||
show_publishers(&pub_server)
|
||||
},
|
||||
_ => {
|
||||
@@ -131,6 +136,7 @@ fn show_repository_response(
|
||||
}
|
||||
|
||||
|
||||
//------------ Error ---------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
pub enum Error {
|
||||
|
||||
+23
-3
@@ -2,11 +2,19 @@ use std::io;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use bcder::encode::Values;
|
||||
use bcder::Mode;
|
||||
use bcder::Captured;
|
||||
use provisioning::publisher::Publisher;
|
||||
use rpki::oob::exchange::RepositoryResponse;
|
||||
use rpki::publication::pubmsg::Message;
|
||||
use rpki::signing::builder::IdCertBuilder;
|
||||
use rpki::signing::builder::SignedMessageBuilder;
|
||||
use rpki::signing::PublicKeyAlgorithm;
|
||||
use rpki::signing::signer::Signer;
|
||||
use rpki::signing::signer::CreateKeyError;
|
||||
use rpki::signing::signer::KeyUseError;
|
||||
use rpki::uri;
|
||||
use signing::identity::MyIdentity;
|
||||
use signing::softsigner;
|
||||
use signing::softsigner::OpenSslSigner;
|
||||
@@ -15,9 +23,6 @@ use storage::keystore;
|
||||
use storage::keystore::Key;
|
||||
use storage::keystore::KeyStore;
|
||||
use storage::keystore::Info;
|
||||
use provisioning::publisher::Publisher;
|
||||
use rpki::oob::exchange::RepositoryResponse;
|
||||
use rpki::uri;
|
||||
|
||||
|
||||
/// # Naming things in the keystore.
|
||||
@@ -141,6 +146,21 @@ impl Responder {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an encoded SignedMessage for a contained Message.
|
||||
pub fn sign_msg(&mut self, msg: Message) -> Result<Captured, Error> {
|
||||
if let Some(id) = self.my_identity()? {
|
||||
let builder = SignedMessageBuilder::new(
|
||||
id.key_id(),
|
||||
&mut self.signer,
|
||||
msg
|
||||
)?;
|
||||
let enc = builder.encode().to_captured(Mode::Der);
|
||||
Ok(enc)
|
||||
} else {
|
||||
Err(Error::Unitialised)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
+151
-7
@@ -2,14 +2,18 @@
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use bcder::Captured;
|
||||
use provisioning::publisher::Publisher;
|
||||
use provisioning::publisher_store;
|
||||
use provisioning::publisher_store::PublisherStore;
|
||||
use provisioning::publisher_store::{self, PublisherStore};
|
||||
use pubd::responder::{self, Responder};
|
||||
use repo::file_store;
|
||||
use repo::repository::{self, Repository};
|
||||
use rpki::uri;
|
||||
use rpki::oob::exchange::RepositoryResponse;
|
||||
use pubd::responder::Responder;
|
||||
use pubd::responder;
|
||||
use rpki::publication::pubmsg::{Message, MessageError, QueryMessage};
|
||||
use rpki::publication::reply::{ErrorReply, ReportError, ReportErrorCode};
|
||||
use rpki::remote::sigmsg::SignedMessage;
|
||||
use rpki::uri;
|
||||
use rpki::x509::ValidationError;
|
||||
|
||||
|
||||
/// # Naming things in the keystore.
|
||||
@@ -102,10 +106,93 @@ impl PubServer {
|
||||
}
|
||||
|
||||
/// # Handle publisher requests
|
||||
///
|
||||
impl PubServer {
|
||||
|
||||
/// Handles an incoming SignedMessage, verifies it's validly signed by
|
||||
/// a known publisher and process the QueryMessage contained. Returns
|
||||
/// a signed response to the publisher.
|
||||
///
|
||||
/// Note this returns an error for cases where we do not want to do any
|
||||
/// work in signing, like the publisher does not exist, or the
|
||||
/// signature is invalid. The daemon will need to map these to HTTTP
|
||||
/// codes.
|
||||
///
|
||||
/// Also note that if garbage is sent to the daemon, this garbage will
|
||||
/// fail to parse as a SignedMessage, and the daemon will just respond
|
||||
/// with an HTTP error response, without invoking any of this.
|
||||
pub fn handle_request(
|
||||
&mut self,
|
||||
sigmsg: SignedMessage,
|
||||
publisher_handle: &str
|
||||
) -> Result<Captured, Error> {
|
||||
let publisher = self.publisher_store.get_publisher(publisher_handle)?;
|
||||
let base_uri = publisher.base_uri();
|
||||
sigmsg.validate(publisher.id_cert())?;
|
||||
|
||||
let res_msg = match Message::from_signed_message(&sigmsg) {
|
||||
Ok(msg) => {
|
||||
match msg.as_query() {
|
||||
Ok(query) => self.handle_query(&query, base_uri),
|
||||
Err(e) => Self::build_error(e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
Self::build_error(e)
|
||||
}
|
||||
};
|
||||
|
||||
let sigres = self.responder.sign_msg(res_msg)?;
|
||||
Ok(sigres)
|
||||
}
|
||||
|
||||
|
||||
/// Handles a publish or list query for a publisher. Needs to know the
|
||||
/// base_uri for the publisher to enforce constraints, but can assume
|
||||
/// that the PubServer has already validated the incoming SignedMessage.
|
||||
///
|
||||
/// Returns the appropriate Success or Error Reply in a Message, ready
|
||||
/// for wrapping into a SignedMessage.
|
||||
fn handle_query(
|
||||
&self,
|
||||
query: &QueryMessage,
|
||||
base_uri: &uri::Rsync
|
||||
) -> Message {
|
||||
match query {
|
||||
QueryMessage::PublishQuery(publish) => {
|
||||
match self.repository.publish(publish, base_uri) {
|
||||
Err(e) => {
|
||||
Self::build_error(e)
|
||||
},
|
||||
Ok(success) => success
|
||||
}
|
||||
},
|
||||
QueryMessage::ListQuery(_list) => {
|
||||
match self.repository.list(base_uri) {
|
||||
Err(e) => {
|
||||
Self::build_error(e)
|
||||
},
|
||||
Ok(success) => success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_error(error: impl ToReportErrorCode) -> Message {
|
||||
let mut error_builder = ErrorReply::build();
|
||||
error_builder.add(
|
||||
ReportError::reply(
|
||||
error.to_report_error_code(),
|
||||
None // Finding the specific PDU is too much hard work.
|
||||
)
|
||||
);
|
||||
error_builder.build_message()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//------------ Error ---------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Fail)]
|
||||
@@ -118,6 +205,12 @@ pub enum Error {
|
||||
|
||||
#[fail(display="{}", _0)]
|
||||
RepositoryError(repository::Error),
|
||||
|
||||
#[fail(display="{}", _0)]
|
||||
MessageError(MessageError),
|
||||
|
||||
#[fail(display="{}", _0)]
|
||||
ValdiationError(ValidationError),
|
||||
}
|
||||
|
||||
impl From<responder::Error> for Error {
|
||||
@@ -138,6 +231,58 @@ impl From<repository::Error> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MessageError> for Error {
|
||||
fn from(e: MessageError) -> Self {
|
||||
Error::MessageError(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValidationError> for Error {
|
||||
fn from(e: ValidationError) -> Self {
|
||||
Error::ValdiationError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------ ToReportErrorCode ---------------------------------------------
|
||||
|
||||
trait ToReportErrorCode {
|
||||
fn to_report_error_code(&self) -> ReportErrorCode;
|
||||
}
|
||||
|
||||
impl ToReportErrorCode for MessageError {
|
||||
fn to_report_error_code(&self) -> ReportErrorCode {
|
||||
ReportErrorCode::XmlError
|
||||
}
|
||||
}
|
||||
|
||||
impl ToReportErrorCode for repository::Error {
|
||||
fn to_report_error_code(&self) -> ReportErrorCode {
|
||||
match self {
|
||||
repository::Error::FileStoreError(error) =>
|
||||
error.to_report_error_code()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToReportErrorCode for file_store::Error {
|
||||
fn to_report_error_code(&self) -> ReportErrorCode {
|
||||
match self {
|
||||
file_store::Error::ObjectAlreadyPresent(_) =>
|
||||
ReportErrorCode::ObjectAlreadyPresent,
|
||||
file_store::Error::NoObjectPresent(_) =>
|
||||
ReportErrorCode::NoObjectPresent,
|
||||
file_store::Error::NoObjectMatchingHash =>
|
||||
ReportErrorCode::NoObjectMatchingHash,
|
||||
file_store::Error::OutsideBaseUri =>
|
||||
ReportErrorCode::PermissionFailure,
|
||||
_ => ReportErrorCode::OtherError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//------------ Tests ---------------------------------------------------------
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -267,8 +412,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn
|
||||
should_initialise_publishers_from_xml_and_have_response() {
|
||||
fn should_initialise_publishers_from_xml_and_have_response() {
|
||||
test::test_with_tmp_dir(|d| {
|
||||
let xml_dir = test::create_sub_dir(&d);
|
||||
|
||||
|
||||
+15
-21
@@ -31,7 +31,7 @@ impl FileStore {
|
||||
///
|
||||
impl FileStore {
|
||||
/// Process a PublishQuery update
|
||||
pub fn update(
|
||||
pub fn publish(
|
||||
&self,
|
||||
update: &PublishQuery,
|
||||
base_uri: &uri::Rsync
|
||||
@@ -85,27 +85,27 @@ impl FileStore {
|
||||
PublishElement::Publish(p) => {
|
||||
Self::assert_uri(base_uri, p.uri())?;
|
||||
if self.get_current_file_opt(p.uri())?.is_some() {
|
||||
return Err(Error::PublishWrongUri(p.uri().clone()))
|
||||
return Err(Error::ObjectAlreadyPresent(p.uri().clone()))
|
||||
}
|
||||
},
|
||||
PublishElement::Update(u) => {
|
||||
Self::assert_uri(base_uri, u.uri())?;
|
||||
if let Some(cur) = self.get_current_file_opt(u.uri())? {
|
||||
if cur.hash() != u.hash() {
|
||||
return Err(Error::UpdateWrongHash)
|
||||
return Err(Error::NoObjectMatchingHash)
|
||||
}
|
||||
} else {
|
||||
return Err(Error::UpdateWrongUri(u.uri().clone()))
|
||||
return Err(Error::NoObjectPresent(u.uri().clone()))
|
||||
}
|
||||
},
|
||||
PublishElement::Withdraw(w) => {
|
||||
Self::assert_uri(base_uri, w.uri())?;
|
||||
if let Some(cur) = self.get_current_file_opt(w.uri())? {
|
||||
if cur.hash() != w.hash() {
|
||||
return Err(Error::WithdrawWrongHash)
|
||||
return Err(Error::NoObjectMatchingHash)
|
||||
}
|
||||
} else {
|
||||
return Err(Error::UpdateWrongUri(w.uri().clone()))
|
||||
return Err(Error::NoObjectPresent(w.uri().clone()))
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -235,19 +235,13 @@ pub enum Error {
|
||||
UriError(uri::Error),
|
||||
|
||||
#[fail(display="File already exists for uri (use update!): {}", _0)]
|
||||
PublishWrongUri(uri::Rsync),
|
||||
ObjectAlreadyPresent(uri::Rsync),
|
||||
|
||||
#[fail(display="File sent for update has no entry for uri: {}", _0)]
|
||||
UpdateWrongUri(uri::Rsync),
|
||||
#[fail(display="Np file present for uri: {}", _0)]
|
||||
NoObjectPresent(uri::Rsync),
|
||||
|
||||
#[fail(display="File for update exists, but hash does not match")]
|
||||
UpdateWrongHash,
|
||||
|
||||
#[fail(display="The withdraw URI is not known: {}", _0)]
|
||||
WithdrawWrongUri(uri::Rsync),
|
||||
|
||||
#[fail(display="File for withdraw exists, but hash does not match")]
|
||||
WithdrawWrongHash,
|
||||
#[fail(display="File does not match hash")]
|
||||
NoObjectMatchingHash,
|
||||
|
||||
#[fail(display="Publishing outside of base URI is not allowed.")]
|
||||
OutsideBaseUri,
|
||||
@@ -294,7 +288,7 @@ mod tests {
|
||||
let message = builder.build_message();
|
||||
let publish = message.as_query().unwrap().as_publish().unwrap();
|
||||
|
||||
file_store.update(&publish, &base_uri).unwrap();
|
||||
file_store.publish(&publish, &base_uri).unwrap();
|
||||
|
||||
// See that it's the only one listed
|
||||
let files = file_store.list(&base_uri).unwrap();
|
||||
@@ -311,7 +305,7 @@ mod tests {
|
||||
builder.add(file_update.clone().as_update(file.content()));
|
||||
let message = builder.build_message();
|
||||
let publish = message.as_query().unwrap().as_publish().unwrap();
|
||||
file_store.update(&publish, &base_uri).unwrap();
|
||||
file_store.publish(&publish, &base_uri).unwrap();
|
||||
|
||||
// See that it's the only one listed
|
||||
let files = file_store.list(&base_uri).unwrap();
|
||||
@@ -323,7 +317,7 @@ mod tests {
|
||||
builder.add(file_update.as_withdraw());
|
||||
let message = builder.build_message();
|
||||
let publish = message.as_query().unwrap().as_publish().unwrap();
|
||||
file_store.update(&publish, &base_uri).unwrap();
|
||||
file_store.publish(&publish, &base_uri).unwrap();
|
||||
|
||||
// See that there are no files listed
|
||||
let files = file_store.list(&base_uri).unwrap();
|
||||
@@ -352,7 +346,7 @@ mod tests {
|
||||
let message = builder.build_message();
|
||||
let publish = message.as_query().unwrap().as_publish().unwrap();
|
||||
|
||||
match file_store.update(&publish, &base_uri) {
|
||||
match file_store.publish(&publish, &base_uri) {
|
||||
Err(Error::OutsideBaseUri) => {},
|
||||
_ => { panic!("Expected Error::OutsideBaseUri") }
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
mod file;
|
||||
mod file_store;
|
||||
pub mod file_store;
|
||||
|
||||
pub mod repository;
|
||||
+42
-1
@@ -1,5 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
use repo::file_store::{self, FileStore};
|
||||
use rpki::publication::pubmsg::Message;
|
||||
use rpki::publication::query::PublishQuery;
|
||||
use rpki::publication::reply::ListElement;
|
||||
use rpki::publication::reply::ListReply;
|
||||
use rpki::publication::reply::SuccessReply;
|
||||
use rpki::uri;
|
||||
|
||||
|
||||
//------------ Repository ----------------------------------------------------
|
||||
@@ -23,6 +29,41 @@ impl Repository {
|
||||
}
|
||||
}
|
||||
|
||||
/// # Publish / List
|
||||
///
|
||||
impl Repository {
|
||||
/// Publishes an publish query and returns a success reply embedded in
|
||||
/// a message. Throws an error in case of issues. The PubServer needs
|
||||
/// to wrap such errors in a response message to the publisher.
|
||||
pub fn publish(
|
||||
&self,
|
||||
update: &PublishQuery,
|
||||
base_uri: &uri::Rsync
|
||||
) -> Result<Message, Error> {
|
||||
self.fs.publish(update, base_uri)?;
|
||||
Ok(SuccessReply::build_message())
|
||||
}
|
||||
|
||||
/// Lists the objects for a base_uri, presumably all for the same
|
||||
/// publisher.
|
||||
pub fn list(
|
||||
&self,
|
||||
base_uri: &uri::Rsync
|
||||
) -> Result<Message, Error> {
|
||||
let files = self.fs.list(base_uri)?;
|
||||
let mut builder = ListReply::build();
|
||||
for file in files {
|
||||
builder.add(
|
||||
ListElement::reply(
|
||||
file.content(),
|
||||
file.uri().clone()
|
||||
)
|
||||
)
|
||||
}
|
||||
Ok(builder.build_message())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//------------ Error ---------------------------------------------------------
|
||||
|
||||
@@ -36,4 +77,4 @@ impl From<file_store::Error> for Error {
|
||||
fn from(e: file_store::Error) -> Self {
|
||||
Error::FileStoreError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user