mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
77149aa85c
BREAKING CHANGE: The functionality provided by the mgmt endpoint has beed redesigned - see details below BREAKING CHANGE: The API keys endpoint has been moved - see details below BREAKING CHANGE: The mgmt extension config has been removed - endpoint is now enabled by having both the search and the ui extensions enabled BREAKING CHANGE: The API keys configuration has been moved from extensions to http>auth>apikey mgmt and imagetrust extensions: - separate the _zot/ext/mgmt into 3 separate endpoints: _zot/ext/auth, _zot/ext/notation, _zot/ext/cosign - signature verification logic is in a separate `imagetrust` extension - better hanling or errors in case of signature uploads: logging and error codes (more 400 and less 500 errors) - add authz on signature uploads (and add a new middleware in common for this purpose) - remove the mgmt extension configuration - it is now enabled if the UI and the search extensions are enabled userprefs estension: - userprefs are enabled if both search and ui extensions are enabled (as opposed to just search) apikey extension is removed and logic moved into the api folder - Move apikeys code out of pkg/extensions and into pkg/api - Remove apikey configuration options from the extensions configuration and move it inside the http auth section - remove the build label apikeys other changes: - move most of the logic adding handlers to the extensions endpoints out of routes.go and into the extensions files. - add warnings in case the users are still using configurations with the obsolete settings for mgmt and api keys - add a new function in the extension package which could be a single point of starting backgroud tasks for all extensions - more clear methods for verifying specific extensions are enabled - fix http methods paired with the UI handlers - rebuild swagger docs Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
183 lines
5.7 KiB
Go
183 lines
5.7 KiB
Go
//go:build imagetrust
|
|
// +build imagetrust
|
|
|
|
package extensions
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
"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/signatures"
|
|
mTypes "zotregistry.io/zot/pkg/meta/types"
|
|
"zotregistry.io/zot/pkg/scheduler"
|
|
)
|
|
|
|
const (
|
|
ConfigResource = "config"
|
|
SignaturesResource = "signatures"
|
|
)
|
|
|
|
func IsBuiltWithImageTrustExtension() bool {
|
|
return true
|
|
}
|
|
|
|
func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, log log.Logger) {
|
|
if !conf.IsImageTrustEnabled() || (!conf.IsCosignEnabled() && !conf.IsNotationEnabled()) {
|
|
log.Info().Msg("skip enabling the image trust routes as the config prerequisites are not met")
|
|
|
|
return
|
|
}
|
|
|
|
log.Info().Msg("setting up image trust routes")
|
|
|
|
trust := ImageTrust{Conf: conf, Log: log}
|
|
allowedMethods := zcommon.AllowedMethods(http.MethodPost)
|
|
|
|
if conf.IsNotationEnabled() {
|
|
log.Info().Msg("setting up notation route")
|
|
|
|
notationRouter := router.PathPrefix(constants.ExtNotation).Subrouter()
|
|
notationRouter.Use(zcommon.CORSHeadersMiddleware(conf.HTTP.AllowOrigin))
|
|
notationRouter.Use(zcommon.AddExtensionSecurityHeaders())
|
|
notationRouter.Use(zcommon.ACHeadersMiddleware(conf, allowedMethods...))
|
|
// The endpoints for uploading signatures should be available only to admins
|
|
notationRouter.Use(zcommon.AuthzOnlyAdminsMiddleware(conf))
|
|
notationRouter.Methods(allowedMethods...).HandlerFunc(trust.HandleNotationCertificateUpload)
|
|
}
|
|
|
|
if conf.IsCosignEnabled() {
|
|
log.Info().Msg("setting up cosign route")
|
|
|
|
cosignRouter := router.PathPrefix(constants.ExtCosign).Subrouter()
|
|
cosignRouter.Use(zcommon.CORSHeadersMiddleware(conf.HTTP.AllowOrigin))
|
|
cosignRouter.Use(zcommon.AddExtensionSecurityHeaders())
|
|
cosignRouter.Use(zcommon.ACHeadersMiddleware(conf, allowedMethods...))
|
|
// The endpoints for uploading signatures should be available only to admins
|
|
cosignRouter.Use(zcommon.AuthzOnlyAdminsMiddleware(conf))
|
|
cosignRouter.Methods(allowedMethods...).HandlerFunc(trust.HandleCosignPublicKeyUpload)
|
|
}
|
|
|
|
log.Info().Msg("finished setting up image trust routes")
|
|
}
|
|
|
|
type ImageTrust struct {
|
|
Conf *config.Config
|
|
Log log.Logger
|
|
}
|
|
|
|
// Cosign handler godoc
|
|
// @Summary Upload cosign public keys for verifying signatures
|
|
// @Description Upload cosign public keys for verifying signatures
|
|
// @Router /v2/_zot/ext/cosign [post]
|
|
// @Accept octet-stream
|
|
// @Produce json
|
|
// @Param requestBody body string true "Public key content"
|
|
// @Success 200 {string} string "ok"
|
|
// @Failure 400 {string} string "bad request".
|
|
// @Failure 500 {string} string "internal server error".
|
|
func (trust *ImageTrust) HandleCosignPublicKeyUpload(response http.ResponseWriter, request *http.Request) {
|
|
body, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
trust.Log.Error().Err(err).Msg("image trust: couldn't read cosign key body")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
err = signatures.UploadPublicKey(body)
|
|
if err != nil {
|
|
if errors.Is(err, zerr.ErrInvalidPublicKeyContent) {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
} else {
|
|
trust.Log.Error().Err(err).Msg("image trust: failed to save cosign key")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
// Notation handler godoc
|
|
// @Summary Upload notation certificates for verifying signatures
|
|
// @Description Upload notation certificates for verifying signatures
|
|
// @Router /v2/_zot/ext/notation [post]
|
|
// @Accept octet-stream
|
|
// @Produce json
|
|
// @Param truststoreType query string false "truststore type"
|
|
// @Param truststoreName query string false "truststore name"
|
|
// @Param requestBody body string true "Certificate content"
|
|
// @Success 200 {string} string "ok"
|
|
// @Failure 400 {string} string "bad request".
|
|
// @Failure 500 {string} string "internal server error".
|
|
func (trust *ImageTrust) HandleNotationCertificateUpload(response http.ResponseWriter, request *http.Request) {
|
|
var truststoreType string
|
|
|
|
if !zcommon.QueryHasParams(request.URL.Query(), []string{"truststoreName"}) {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
if zcommon.QueryHasParams(request.URL.Query(), []string{"truststoreType"}) {
|
|
truststoreType = request.URL.Query().Get("truststoreType")
|
|
} else {
|
|
truststoreType = "ca" // default value of "truststoreType" query param
|
|
}
|
|
|
|
truststoreName := request.URL.Query().Get("truststoreName")
|
|
|
|
if truststoreType == "" || truststoreName == "" {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
}
|
|
|
|
body, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
trust.Log.Error().Err(err).Msg("image trust: couldn't read notation certificate body")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
err = signatures.UploadCertificate(body, truststoreType, truststoreName)
|
|
if err != nil {
|
|
if errors.Is(err, zerr.ErrInvalidTruststoreType) ||
|
|
errors.Is(err, zerr.ErrInvalidTruststoreName) ||
|
|
errors.Is(err, zerr.ErrInvalidCertificateContent) {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
} else {
|
|
trust.Log.Error().Err(err).Msg("image trust: failed to save notation certificate")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func EnableImageTrustVerification(conf *config.Config, taskScheduler *scheduler.Scheduler,
|
|
metaDB mTypes.MetaDB, log log.Logger,
|
|
) {
|
|
if !conf.IsImageTrustEnabled() {
|
|
return
|
|
}
|
|
|
|
generator := signatures.NewTaskGenerator(metaDB, log)
|
|
|
|
numberOfHours := 2
|
|
interval := time.Duration(numberOfHours) * time.Minute
|
|
taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority)
|
|
}
|