mirror of
https://github.com/project-zot/zot.git
synced 2025-01-13 22:50:38 -05:00
17d1338af1
This change introduces OpenID authn by using providers such as Github, Gitlab, Google and Dex. User sessions are now used for web clients to identify and persist an authenticated users session, thus not requiring every request to use credentials. Another change is apikey feature, users can create/revoke their api keys and use them to authenticate when using cli clients such as skopeo. eg: login: /auth/login?provider=github /auth/login?provider=gitlab and so on logout: /auth/logout redirectURL: /auth/callback/github /auth/callback/gitlab and so on If network policy doesn't allow inbound connections, this callback wont work! for more info read documentation added in this commit. Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro> Signed-off-by: Petu Eusebiu <peusebiu@cisco.com> Co-authored-by: Alex Stan <alexandrustan96@yahoo.ro>
197 lines
5.2 KiB
Go
197 lines
5.2 KiB
Go
//go:build apikey
|
|
// +build apikey
|
|
|
|
package extensions
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
guuid "github.com/gofrs/uuid"
|
|
"github.com/gorilla/mux"
|
|
"github.com/gorilla/sessions"
|
|
jsoniter "github.com/json-iterator/go"
|
|
godigest "github.com/opencontainers/go-digest"
|
|
|
|
"zotregistry.io/zot/pkg/api/config"
|
|
"zotregistry.io/zot/pkg/api/constants"
|
|
zcommon "zotregistry.io/zot/pkg/common"
|
|
"zotregistry.io/zot/pkg/log"
|
|
"zotregistry.io/zot/pkg/meta/repodb"
|
|
)
|
|
|
|
func SetupAPIKeyRoutes(config *config.Config, router *mux.Router, repoDB repodb.RepoDB,
|
|
cookieStore sessions.Store, log log.Logger,
|
|
) {
|
|
if config.Extensions.APIKey != nil && *config.Extensions.APIKey.Enable {
|
|
log.Info().Msg("setting up api key routes")
|
|
|
|
allowedMethods := zcommon.AllowedMethods(http.MethodPost, http.MethodDelete)
|
|
|
|
apiKeyRouter := router.PathPrefix(constants.ExtAPIKey).Subrouter()
|
|
apiKeyRouter.Use(zcommon.ACHeadersHandler(allowedMethods...))
|
|
apiKeyRouter.Use(zcommon.AddExtensionSecurityHeaders())
|
|
apiKeyRouter.Methods(allowedMethods...).Handler(HandleAPIKeyRequest(repoDB, cookieStore, log))
|
|
}
|
|
}
|
|
|
|
type APIKeyPayload struct { //nolint:revive
|
|
Label string `json:"label"`
|
|
Scopes []string `json:"scopes"`
|
|
}
|
|
|
|
func HandleAPIKeyRequest(repoDB repodb.RepoDB, cookieStore sessions.Store,
|
|
log log.Logger,
|
|
) http.Handler {
|
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
|
switch req.Method {
|
|
case http.MethodPost:
|
|
CreateAPIKey(resp, req, repoDB, cookieStore, log) //nolint:contextcheck
|
|
|
|
return
|
|
case http.MethodDelete:
|
|
RevokeAPIKey(resp, req, repoDB, cookieStore, log) //nolint:contextcheck
|
|
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// CreateAPIKey godoc
|
|
// @Summary Create an API key for the current user
|
|
// @Description Can create an api key for a logged in user, based on the provided label and scopes.
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Success 201 {string} string "created"
|
|
// @Failure 401 {string} string "unauthorized"
|
|
// @Failure 500 {string} string "internal server error"
|
|
// @Router /v2/_zot/ext/apikey [post].
|
|
func CreateAPIKey(resp http.ResponseWriter, req *http.Request, repoDB repodb.RepoDB,
|
|
cookieStore sessions.Store, log log.Logger,
|
|
) {
|
|
var payload APIKeyPayload
|
|
|
|
body, err := io.ReadAll(req.Body)
|
|
if err != nil {
|
|
log.Error().Msg("unable to read request body")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
err = json.Unmarshal(body, &payload)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to unmarshal body")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
apiKeyBase, err := guuid.NewV4()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to generate uuid")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
apiKey := strings.ReplaceAll(apiKeyBase.String(), "-", "")
|
|
|
|
hashedAPIKey := hashUUID(apiKey)
|
|
|
|
// will be used for identifying a specific api key
|
|
apiKeyID, err := guuid.NewV4()
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to generate uuid")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
apiKeyDetails := &repodb.APIKeyDetails{
|
|
CreatedAt: time.Now(),
|
|
LastUsed: time.Now(),
|
|
CreatorUA: req.UserAgent(),
|
|
GeneratedBy: "manual",
|
|
Label: payload.Label,
|
|
Scopes: payload.Scopes,
|
|
UUID: apiKeyID.String(),
|
|
}
|
|
|
|
err = repoDB.AddUserAPIKey(req.Context(), hashedAPIKey, apiKeyDetails)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("error storing API key")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
apiKeyResponse := struct {
|
|
repodb.APIKeyDetails
|
|
APIKey string `json:"apiKey"`
|
|
}{
|
|
APIKey: fmt.Sprintf("%s%s", constants.APIKeysPrefix, apiKey),
|
|
APIKeyDetails: *apiKeyDetails,
|
|
}
|
|
|
|
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
|
|
|
data, err := json.Marshal(apiKeyResponse)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("unable to marshal api key response")
|
|
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
resp.Header().Set("Content-Type", constants.DefaultMediaType)
|
|
resp.WriteHeader(http.StatusCreated)
|
|
_, _ = resp.Write(data)
|
|
}
|
|
|
|
// RevokeAPIKey godoc
|
|
// @Summary Revokes one current user API key
|
|
// @Description Revokes one current user API key based on given key ID
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path string true "api token id (UUID)"
|
|
// @Success 200 {string} string "ok"
|
|
// @Failure 500 {string} string "internal server error"
|
|
// @Failure 401 {string} string "unauthorized"
|
|
// @Failure 400 {string} string "bad request"
|
|
// @Router /v2/_zot/ext/apikey?id=UUID [delete].
|
|
func RevokeAPIKey(resp http.ResponseWriter, req *http.Request, repoDB repodb.RepoDB,
|
|
cookieStore sessions.Store, log log.Logger,
|
|
) {
|
|
ids, ok := req.URL.Query()["id"]
|
|
if !ok || len(ids) != 1 {
|
|
resp.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
keyID := ids[0]
|
|
|
|
err := repoDB.DeleteUserAPIKey(req.Context(), keyID)
|
|
if err != nil {
|
|
log.Error().Err(err).Str("keyID", keyID).Msg("error deleting API key")
|
|
resp.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
resp.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func hashUUID(uuid string) string {
|
|
digester := sha256.New()
|
|
digester.Write([]byte(uuid))
|
|
|
|
return godigest.NewDigestFromEncoded(godigest.SHA256, fmt.Sprintf("%x", digester.Sum(nil))).Encoded()
|
|
}
|