From 1f0ee5855b2f19ebfd028872498c3d121b0b100b Mon Sep 17 00:00:00 2001 From: Tim Bruijnzeels Date: Wed, 28 Nov 2018 16:07:14 +0100 Subject: [PATCH] Handle publish/list requests. Still to do: integration testing, and exposing this in the http handler. --- defaults/server.toml | 4 +- src/pubd/daemon.rs | 18 +++-- src/pubd/responder.rs | 26 ++++++- src/pubd/server.rs | 158 +++++++++++++++++++++++++++++++++++++++-- src/repo/file_store.rs | 36 ++++------ src/repo/mod.rs | 2 +- src/repo/repository.rs | 43 ++++++++++- 7 files changed, 246 insertions(+), 41 deletions(-) diff --git a/defaults/server.toml b/defaults/server.toml index e699c345..4ddc21f9 100644 --- a/defaults/server.toml +++ b/defaults/server.toml @@ -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" \ No newline at end of file +# Specify the base service URI where publishers can connect. +service_uri = "https://127.0.0.1/rfc8181/" \ No newline at end of file diff --git a/src/pubd/daemon.rs b/src/pubd/daemon.rs index 68c363ad..5a8d5ff7 100644 --- a/src/pubd/daemon.rs +++ b/src/pubd/daemon.rs @@ -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| { + 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 { diff --git a/src/pubd/responder.rs b/src/pubd/responder.rs index 91cc748a..6898718d 100644 --- a/src/pubd/responder.rs +++ b/src/pubd/responder.rs @@ -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 { + 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) + } + } + } diff --git a/src/pubd/server.rs b/src/pubd/server.rs index 427573d5..17cc2b89 100644 --- a/src/pubd/server.rs +++ b/src/pubd/server.rs @@ -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 { + 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 for Error { @@ -138,6 +231,58 @@ impl From for Error { } } +impl From for Error { + fn from(e: MessageError) -> Self { + Error::MessageError(e) + } +} + +impl From 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); diff --git a/src/repo/file_store.rs b/src/repo/file_store.rs index 4cddf6d6..2adbdebb 100644 --- a/src/repo/file_store.rs +++ b/src/repo/file_store.rs @@ -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") } } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index d6e8126c..c14c0359 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,4 +1,4 @@ mod file; -mod file_store; +pub mod file_store; pub mod repository; \ No newline at end of file diff --git a/src/repo/repository.rs b/src/repo/repository.rs index 70b53783..3430717b 100644 --- a/src/repo/repository.rs +++ b/src/repo/repository.rs @@ -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 { + 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 { + 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 for Error { fn from(e: file_store::Error) -> Self { Error::FileStoreError(e) } -} +} \ No newline at end of file