Use absolute realm URL in WWW-Authenticate and add service_name

OCI clients (podman, docker) require an absolute URL in the
WWW-Authenticate realm. Derive it from the request Host header
so it works behind any proxy. Add service_name to rift config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 22:41:36 -07:00
parent 7f673e8ef0
commit fa35899443
4 changed files with 9 additions and 4 deletions

View File

@@ -26,6 +26,7 @@ uploads_path = "/srv/mcr/uploads"
[mcias] [mcias]
server_url = "https://mcias.metacircular.net:8443" server_url = "https://mcias.metacircular.net:8443"
ca_cert = "/srv/mcr/certs/ca.pem" ca_cert = "/srv/mcr/certs/ca.pem"
service_name = "mcr"
service_token = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL21jaWFzLm1ldGFjaXJjdWxhci5uZXQiLCJzdWIiOiIwYWM3NDk3ZS0wZTE5LTRhOWMtYWI3Yi03YWZjMzc0ZDU3NzIiLCJleHAiOjE4MDYwMzczNzMsIm5iZiI6MTc3NDUwMTM3MywiaWF0IjoxNzc0NTAxMzczLCJqdGkiOiI1NTM0ZDU0OS1kYzY5LTRiNzctYTY5MC0xNzQ3NjE0MDUzYzEiLCJyb2xlcyI6bnVsbH0.bsnoGMrFzJJCIanGuiAvpqmlO2OssvFjYynQgiSt_TPMuLxziRuwuRIL9C_kRnHdF7C6c1mTHncKVj1hkLPiCg" service_token = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL21jaWFzLm1ldGFjaXJjdWxhci5uZXQiLCJzdWIiOiIwYWM3NDk3ZS0wZTE5LTRhOWMtYWI3Yi03YWZjMzc0ZDU3NzIiLCJleHAiOjE4MDYwMzczNzMsIm5iZiI6MTc3NDUwMTM3MywiaWF0IjoxNzc0NTAxMzczLCJqdGkiOiI1NTM0ZDU0OS1kYzY5LTRiNzctYTY5MC0xNzQ3NjE0MDUzYzEiLCJyb2xlcyI6bnVsbH0.bsnoGMrFzJJCIanGuiAvpqmlO2OssvFjYynQgiSt_TPMuLxziRuwuRIL9C_kRnHdF7C6c1mTHncKVj1hkLPiCg"
[web] [web]

View File

@@ -19,10 +19,14 @@ type TokenValidator interface {
// into the request context. On failure a 401 with an OCI-format error // into the request context. On failure a 401 with an OCI-format error
// body and a WWW-Authenticate header is returned. // body and a WWW-Authenticate header is returned.
func RequireAuth(validator TokenValidator, serviceName string) func(http.Handler) http.Handler { func RequireAuth(validator TokenValidator, serviceName string) func(http.Handler) http.Handler {
wwwAuth := fmt.Sprintf(`Bearer realm="/v2/token",service="%s"`, serviceName)
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Build the WWW-Authenticate header with an absolute realm URL
// derived from the request Host, per OCI Distribution Spec.
scheme := "https"
realm := fmt.Sprintf("%s://%s/v2/token", scheme, r.Host)
wwwAuth := fmt.Sprintf(`Bearer realm="%s",service="%s"`, realm, serviceName)
token := extractBearerToken(r) token := extractBearerToken(r)
if token == "" { if token == "" {
w.Header().Set("WWW-Authenticate", wwwAuth) w.Header().Set("WWW-Authenticate", wwwAuth)

View File

@@ -68,7 +68,7 @@ func TestRequireAuthMissing(t *testing.T) {
} }
wwwAuth := rec.Header().Get("WWW-Authenticate") wwwAuth := rec.Header().Get("WWW-Authenticate")
want := `Bearer realm="/v2/token",service="mcr-test"` want := `Bearer realm="https://example.com/v2/token",service="mcr-test"`
if wwwAuth != want { if wwwAuth != want {
t.Fatalf("WWW-Authenticate: got %q, want %q", wwwAuth, want) t.Fatalf("WWW-Authenticate: got %q, want %q", wwwAuth, want)
} }

View File

@@ -55,7 +55,7 @@ func TestRoutesV2Unauthenticated(t *testing.T) {
} }
wwwAuth := rec.Header().Get("WWW-Authenticate") wwwAuth := rec.Header().Get("WWW-Authenticate")
want := `Bearer realm="/v2/token",service="mcr-test"` want := `Bearer realm="https://example.com/v2/token",service="mcr-test"`
if wwwAuth != want { if wwwAuth != want {
t.Fatalf("WWW-Authenticate: got %q, want %q", wwwAuth, want) t.Fatalf("WWW-Authenticate: got %q, want %q", wwwAuth, want)
} }