From 00060ff73c12f468008b0fb7e9b4fd320861b7fd Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Thu, 19 Mar 2026 07:43:44 +0800 Subject: [PATCH] Make container registry support Apple Container (basic auth) (#36920) Fix #36907 --- routers/api/packages/api.go | 24 ++++++++++++++----- routers/api/packages/container/container.go | 10 ++++---- .../api_packages_container_test.go | 5 +++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 7f81242db4..44bb80018b 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -88,7 +88,11 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { } } -func verifyAuth(r *web.Router, authMethods []auth.Method) { +type verifyAuthOptions struct { + afterAuthCallback func(ctx *context.Context, err error) +} + +func verifyAuth(r *web.Router, authMethods []auth.Method, opts verifyAuthOptions) { if setting.Service.EnableReverseProxyAuth { authMethods = append(authMethods, &auth.ReverseProxy{}) } @@ -97,12 +101,13 @@ func verifyAuth(r *web.Router, authMethods []auth.Method) { r.AfterRouting(func(ctx *context.Context) { var err error ctx.Doer, err = authGroup.Verify(ctx.Req, ctx.Resp, ctx, ctx.Session) - if err != nil { + ctx.IsSigned = ctx.Doer != nil + if opts.afterAuthCallback != nil { + opts.afterAuthCallback(ctx, err) + } else if err != nil { log.Error("Failed to verify user: %v", err) ctx.HTTPError(http.StatusUnauthorized, "Failed to authenticate user") - return } - ctx.IsSigned = ctx.Doer != nil }) } @@ -119,7 +124,7 @@ func CommonRoutes() *web.Router { &nuget.Auth{}, &Auth{}, &chef.Auth{}, - }) + }, verifyAuthOptions{}) r.Group("/{username}", func() { r.Group("/alpine", func() { @@ -537,8 +542,15 @@ func ContainerRoutes() *web.Router { verifyAuth(r, []auth.Method{ &auth.Basic{}, - // container auth requires an token, so container.Authenticate issues a Ghost user token for anonymous access + // container auth requires token, so container.Authenticate issues a Ghost user token for anonymous access &Auth{AllowGhostUser: true}, + }, verifyAuthOptions{ + afterAuthCallback: func(ctx *context.Context, err error) { + if err != nil { + log.Error("Failed to verify container user: %v", err) + container.APIUnauthorizedError(ctx) + } + }, }) // TODO: Content Discovery / References (not implemented yet) diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go index 7cf1c36375..a6512181e0 100644 --- a/routers/api/packages/container/container.go +++ b/routers/api/packages/container/container.go @@ -120,17 +120,19 @@ func apiErrorDefined(ctx *context.Context, err *namedError) { }) } -func apiUnauthorizedError(ctx *context.Context) { +func APIUnauthorizedError(ctx *context.Context) { // container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed realmURL := httplib.GuessCurrentHostURL(ctx) + "/v2/token" ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`) + // support apple container like: container registry login -u + ctx.Resp.Header().Add("WWW-Authenticate", `Basic realm="Gitea Container Registry"`) apiErrorDefined(ctx, errUnauthorized) } // ReqContainerAccess is a middleware which checks the current user valid (real user or ghost if anonymous access is enabled) func ReqContainerAccess(ctx *context.Context) { if ctx.Doer == nil || (setting.Service.RequireSignInViewStrict && ctx.Doer.IsGhost()) { - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) } } @@ -156,7 +158,7 @@ func Authenticate(ctx *context.Context) { packageScope := auth_service.GetAccessScope(ctx.Data) if u == nil { if setting.Service.RequireSignInViewStrict { - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) return } @@ -170,7 +172,7 @@ func Authenticate(ctx *context.Context) { if err != nil { log.Error("Error checking access scope: %v", err) } - apiUnauthorizedError(ctx) + APIUnauthorizedError(ctx) return } } diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index efa04684af..d5d243f9ab 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -88,7 +88,10 @@ func TestPackageContainer(t *testing.T) { Token string `json:"token"` } - defaultAuthenticateValues := []string{`Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`} + defaultAuthenticateValues := []string{ + `Bearer realm="` + setting.AppURL + `v2/token",service="container_registry",scope="*"`, + `Basic realm="Gitea Container Registry"`, + } t.Run("Anonymous", func(t *testing.T) { defer tests.PrintCurrentTest(t)()