1
0
mirror of https://github.com/openshift/image-registry.git synced 2026-02-05 09:45:55 +01:00
Files
Flavian Missi 018bd4544a pkg/dockerregistry/server: use SelfAccessReview api instead of users
the users api is specific to openshift, and is not available on every
openshift cluster, i.e when OIDC is configured with external users.
2024-06-14 14:43:41 +02:00

114 lines
3.4 KiB
Go

package server
import (
"context"
"encoding/json"
"fmt"
"net/http"
dcontext "github.com/distribution/distribution/v3/context"
"github.com/openshift/image-registry/pkg/dockerregistry/server/auth"
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type tokenHandler struct {
ctx context.Context
client client.RegistryClient
}
// NewTokenHandler returns a handler that implements the docker token protocol
func NewTokenHandler(ctx context.Context, client client.RegistryClient) http.Handler {
return &tokenHandler{
ctx: ctx,
client: client,
}
}
// bearer token issued to token requests that present no credentials
// recognized by the openshift auth provider as identifying the anonymous user
const anonymousToken = "anonymous"
func (t *tokenHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx := dcontext.WithRequest(t.ctx, req)
params := req.URL.Query()
if len(params.Get("scope")) > 0 {
accessRecords := auth.ResolveScopeSpecifiers(ctx, params["scope"])
for _, access := range accessRecords {
switch access.Resource.Type {
case "repository", "signature":
_, _, err := getNamespaceName(access.Resource.Name)
if err != nil {
dcontext.GetRequestLogger(ctx).Errorf("auth token request for unsupported resource name: %s", access.Resource.Name)
t.writeError(w, req, err.Error())
return
}
}
}
}
// If no authorization is provided, return a token the auth provider will treat as an anonymous user
if len(req.Header.Get("Authorization")) == 0 {
dcontext.GetRequestLogger(ctx).Debugf("anonymous token request")
t.writeToken(anonymousToken, w, req)
return
}
// use the password as the token
_, token, ok := req.BasicAuth()
if !ok {
dcontext.GetRequestLogger(ctx).Debugf("no basic auth credentials provided")
t.writeUnauthorized(w, req)
return
}
// TODO: if this doesn't validate as an API token, attempt to obtain an API token using the given username/password
osClient, err := t.client.ClientFromToken(token)
if err != nil {
dcontext.GetRequestLogger(ctx).Errorf("error building client: %v", err)
t.writeError(w, req, "invalid request")
return
}
if _, _, err := verifyOpenShiftUser(ctx, osClient); err != nil {
dcontext.GetRequestLogger(ctx).Errorf("invalid token: %v", err)
if kerrors.IsUnauthorized(err) {
t.writeUnauthorized(w, req)
} else {
msg := "unable to validate token"
if reason := kerrors.ReasonForError(err); reason != metav1.StatusReasonUnknown {
msg = fmt.Sprintf("%s: %s", msg, reason)
}
t.writeError(w, req, msg)
}
return
}
t.writeToken(token, w, req)
}
func (t *tokenHandler) writeError(w http.ResponseWriter, req *http.Request, msg string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(401)
// TODO(dmage): log error?
_ = json.NewEncoder(w).Encode(map[string]interface{}{"details": msg})
}
func (t *tokenHandler) writeToken(token string, w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(200)
// TODO(dmage): log error?
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"token": token,
"access_token": token,
})
}
func (t *tokenHandler) writeUnauthorized(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(401)
}