From 3cbc5a12334ef3aa318bb8b1d3f0ad5438caa116 Mon Sep 17 00:00:00 2001 From: Baptiste Girard-Carrabin Date: Thu, 31 Jul 2025 12:36:45 +0200 Subject: [PATCH] [registry] Accept empty scope during token auth challenge The distribution spec (https://distribution.github.io/distribution/spec/auth/scope/#authorization-server-use) mentions that the access token provided during auth challenge "may include a scope" which means that it's not necessary to have one either to comply with the spec. Additionally, this is something that is already accepted by containerd which will simply log a warning when no scope is specified: https://github.com/containerd/containerd/blob/main/core/remotes/docker/auth/fetch.go#L64 To match with what containerd and the spec suggest, the commit modifies the `parse_auth` logic to accept an empty `scope` field. It also logs the same warning as containerd. Signed-off-by: Baptiste Girard-Carrabin --- storage/src/backend/registry.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/storage/src/backend/registry.rs b/storage/src/backend/registry.rs index f8246792798..eb2827b921a 100644 --- a/storage/src/backend/registry.rs +++ b/storage/src/backend/registry.rs @@ -436,17 +436,21 @@ impl RegistryState { Some(Auth::Basic(BasicAuth { realm })) } "Bearer" => { - if !paras.contains_key("realm") - || !paras.contains_key("service") - || !paras.contains_key("scope") - { + if !paras.contains_key("realm") || !paras.contains_key("service") { return None; } + let scope = if let Some(scope) = paras.get("scope") { + (*scope).to_string() + } else { + debug!("no scope specified for token auth challenge"); + String::new() + }; + Some(Auth::Bearer(BearerAuth { realm: (*paras.get("realm").unwrap()).to_string(), service: (*paras.get("service").unwrap()).to_string(), - scope: (*paras.get("scope").unwrap()).to_string(), + scope, })) } _ => None, @@ -1114,12 +1118,25 @@ mod tests { _ => panic!("failed to parse `Bearer` authentication header"), } + // No scope is accetpable + let str = "Bearer realm=\"https://auth.my-registry.com/token\",service=\"my-registry.com\""; + let header = HeaderValue::from_str(str).unwrap(); + let auth = RegistryState::parse_auth(&header).unwrap(); + match auth { + Auth::Bearer(auth) => { + assert_eq!(&auth.realm, "https://auth.my-registry.com/token"); + assert_eq!(&auth.service, "my-registry.com"); + assert_eq!(&auth.scope, ""); + } + _ => panic!("failed to parse `Bearer` authentication header without scope"), + } + let str = "Basic realm=\"https://auth.my-registry.com/token\""; let header = HeaderValue::from_str(str).unwrap(); let auth = RegistryState::parse_auth(&header).unwrap(); match auth { Auth::Basic(auth) => assert_eq!(&auth.realm, "https://auth.my-registry.com/token"), - _ => panic!("failed to parse `Bearer` authentication header"), + _ => panic!("failed to parse `Basic` authentication header"), } let str = "Base realm=\"https://auth.my-registry.com/token\"";