Skip to content

Commit

Permalink
Apply actual password protection to protected endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
mbr committed Jan 6, 2024
1 parent 4101850 commit e1e8135
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ hex = "0.4.3"
itertools = "0.12.0"
nom = "7.1.3"
reqwest = { version = "0.11.23", default-features = false }
sec = { version = "1.0.0", features = ["deserialize"] }
sec = { version = "1.0.0", features = [ "deserialize", "serialize" ] }
serde = { version = "1.0.193", features = [ "derive" ] }
serde_json = "1.0.108"
sha2 = "0.10.8"
Expand Down
17 changes: 13 additions & 4 deletions src/container_orchestrator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub(crate) struct ContainerOrchestrator {
pub(crate) struct PublishedContainer {
host_addr: SocketAddr,
manifest_reference: ManifestReference,
config: RuntimeConfig,
config: Arc<RuntimeConfig>,
}

impl PublishedContainer {
Expand All @@ -57,12 +57,16 @@ impl PublishedContainer {
pub(crate) fn host_addr(&self) -> SocketAddr {
self.host_addr
}

pub(crate) fn config(&self) -> &Arc<RuntimeConfig> {
&self.config
}
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub(crate) struct RuntimeConfig {
#[serde(default)]
http_access: HashMap<String, String>,
pub(crate) http_access: Option<HashMap<String, Secret<String>>>,
}

impl IntoResponse for RuntimeConfig {
Expand Down Expand Up @@ -115,7 +119,12 @@ impl ContainerOrchestrator {
self.configs_dir
.join(location.repository())
.join(location.image())
.join(manifest_reference.reference().to_string())
.join(
manifest_reference
.reference()
.to_string()
.trim_start_matches(':'),
)
}

pub(crate) async fn load_config(
Expand Down Expand Up @@ -196,7 +205,7 @@ impl ContainerOrchestrator {
return Ok(None);
};

let config = self.load_config(&manifest_reference).await?;
let config = Arc::new(self.load_config(&manifest_reference).await?);

Ok(Some(PublishedContainer {
host_addr: port_mapping
Expand Down
26 changes: 25 additions & 1 deletion src/registry/auth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{str, sync::Arc};
use std::{collections::HashMap, str, sync::Arc};

use axum::{
async_trait,
Expand Down Expand Up @@ -96,6 +96,30 @@ impl AuthProvider for bool {
}
}

#[async_trait]
impl AuthProvider for HashMap<String, Secret<String>> {
async fn check_credentials(
&self,
UnverifiedCredentials {
username: unverified_username,
password: unverified_password,
}: &UnverifiedCredentials,
) -> bool {
if let Some(correct_password) = self.get(unverified_username) {
// TODO: Use constant-time compare. Maybe add to `sec`?
if correct_password == unverified_password {
return true;
}
}

false
}

async fn has_access_to(&self, _username: &str, _namespace: &str, _image: &str) -> bool {
true
}
}

#[async_trait]
impl<T> AuthProvider for Box<T>
where
Expand Down
40 changes: 32 additions & 8 deletions src/reverse_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,10 @@ impl PartialEq<String> for Domain {

#[derive(Debug)]
enum Destination {
ReverseProxied(Uri),
ReverseProxied {
uri: Uri,
config: Arc<RuntimeConfig>,
},
Internal(Uri),
NotFound,
}
Expand Down Expand Up @@ -129,9 +132,10 @@ impl RoutingTable {
Authority::from_str(&pc.host_addr().to_string())
.expect("SocketAddr should never fail to convert to Authority"),
);
return Destination::ReverseProxied(
Uri::from_parts(parts).expect("should not have invalidated Uri"),
);
return Destination::ReverseProxied {
uri: Uri::from_parts(parts).expect("should not have invalidated Uri"),
config: pc.config().clone(),
};
}

// Matching a domain did not succeed, let's try with a path.
Expand Down Expand Up @@ -161,7 +165,10 @@ impl RoutingTable {
parts.authority = Some(Authority::from_str(&container_addr.to_string()).unwrap());
parts.path_and_query = Some(PathAndQuery::from_str(&dest_path_and_query).unwrap());

return Destination::ReverseProxied(Uri::from_parts(parts).unwrap());
return Destination::ReverseProxied {
uri: Uri::from_parts(parts).unwrap(),
config: pc.config().clone(),
};
}
}

Expand Down Expand Up @@ -271,17 +278,34 @@ fn split_path_base_url(uri: &Uri) -> Option<(ImageLocation, String)> {

async fn route_request(
State(rp): State<Arc<ReverseProxy>>,
request: Request,
mut request: Request,
) -> Result<Response, AppError> {
let dest_uri = {
let routing_table = rp.routing_table.read().await;
routing_table.get_destination_uri_from_request(&request)
};

match dest_uri {
Destination::ReverseProxied(dest) => {
Destination::ReverseProxied { uri: dest, config } => {
trace!(%dest, "reverse proxying");

// First, check if http authentication is enabled.
if let Some(ref http_access) = config.http_access {
let creds = request
.extract_parts::<UnverifiedCredentials>()
.await
.map_err(AppError::AuthFailure)?;

if !http_access.check_credentials(&creds).await {
return Response::builder()
.status(StatusCode::FORBIDDEN)
.body(Body::empty())
.map_err(|_| {
AppError::AssertionFailed("should not fail to build response")
});
}
}

// Note: `reqwest` and `axum` currently use different versions of `http`
let method = request.method().to_string().parse().map_err(|_| {
AppError::AssertionFailed("method http version mismatch workaround failed")
Expand Down Expand Up @@ -367,7 +391,7 @@ async fn route_request(
Ok(config.into_response())
}
Method::PUT => {
let raw = dbg!(opt_body.ok_or(AppError::InvalidPayload)?);
let raw = opt_body.ok_or(AppError::InvalidPayload)?;
let new_config: RuntimeConfig =
toml::from_str(&raw).map_err(|_| AppError::InvalidPayload)?;
let stored = orchestrator
Expand Down

0 comments on commit e1e8135

Please sign in to comment.