diff --git a/src/container_orchestrator.rs b/src/container_orchestrator.rs index 98e4f73..7f85a9e 100644 --- a/src/container_orchestrator.rs +++ b/src/container_orchestrator.rs @@ -30,7 +30,6 @@ macro_rules! try_quiet { }; } -#[derive(Debug)] pub(crate) struct ContainerOrchestrator { podman: Podman, reverse_proxy: Arc, diff --git a/src/main.rs b/src/main.rs index 40a175c..e4dc80f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,6 +55,9 @@ async fn main() -> anyhow::Result<()> { debug!(?cfg, "loaded configuration"); + let rockslide_pw = cfg.rockslide.master_key.as_secret_string(); + let auth_provider = Arc::new(cfg.rockslide.master_key); + let local_ip: IpAddr = if podman_is_remote() { info!("podman is remote, trying to guess IP address"); let local_hostname = gethostname(); @@ -78,12 +81,9 @@ async fn main() -> anyhow::Result<()> { let local_addr = SocketAddr::from(([127, 0, 0, 1], cfg.reverse_proxy.http_bind.port())); info!(%local_addr, "guessing local registry address"); - let reverse_proxy = ReverseProxy::new(); + let reverse_proxy = ReverseProxy::new(auth_provider.clone()); - let credentials = ( - "rockslide-podman".to_owned(), - cfg.rockslide.master_key.as_secret_string(), - ); + let credentials = ("rockslide-podman".to_owned(), rockslide_pw); let orchestrator = Arc::new(ContainerOrchestrator::new( &cfg.containers.podman_path, reverse_proxy.clone(), @@ -97,11 +97,7 @@ async fn main() -> anyhow::Result<()> { orchestrator.synchronize_all().await?; orchestrator.updated_published_set().await; - let registry = ContainerRegistry::new( - &cfg.registry.storage_path, - orchestrator, - cfg.rockslide.master_key, - )?; + let registry = ContainerRegistry::new(&cfg.registry.storage_path, orchestrator, auth_provider)?; let app = Router::new() .merge(registry.make_router()) diff --git a/src/registry.rs b/src/registry.rs index 0c3f8fa..d2af01e 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -92,24 +92,20 @@ impl IntoResponse for AppError { pub(crate) struct ContainerRegistry { realm: String, - auth_provider: Box, + auth_provider: Arc, storage: Box, hooks: Box, } impl ContainerRegistry { - pub(crate) fn new< - P: AsRef, - T: RegistryHooks + 'static, - A: AuthProvider + 'static, - >( + pub(crate) fn new, T: RegistryHooks + 'static>( storage_path: P, orchestrator: T, - auth_provider: A, + auth_provider: Arc, ) -> Result, FilesystemStorageError> { Ok(Arc::new(ContainerRegistry { realm: "ContainerRegistry".to_string(), - auth_provider: Box::new(auth_provider), + auth_provider: auth_provider, storage: Box::new(FilesystemStorage::new(storage_path)?), hooks: Box::new(orchestrator), })) diff --git a/src/reverse_proxy.rs b/src/reverse_proxy.rs index 4d22537..18da953 100644 --- a/src/reverse_proxy.rs +++ b/src/reverse_proxy.rs @@ -15,7 +15,7 @@ use axum::{ Method, StatusCode, Uri, }, response::{IntoResponse, Response}, - Router, + RequestExt, Router, }; use itertools::Itertools; use tokio::sync::RwLock; @@ -23,11 +23,13 @@ use tracing::{trace, warn}; use crate::{ container_orchestrator::{ContainerOrchestrator, PublishedContainer}, - registry::{storage::ImageLocation, ManifestReference, Reference}, + registry::{ + storage::ImageLocation, AuthProvider, ManifestReference, Reference, UnverifiedCredentials, + }, }; -#[derive(Debug)] pub(crate) struct ReverseProxy { + auth_provider: Arc, client: reqwest::Client, routing_table: RwLock, orchestrator: OnceLock>, @@ -173,6 +175,7 @@ enum AppError { InternalUrlInvalid, AssertionFailed(&'static str), NonUtf8Header, + AuthFailure(StatusCode), Internal(anyhow::Error), } @@ -184,6 +187,7 @@ impl Display for AppError { AppError::InternalUrlInvalid => f.write_str("internal url invalid"), AppError::AssertionFailed(msg) => f.write_str(msg), AppError::NonUtf8Header => f.write_str("a header contained non-utf8 data"), + AppError::AuthFailure(_) => f.write_str("authentication missing or not present"), AppError::Internal(err) => Display::fmt(err, f), } } @@ -207,6 +211,7 @@ impl IntoResponse for AppError { AppError::InternalUrlInvalid => StatusCode::NOT_FOUND.into_response(), AppError::AssertionFailed(msg) => (StatusCode::NOT_FOUND, msg).into_response(), AppError::NonUtf8Header => StatusCode::BAD_REQUEST.into_response(), + AppError::AuthFailure(status) => status.into_response(), AppError::Internal(err) => { (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response() } @@ -215,8 +220,9 @@ impl IntoResponse for AppError { } impl ReverseProxy { - pub(crate) fn new() -> Arc { + pub(crate) fn new(auth_provider: Arc) -> Arc { Arc::new(ReverseProxy { + auth_provider, client: reqwest::Client::new(), routing_table: RwLock::new(Default::default()), orchestrator: OnceLock::new(), @@ -240,6 +246,7 @@ impl ReverseProxy { pub(crate) fn set_orchestrator(&self, orchestrator: Arc) -> &Self { self.orchestrator .set(orchestrator) + .map_err(|_| ()) .expect("set already set orchestrator"); self } @@ -271,7 +278,18 @@ async fn route_request( let dest = match dest_uri { Destination::ReverseProxied(dest) => dest, Destination::Internal(uri) => { - todo!("check access (master password)"); + let method = request.method().clone(); + // Note: The auth functionality has been lifted from `registry`. It may need to be + // refactored out because of that. + let creds = request + .extract::() + .await + .map_err(AppError::AuthFailure)?; + + // Any internal URL is subject to requiring auth through the master key. + if !rp.auth_provider.check_credentials(&creds).await { + todo!("return 403"); + } let remainder = uri .path() @@ -297,7 +315,7 @@ async fn route_request( .get() .ok_or_else(|| AppError::AssertionFailed("no orchestrator configured"))?; - return match *request.method() { + return match method { Method::GET => { let config = orchestrator .load_config(&manifest_reference)