add auth to all registry routes
This commit is contained in:
parent
059cd4fa0e
commit
381ce040fd
1 changed files with 75 additions and 31 deletions
|
@ -6,6 +6,7 @@ use axum::headers::Authorization;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::{IntoResponse, Response};
|
||||||
use axum::routing::{get, head, post, put};
|
use axum::routing::{get, head, post, put};
|
||||||
use axum::{async_trait, Router};
|
use axum::{async_trait, Router};
|
||||||
|
use futures::StreamExt;
|
||||||
use hyper::StatusCode;
|
use hyper::StatusCode;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
@ -14,7 +15,7 @@ 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, DatabaseConnection};
|
||||||
|
|
||||||
use crate::db::users::{authenticate_user, Credentials, User};
|
use crate::db::users::{authenticate_user, Credentials, User};
|
||||||
|
|
||||||
|
@ -133,22 +134,28 @@ pub struct RegistryError {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn check_blob_exists(
|
async fn check_blob_exists(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
auth: RegistryAuth,
|
||||||
) -> impl IntoResponse {
|
Path((repository_name, raw_digest)): Path<(String, String)>,
|
||||||
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
||||||
let blob_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
let blob_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
||||||
if blob_path.exists() {
|
if blob_path.exists() {
|
||||||
StatusCode::OK
|
Ok(StatusCode::OK)
|
||||||
} else {
|
} else {
|
||||||
StatusCode::NOT_FOUND
|
Err(StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_blob(
|
async fn get_blob(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
Path((_repository_name, raw_digest)): Path<(String, String)>,
|
auth: RegistryAuth,
|
||||||
) -> impl IntoResponse {
|
Path((repository_name, raw_digest)): Path<(String, String)>,
|
||||||
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
let digest = raw_digest.strip_prefix("sha256:").unwrap();
|
||||||
let blob_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
let blob_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
||||||
if !blob_path.exists() {
|
if !blob_path.exists() {
|
||||||
|
@ -161,15 +168,18 @@ async fn get_blob(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_upload(
|
async fn create_upload(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
|
auth: RegistryAuth,
|
||||||
Path(repository_name): Path<String>,
|
Path(repository_name): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
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
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::ACCEPTED)
|
.status(StatusCode::ACCEPTED)
|
||||||
.header(
|
.header(
|
||||||
"Location",
|
"Location",
|
||||||
|
@ -178,16 +188,17 @@ async fn create_upload(
|
||||||
.header("Docker-Upload-UUID", uuid)
|
.header("Docker-Upload-UUID", uuid)
|
||||||
.header("Range", "bytes=0-0")
|
.header("Range", "bytes=0-0")
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap()
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
use futures::StreamExt;
|
|
||||||
|
|
||||||
async fn patch_upload(
|
async fn patch_upload(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
|
auth: RegistryAuth,
|
||||||
Path((repository_name, uuid)): Path<(String, String)>,
|
Path((repository_name, uuid)): Path<(String, String)>,
|
||||||
mut stream: BodyStream,
|
mut stream: BodyStream,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
// let content_length = headers.get("Content-Length").unwrap();
|
// let content_length = headers.get("Content-Length").unwrap();
|
||||||
// let content_range = headers.get("Content-Range").unwrap();
|
// let content_range = headers.get("Content-Range").unwrap();
|
||||||
// let content_type = headers.get("Content-Type").unwrap();
|
// let content_type = headers.get("Content-Type").unwrap();
|
||||||
|
@ -207,7 +218,7 @@ async fn patch_upload(
|
||||||
len += n_bytes;
|
len += n_bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::ACCEPTED)
|
.status(StatusCode::ACCEPTED)
|
||||||
.header(
|
.header(
|
||||||
"Location",
|
"Location",
|
||||||
|
@ -216,7 +227,7 @@ async fn patch_upload(
|
||||||
.header("Docker-Upload-UUID", uuid)
|
.header("Docker-Upload-UUID", uuid)
|
||||||
.header("Range", format!("0-{}", len))
|
.header("Range", format!("0-{}", len))
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap()
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -226,11 +237,14 @@ struct UploadParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn put_upload(
|
async fn put_upload(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
|
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,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
let mut _len = 0;
|
let mut _len = 0;
|
||||||
let upload_path = PathBuf::from(REGISTRY_PATH).join("uploads").join(&uuid);
|
let upload_path = PathBuf::from(REGISTRY_PATH).join("uploads").join(&uuid);
|
||||||
let mut file = tokio::fs::OpenOptions::new()
|
let mut file = tokio::fs::OpenOptions::new()
|
||||||
|
@ -251,7 +265,7 @@ async fn put_upload(
|
||||||
let target_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
let target_path = PathBuf::from(REGISTRY_PATH).join("sha256").join(&digest);
|
||||||
tokio::fs::rename(&upload_path, &target_path).await.unwrap();
|
tokio::fs::rename(&upload_path, &target_path).await.unwrap();
|
||||||
|
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::CREATED)
|
.status(StatusCode::CREATED)
|
||||||
.header(
|
.header(
|
||||||
"Location",
|
"Location",
|
||||||
|
@ -261,13 +275,16 @@ async fn put_upload(
|
||||||
// .header("Range", format!("0-{}", len))
|
// .header("Range", format!("0-{}", len))
|
||||||
.header("Docker-Content-Digest", digest)
|
.header("Docker-Content-Digest", digest)
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap()
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_manifest(
|
async fn get_manifest(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
|
auth: RegistryAuth,
|
||||||
Path((repository_name, reference)): Path<(String, String)>,
|
Path((repository_name, reference)): Path<(String, String)>,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
let manifest_path = PathBuf::from(REGISTRY_PATH)
|
let manifest_path = PathBuf::from(REGISTRY_PATH)
|
||||||
.join("manifests")
|
.join("manifests")
|
||||||
.join(&repository_name)
|
.join(&repository_name)
|
||||||
|
@ -278,18 +295,21 @@ async fn get_manifest(
|
||||||
let manifest: serde_json::Map<String, serde_json::Value> =
|
let manifest: serde_json::Map<String, serde_json::Value> =
|
||||||
serde_json::from_slice(&data).unwrap();
|
serde_json::from_slice(&data).unwrap();
|
||||||
let media_type = manifest.get("mediaType").unwrap().as_str().unwrap();
|
let media_type = manifest.get("mediaType").unwrap().as_str().unwrap();
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::OK)
|
.status(StatusCode::OK)
|
||||||
.header("Content-Type", media_type)
|
.header("Content-Type", media_type)
|
||||||
.body(axum::body::Full::from(data))
|
.body(axum::body::Full::from(data))
|
||||||
.unwrap()
|
.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn put_manifest(
|
async fn put_manifest(
|
||||||
_auth: RegistryAuth,
|
db_conn: DatabaseConnection,
|
||||||
|
auth: RegistryAuth,
|
||||||
Path((repository_name, reference)): Path<(String, String)>,
|
Path((repository_name, reference)): Path<(String, String)>,
|
||||||
mut stream: BodyStream,
|
mut stream: BodyStream,
|
||||||
) -> impl IntoResponse {
|
) -> Result<impl IntoResponse, StatusCode> {
|
||||||
|
check_access(&repository_name, &auth, &db_conn)?;
|
||||||
|
|
||||||
let repository_dir = PathBuf::from(REGISTRY_PATH)
|
let repository_dir = PathBuf::from(REGISTRY_PATH)
|
||||||
.join("manifests")
|
.join("manifests")
|
||||||
.join(&repository_name);
|
.join(&repository_name);
|
||||||
|
@ -317,7 +337,7 @@ async fn put_manifest(
|
||||||
let digest_path = repository_dir.join(&content_digest).with_extension("json");
|
let digest_path = repository_dir.join(&content_digest).with_extension("json");
|
||||||
tokio::fs::copy(manifest_path, digest_path).await.unwrap();
|
tokio::fs::copy(manifest_path, digest_path).await.unwrap();
|
||||||
|
|
||||||
Response::builder()
|
Ok(Response::builder()
|
||||||
.status(StatusCode::CREATED)
|
.status(StatusCode::CREATED)
|
||||||
.header(
|
.header(
|
||||||
"Location",
|
"Location",
|
||||||
|
@ -325,5 +345,29 @@ async fn put_manifest(
|
||||||
)
|
)
|
||||||
.header("Docker-Content-Digest", content_digest)
|
.header("Docker-Content-Digest", content_digest)
|
||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap()
|
.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_access(
|
||||||
|
repository_name: &str,
|
||||||
|
auth: &RegistryAuth,
|
||||||
|
db_conn: &DatabaseConnection,
|
||||||
|
) -> Result<(), StatusCode> {
|
||||||
|
use diesel::OptionalExtension;
|
||||||
|
|
||||||
|
let res = db::bots::find_bot_by_name(repository_name, db_conn)
|
||||||
|
.optional()
|
||||||
|
.expect("could not run query");
|
||||||
|
|
||||||
|
match res {
|
||||||
|
None => Ok(()), // name has not been claimed yet (TODO: verify its validity)
|
||||||
|
Some(existing_bot) => {
|
||||||
|
let RegistryAuth::User(user) = auth;
|
||||||
|
if existing_bot.owner_id == Some(user.id) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(StatusCode::FORBIDDEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue