Handle publish/list requests. Still to do: integration testing, and exposing this in the http handler.

This commit is contained in:
Tim Bruijnzeels
2018-11-28 16:07:14 +01:00
parent ea7143ef37
commit 1f0ee5855b
7 changed files with 246 additions and 41 deletions
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +1,4 @@
mod file;
mod file_store;
pub mod file_store;
pub mod repository;
+42 -1
View File
@@ -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)
}
}
}