implement basic auth checking
This commit is contained in:
parent
951cb29311
commit
059cd4fa0e
1 changed files with 62 additions and 22 deletions
|
@ -1,4 +1,4 @@
|
||||||
use axum::body::{Body, Bytes, StreamBody};
|
use axum::body::{Body, StreamBody};
|
||||||
use axum::extract::{BodyStream, FromRequest, Path, Query, RequestParts, TypedHeader};
|
use axum::extract::{BodyStream, FromRequest, Path, Query, RequestParts, TypedHeader};
|
||||||
use axum::handler::Handler;
|
use axum::handler::Handler;
|
||||||
use axum::headers::authorization::Basic;
|
use axum::headers::authorization::Basic;
|
||||||
|
@ -14,8 +14,12 @@ use tokio::io::AsyncWriteExt;
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::util::gen_alphanumeric;
|
use crate::util::gen_alphanumeric;
|
||||||
|
use crate::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::db::users::{authenticate_user, Credentials, User};
|
||||||
|
|
||||||
|
const REGISTRY_PATH: &str = "./data/registry";
|
||||||
|
|
||||||
const REGISTRY_PATH: &'static str = "./data/registry";
|
|
||||||
pub fn registry_service() -> Router {
|
pub fn registry_service() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
// The docker API requires this trailing slash
|
// The docker API requires this trailing slash
|
||||||
|
@ -49,18 +53,18 @@ async fn fallback(request: axum::http::Request<Body>) -> impl IntoResponse {
|
||||||
|
|
||||||
type AuthorizationHeader = TypedHeader<Authorization<Basic>>;
|
type AuthorizationHeader = TypedHeader<Authorization<Basic>>;
|
||||||
|
|
||||||
struct RegistryAuth;
|
enum RegistryAuth {
|
||||||
|
User(User),
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
enum RegistryAuthError {
|
||||||
impl<B> FromRequest<B> for RegistryAuth
|
NoAuthHeader,
|
||||||
where
|
InvalidCredentials,
|
||||||
B: Send,
|
}
|
||||||
{
|
|
||||||
type Rejection = Response<axum::body::Full<Bytes>>;
|
|
||||||
|
|
||||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
impl IntoResponse for RegistryAuthError {
|
||||||
let TypedHeader(Authorization(_basic)) =
|
fn into_response(self) -> Response {
|
||||||
AuthorizationHeader::from_request(req).await.map_err(|_| {
|
// TODO: create enum for registry errors
|
||||||
let err = RegistryErrors {
|
let err = RegistryErrors {
|
||||||
errors: vec![RegistryError {
|
errors: vec![RegistryError {
|
||||||
code: "UNAUTHORIZED".to_string(),
|
code: "UNAUTHORIZED".to_string(),
|
||||||
|
@ -68,15 +72,42 @@ where
|
||||||
detail: serde_json::Value::Null,
|
detail: serde_json::Value::Null,
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
Response::builder()
|
|
||||||
.status(StatusCode::UNAUTHORIZED)
|
|
||||||
.header("Docker-Distribution-API-Version", "registry/2.0")
|
|
||||||
.header("WWW-Authenticate", "Basic")
|
|
||||||
.body(axum::body::Full::from(serde_json::to_vec(&err).unwrap()))
|
|
||||||
.unwrap()
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(RegistryAuth)
|
(
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
[
|
||||||
|
("Docker-Distribution-API-Version", "registry/2.0"),
|
||||||
|
("WWW-Authenticate", "Basic"),
|
||||||
|
],
|
||||||
|
serde_json::to_string(&err).unwrap(),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<B> FromRequest<B> for RegistryAuth
|
||||||
|
where
|
||||||
|
B: Send,
|
||||||
|
{
|
||||||
|
type Rejection = RegistryAuthError;
|
||||||
|
|
||||||
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
|
let db_conn = DatabaseConnection::from_request(req).await.unwrap();
|
||||||
|
|
||||||
|
let TypedHeader(Authorization(basic)) = AuthorizationHeader::from_request(req)
|
||||||
|
.await
|
||||||
|
.map_err(|_| RegistryAuthError::NoAuthHeader)?;
|
||||||
|
|
||||||
|
// TODO: Into<Credentials> would be nice
|
||||||
|
let credentials = Credentials {
|
||||||
|
username: basic.username(),
|
||||||
|
password: basic.password(),
|
||||||
|
};
|
||||||
|
let user = authenticate_user(&credentials, &db_conn)
|
||||||
|
.ok_or(RegistryAuthError::InvalidCredentials)?;
|
||||||
|
|
||||||
|
Ok(RegistryAuth::User(user))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,6 +133,7 @@ pub struct RegistryError {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_blob_exists(
|
async fn check_blob_exists(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
||||||
|
@ -114,6 +146,7 @@ async fn check_blob_exists(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_blob(
|
async fn get_blob(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
||||||
|
@ -127,7 +160,10 @@ async fn get_blob(
|
||||||
Ok(stream_body)
|
Ok(stream_body)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_upload(Path(repository_name): Path<String>) -> impl IntoResponse {
|
async fn create_upload(
|
||||||
|
_auth: RegistryAuth,
|
||||||
|
Path(repository_name): Path<String>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
let uuid = gen_alphanumeric(16);
|
let uuid = gen_alphanumeric(16);
|
||||||
tokio::fs::File::create(PathBuf::from(REGISTRY_PATH).join("uploads").join(&uuid))
|
tokio::fs::File::create(PathBuf::from(REGISTRY_PATH).join("uploads").join(&uuid))
|
||||||
.await
|
.await
|
||||||
|
@ -148,6 +184,7 @@ async fn create_upload(Path(repository_name): Path<String>) -> impl IntoResponse
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
async fn patch_upload(
|
async fn patch_upload(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((repository_name, uuid)): Path<(String, String)>,
|
Path((repository_name, uuid)): Path<(String, String)>,
|
||||||
mut stream: BodyStream,
|
mut stream: BodyStream,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
@ -189,6 +226,7 @@ struct UploadParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn put_upload(
|
async fn put_upload(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((repository_name, uuid)): Path<(String, String)>,
|
Path((repository_name, uuid)): Path<(String, String)>,
|
||||||
Query(params): Query<UploadParams>,
|
Query(params): Query<UploadParams>,
|
||||||
mut stream: BodyStream,
|
mut stream: BodyStream,
|
||||||
|
@ -227,6 +265,7 @@ async fn put_upload(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_manifest(
|
async fn get_manifest(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((repository_name, reference)): Path<(String, String)>,
|
Path((repository_name, reference)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let manifest_path = PathBuf::from(REGISTRY_PATH)
|
let manifest_path = PathBuf::from(REGISTRY_PATH)
|
||||||
|
@ -247,6 +286,7 @@ async fn get_manifest(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn put_manifest(
|
async fn put_manifest(
|
||||||
|
_auth: RegistryAuth,
|
||||||
Path((repository_name, reference)): Path<(String, String)>,
|
Path((repository_name, reference)): Path<(String, String)>,
|
||||||
mut stream: BodyStream,
|
mut stream: BodyStream,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
Loading…
Reference in a new issue