respond with forbidden error if the user doesn't have access to an image

This commit is contained in:
SeanOMik 2023-06-16 00:28:50 -04:00
parent 875a1ed2b7
commit b9e41f6d6e
Signed by: SeanOMik
GPG Key ID: 568F326C7EB33ACB
5 changed files with 30 additions and 16 deletions

View File

@ -9,4 +9,5 @@
- [ ] prometheus metrics - [ ] prometheus metrics
- [ ] simple webui for managing the registry - [ ] simple webui for managing the registry
- [x] streaming layer bytes into providers - [x] streaming layer bytes into providers
- [x] streaming layer bytes from providers - [x] streaming layer bytes from providers
- [ ] better client error messages

View File

@ -8,7 +8,7 @@ use axum::response::{IntoResponse, Response};
use tokio_util::io::ReaderStream; use tokio_util::io::ReaderStream;
use crate::app_state::AppState; use crate::app_state::AppState;
use crate::auth::unauthenticated_response; use crate::auth::access_denied_response;
use crate::dto::RepositoryVisibility; use crate::dto::RepositoryVisibility;
use crate::dto::user::{Permission, UserAuth}; use crate::dto::user::{Permission, UserAuth};
use crate::error::AppError; use crate::error::AppError;
@ -17,7 +17,7 @@ pub async fn digest_exists_head(Path((name, layer_digest)): Path<(String, String
// Check if the user has permission to pull, or that the repository is public // Check if the user has permission to pull, or that the repository is public
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -42,7 +42,7 @@ pub async fn pull_digest_get(Path((name, layer_digest)): Path<(String, String)>,
// Check if the user has permission to pull, or that the repository is public // Check if the user has permission to pull, or that the repository is public
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);

View File

@ -7,7 +7,7 @@ use axum::http::{StatusCode, HeaderName, header};
use tracing::log::warn; use tracing::log::warn;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::auth::unauthenticated_response; use crate::auth::access_denied_response;
use crate::app_state::AppState; use crate::app_state::AppState;
use crate::database::Database; use crate::database::Database;
use crate::dto::RepositoryVisibility; use crate::dto::RepositoryVisibility;
@ -19,7 +19,7 @@ use crate::error::AppError;
pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>, body: String) -> Result<Response, AppError> { pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>, body: String) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -68,7 +68,7 @@ pub async fn pull_manifest_get(Path((name, reference)): Path<(String, String)>,
// Check if the user has permission to pull, or that the repository is public // Check if the user has permission to pull, or that the repository is public
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -111,7 +111,7 @@ pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String)
// Check if the user has permission to pull, or that the repository is public // Check if the user has permission to pull, or that the repository is public
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -151,7 +151,7 @@ pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String)
pub async fn delete_manifest(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> { pub async fn delete_manifest(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);

View File

@ -12,7 +12,7 @@ use futures::StreamExt;
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::app_state::AppState; use crate::app_state::AppState;
use crate::auth::unauthenticated_response; use crate::auth::access_denied_response;
use crate::byte_stream::ByteStream; use crate::byte_stream::ByteStream;
use crate::dto::user::{UserAuth, Permission}; use crate::dto::user::{UserAuth, Permission};
use crate::error::AppError; use crate::error::AppError;
@ -35,14 +35,13 @@ pub async fn start_upload_post(Path((name, )): Path<(String, )>, Extension(auth)
).into_response()); ).into_response());
} }
Ok(unauthenticated_response(&state.config)) Ok(access_denied_response(&state.config))
} }
pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, String)>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, mut body: BodyStream) -> Result<Response, AppError> { pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, String)>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, mut body: BodyStream) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
debug!("user is not authenticated"); return Ok(access_denied_response(&state.config));
return Ok(unauthenticated_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -101,7 +100,7 @@ pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String,
pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, String)>, Query(query): Query<HashMap<String, String>>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, body: Bytes) -> Result<Response, AppError> { pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, String)>, Query(query): Query<HashMap<String, String>>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, body: Bytes) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -130,7 +129,7 @@ pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, S
pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> { pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);
@ -144,7 +143,7 @@ pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String
pub async fn check_upload_status_get(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> { pub async fn check_upload_status_get(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
let mut auth_driver = state.auth_checker.lock().await; let mut auth_driver = state.auth_checker.lock().await;
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? { if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
return Ok(unauthenticated_response(&state.config)); return Ok(access_denied_response(&state.config));
} }
drop(auth_driver); drop(auth_driver);

View File

@ -130,6 +130,8 @@ pub async fn require_auth<B>(State(state): State<Arc<AppState>>, mut request: Re
} }
} }
/// Creates a response with an Unauthorized (401) status code.
/// The www-authenticate header is set to notify the client of where to authorize with.
#[inline(always)] #[inline(always)]
pub fn unauthenticated_response(config: &Config) -> Response { pub fn unauthenticated_response(config: &Config) -> Response {
let bearer = format!("Bearer realm=\"{}/auth\"", config.get_url()); let bearer = format!("Bearer realm=\"{}/auth\"", config.get_url());
@ -140,4 +142,16 @@ pub fn unauthenticated_response(config: &Config) -> Response {
( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() ) ( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() )
] ]
).into_response() ).into_response()
}
/// Creates a response with a Forbidden (403) status code.
/// No other headers are set.
#[inline(always)]
pub fn access_denied_response(config: &Config) -> Response {
(
StatusCode::FORBIDDEN,
[
( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() )
]
).into_response()
} }