package auth import ( "crypto" "fmt" "net/http" "net/http/httptest" "os" "regexp" "strings" "time" "github.com/golang-jwt/jwt/v5" "github.com/mitchellh/mapstructure" "zotregistry.dev/zot/v2/pkg/api" ) type ( AccessTokenResponse struct { AccessToken string `json:"access_token"` //nolint:tagliatelle // token format } AuthHeader struct { Realm string Service string Scope string } ) func MakeAuthTestServer(serverKey, signAlg string, unauthorizedNamespace string) *httptest.Server { signingKey := loadPrivateKeyFromFile(serverKey) signingMethod := jwt.GetSigningMethod(signAlg) authTestServer := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { if request.Method != http.MethodGet { response.WriteHeader(http.StatusMethodNotAllowed) return } var access []api.ResourceAccess scope := request.URL.Query().Get("scope") if scope != "" { parts := strings.Split(scope, ":") name := parts[1] actions := strings.Split(parts[2], ",") if name == unauthorizedNamespace { actions = []string{} } access = []api.ResourceAccess{ { Name: name, Type: "repository", Actions: actions, }, } } now := time.Now() claims := api.ClaimsWithAccess{ Access: access, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(time.Minute * 1)), IssuedAt: jwt.NewNumericDate(now), Issuer: "Zot", Audience: []string{"Zot Registry"}, }, } token := jwt.NewWithClaims(signingMethod, claims) signedString, err := token.SignedString(signingKey) if err != nil { panic(err) } response.Header().Set("Content-Type", "application/json") fmt.Fprintf(response, `{"access_token": "%s"}`, signedString) })) return authTestServer } func ParseBearerAuthHeader(authHeaderRaw string) *AuthHeader { re := regexp.MustCompile(`([a-zA-z]+)="(.+?)"`) matches := re.FindAllStringSubmatch(authHeaderRaw, -1) matchmap := make(map[string]string) for i := range matches { matchmap[matches[i][1]] = matches[i][2] } var h AuthHeader if err := mapstructure.Decode(matchmap, &h); err != nil { panic(err) } return &h } func loadPrivateKeyFromFile(path string) crypto.PrivateKey { privateKeyBytes, err := os.ReadFile(path) if err != nil { panic(err) } rsaKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyBytes) if err == nil { return rsaKey } ecKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyBytes) if err == nil { return ecKey } edKey, err := jwt.ParseEdPrivateKeyFromPEM(privateKeyBytes) if err == nil { return edKey } panic("no valid private key found in file " + path) }