blob: 410fd88de03cdc7ecd30d97af5ee2561d3ad21b6 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
anyhow::{Error, Result},
argh::{from_env, FromArgs},
fidl::endpoints::create_proxy,
fidl_fuchsia_io as fio,
fidl_test_security_pkg::{PackageServer_Request, PackageServer_RequestStream},
fuchsia_async::{net::TcpListener, Task},
fuchsia_component::server::ServiceFs,
fuchsia_fs::{open_directory_in_namespace, open_file, open_file_in_namespace, read_file_bytes},
fuchsia_hyper::{Executor, TcpStream},
fuchsia_syslog::{fx_log_info, fx_log_warn, init},
futures::{
channel::oneshot::{channel, Receiver},
stream::{Stream, StreamExt, TryStreamExt},
FutureExt,
},
hyper::{
server::{accept::from_stream, Server},
service::{make_service_fn, service_fn},
Body, Method, Request, Response, StatusCode,
},
rustls::{Certificate, NoClientAuth, ServerConfig},
std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::Path,
pin::Pin,
sync::Arc,
},
tokio::io::{AsyncRead, AsyncWrite},
tokio_rustls::TlsAcceptor,
};
/// Flags for pkg_server.
#[derive(FromArgs, Debug, PartialEq)]
pub struct Args {
/// absolute path to only root SSL certificates file.
#[argh(option)]
tls_certificate_chain_path: String,
/// absolute path to TLS private key for HTTPS server.
#[argh(option)]
tls_private_key_path: String,
/// absolute path to directory to serve over HTTPS.
#[argh(option)]
repository_path: String,
}
trait AsyncReadWrite: AsyncRead + AsyncWrite + Send {}
impl<T> AsyncReadWrite for T where T: AsyncRead + AsyncWrite + Send {}
fn parse_cert_chain(mut bytes: &[u8]) -> Vec<Certificate> {
rustls::internal::pemfile::certs(&mut bytes).expect("certs to parse")
}
fn parse_private_key(mut bytes: &[u8]) -> rustls::PrivateKey {
let keys =
rustls::internal::pemfile::rsa_private_keys(&mut bytes).expect("private keys to parse");
assert_eq!(keys.len(), 1, "expecting a single private key");
keys.into_iter().next().unwrap()
}
struct RequestHandler {
repository_dir: fio::DirectoryProxy,
}
impl RequestHandler {
pub fn new(repository_dir_ref: &fio::DirectoryProxy) -> Self {
let (repository_dir, server_end) = create_proxy::<fio::DirectoryMarker>().unwrap();
let server_end = server_end.into_channel().into();
repository_dir_ref.clone(fio::OpenFlags::CLONE_SAME_RIGHTS, server_end).unwrap();
Self { repository_dir }
}
pub async fn handle_request(&self, req: Request<Body>) -> Result<Response<Body>> {
match (req.method(), req.uri().path()) {
(&Method::GET, path) => self.simple_file_send(path).await,
(_, path) => Self::not_found(path, None),
}
}
fn not_found(path: &str, err: Option<Error>) -> Result<Response<Body>> {
match err {
Some(err) => fx_log_warn!("Not found: {}: {:?}", path, err),
None => fx_log_warn!("Not found: {}", path),
}
Response::builder()
.status(StatusCode::NOT_FOUND)
.body("Not found".into())
.map_err(Error::from)
}
fn ok(path: &str, body: Vec<u8>) -> Result<Response<Body>> {
fx_log_info!("OK: {}", path);
Response::builder().status(StatusCode::OK).body(body.into()).map_err(Error::from)
}
async fn simple_file_send(&self, path: &str) -> Result<Response<Body>> {
// Drop leading "/" from path.
assert!(path.starts_with("/"));
let mut path_chars = path.chars();
path_chars.next();
let path = path_chars.as_str();
match open_file(&self.repository_dir, Path::new(path), fio::OpenFlags::RIGHT_READABLE) {
Ok(file) => match read_file_bytes(&file).await {
Ok(bytes) => Self::ok(path, bytes),
Err(err) => Self::not_found(path, Some(err)),
},
Err(err) => Self::not_found(path, Some(err)),
}
}
}
fn serve_package_server_protocol(url_recv: Receiver<String>) {
let local_url = url_recv.shared();
Task::spawn(async move {
fx_log_info!("Preparing to serve test.security.pkg.PackageServer");
let mut fs = ServiceFs::new();
fs.dir("svc").add_fidl_service(move |mut stream: PackageServer_RequestStream| {
let local_url = local_url.clone();
fx_log_info!("New connection to test.security.pkg.PackageServer");
Task::spawn(async move {
while let Some(request) = stream.try_next().await.unwrap() {
let local_url = local_url.clone();
match request {
PackageServer_Request::GetUrl { responder } => {
let local_url = local_url.await.unwrap();
fx_log_info!(
"Responding to test.security.pkg.PackageServer.GetUrl request with {}",
local_url
);
responder.send(&local_url).unwrap();
}
}
}
})
.detach();
});
fs.take_and_serve_directory_handle().unwrap();
fs.collect::<()>().await;
})
.detach()
}
#[fuchsia_async::run_singlethreaded]
async fn main() {
init().unwrap();
fx_log_info!("Starting pkg_server");
let args @ Args { tls_certificate_chain_path, tls_private_key_path, repository_path } =
&from_env();
fx_log_info!("Initalizing pkg_server with {:?}", args);
let (url_send, url_recv) = channel();
serve_package_server_protocol(url_recv);
let root_ssl_certificates_file =
open_file_in_namespace(tls_certificate_chain_path, fio::OpenFlags::RIGHT_READABLE).unwrap();
let root_ssl_certificates_contents =
read_file_bytes(&root_ssl_certificates_file).await.unwrap();
let tls_private_key_file =
open_file_in_namespace(tls_private_key_path, fio::OpenFlags::RIGHT_READABLE).unwrap();
let tls_private_key_contents = read_file_bytes(&tls_private_key_file).await.unwrap();
let certs = parse_cert_chain(root_ssl_certificates_contents.as_slice());
let key = parse_private_key(tls_private_key_contents.as_slice());
let mut tls_config = ServerConfig::new(NoClientAuth::new());
// Configure ALPN and prefer H2 over HTTP/1.1.
tls_config.set_protocols(&[b"h2".to_vec(), b"http/1.1".to_vec()]);
tls_config.set_single_cert(certs, key).unwrap();
let tls_acceptor = TlsAcceptor::from(Arc::new(tls_config));
let (listener, addr) = {
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 443);
let listener = TcpListener::bind(&addr).unwrap();
let local_addr = listener.local_addr().unwrap();
(listener, local_addr)
};
let listener = listener
.accept_stream()
.map_err(Error::from)
.map_ok(|(conn, _addr)| TcpStream { stream: conn });
let connections: Pin<
Box<dyn Stream<Item = Result<Pin<Box<dyn AsyncReadWrite>>, Error>> + Send>,
> = listener
.and_then(move |conn| {
tls_acceptor.accept(conn).map(|res| match res {
Ok(conn) => Ok(Pin::new(Box::new(conn)) as Pin<Box<dyn AsyncReadWrite>>),
Err(e) => Err(Error::from(e)),
})
})
.boxed();
let make_service = make_service_fn(|_| {
let repository_dir =
open_directory_in_namespace(repository_path, fio::OpenFlags::RIGHT_READABLE).unwrap();
async move {
Ok::<_, Error>(service_fn(move |req: Request<Body>| {
let handler = RequestHandler::new(&repository_dir);
async move { handler.handle_request(req).await }
}))
}
});
let server: Server<_, _, Executor> =
Server::builder(from_stream(connections)).executor(Executor).serve(make_service);
fx_log_info!("pkg_server listening on {}", addr);
url_send.send("https://localhost".to_string()).unwrap();
server.await.unwrap();
}