mirror of
https://github.com/project-zot/zot.git
synced 2026-06-18 05:28:07 +08:00
a5cc8ab810
* feat: support pushing multiple tags for a single manifest See https://github.com/opencontainers/distribution-spec/pull/600 Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> * fix: constants not replaced in swagger output Also godot mandates comments ending in dots, which produces bad results in the swagger generated files, see the extra ". which is now fixed below: ``` diff --git a/swagger/docs.go b/swagger/docs.go index 84b08277..fb2c45c3 100644 --- a/swagger/docs.go +++ b/swagger/docs.go @@ -114,7 +114,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -200,7 +200,7 @@ const docTemplate = `{ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.json b/swagger/swagger.json index cfeb3900..247f95fa 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -106,7 +106,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } @@ -192,7 +192,7 @@ } }, "400": { - "description": "bad request\".", + "description": "bad request", "schema": { "type": "string" } diff --git a/swagger/swagger.yaml b/swagger/swagger.yaml index 57641c2f..09b30dcc 100644 --- a/swagger/swagger.yaml +++ b/swagger/swagger.yaml @@ -310,7 +310,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": @@ -366,7 +366,7 @@ paths: schema: type: string "400": - description: bad request". + description: bad request schema: type: string "500": ``` Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com> --------- Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
203 lines
6.9 KiB
Go
203 lines
6.9 KiB
Go
//go:build imagetrust
|
|
|
|
package extensions
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/mux"
|
|
|
|
zerr "zotregistry.dev/zot/v2/errors"
|
|
"zotregistry.dev/zot/v2/pkg/api/config"
|
|
"zotregistry.dev/zot/v2/pkg/api/constants"
|
|
zcommon "zotregistry.dev/zot/v2/pkg/common"
|
|
"zotregistry.dev/zot/v2/pkg/extensions/imagetrust"
|
|
"zotregistry.dev/zot/v2/pkg/log"
|
|
mTypes "zotregistry.dev/zot/v2/pkg/meta/types"
|
|
"zotregistry.dev/zot/v2/pkg/scheduler"
|
|
sconstants "zotregistry.dev/zot/v2/pkg/storage/constants"
|
|
)
|
|
|
|
func IsBuiltWithImageTrustExtension() bool {
|
|
return true
|
|
}
|
|
|
|
func SetupImageTrustRoutes(conf *config.Config, router *mux.Router, metaDB mTypes.MetaDB, log log.Logger) {
|
|
extensionsConfig := conf.CopyExtensionsConfig()
|
|
if !extensionsConfig.IsImageTrustEnabled() ||
|
|
(!extensionsConfig.IsCosignEnabled() && !extensionsConfig.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")
|
|
|
|
imgTrustStore, _ := metaDB.ImageTrustStore().(*imagetrust.ImageTrustStore)
|
|
trust := ImageTrust{Conf: conf, ImageTrustStore: imgTrustStore, Log: log}
|
|
allowedMethods := zcommon.AllowedMethods(http.MethodPost)
|
|
|
|
if extensionsConfig.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 extensionsConfig.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
|
|
ImageTrustStore *imagetrust.ImageTrustStore
|
|
Log log.Logger
|
|
}
|
|
|
|
// HandleCosignPublicKeyUpload 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).Str("component", "image-trust").Msg("failed to read cosign key body")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
err = imagetrust.UploadPublicKey(trust.ImageTrustStore.CosignStorage, body)
|
|
if err != nil {
|
|
if errors.Is(err, zerr.ErrInvalidPublicKeyContent) {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
} else {
|
|
trust.Log.Error().Err(err).Str("component", "image-trust").Msg("failed to save cosign key")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
response.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
// HandleNotationCertificateUpload 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 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{"truststoreType"}) {
|
|
truststoreType = request.URL.Query().Get("truststoreType")
|
|
} else {
|
|
truststoreType = "ca" // default value of "truststoreType" query param
|
|
}
|
|
|
|
body, err := io.ReadAll(request.Body)
|
|
if err != nil {
|
|
trust.Log.Error().Err(err).Str("component", "image-trust").Msg("failed to read notation certificate body")
|
|
response.WriteHeader(http.StatusInternalServerError)
|
|
|
|
return
|
|
}
|
|
|
|
err = imagetrust.UploadCertificate(trust.ImageTrustStore.NotationStorage, body, truststoreType)
|
|
if err != nil {
|
|
if errors.Is(err, zerr.ErrInvalidTruststoreType) ||
|
|
errors.Is(err, zerr.ErrInvalidCertificateContent) {
|
|
response.WriteHeader(http.StatusBadRequest)
|
|
} else {
|
|
trust.Log.Error().Err(err).Str("component", "image-trust").Msg("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,
|
|
) {
|
|
extensionsConfig := conf.CopyExtensionsConfig()
|
|
if !extensionsConfig.IsImageTrustEnabled() {
|
|
return
|
|
}
|
|
|
|
generator := imagetrust.NewTaskGenerator(metaDB, log)
|
|
|
|
numberOfHours := 2
|
|
interval := time.Duration(numberOfHours) * time.Hour
|
|
taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority)
|
|
}
|
|
|
|
func SetupImageTrustExtension(conf *config.Config, metaDB mTypes.MetaDB, log log.Logger) error {
|
|
extensionsConfig := conf.CopyExtensionsConfig()
|
|
if !extensionsConfig.IsImageTrustEnabled() {
|
|
return nil
|
|
}
|
|
|
|
var imgTrustStore mTypes.ImageTrustStore
|
|
|
|
var err error
|
|
|
|
if conf.Storage.RemoteCache && conf.Storage.CacheDriver["name"] == sconstants.DynamoDBDriverName {
|
|
// AWS secrets manager
|
|
// In case of AWS let's assume if dynamodDB is used, the AWS secrets manager is also used
|
|
// we use the CacheDriver settings as opposed to the storage settings because we want to
|
|
// be able to use S3/minio and redis in the same configuration
|
|
endpoint, _ := conf.Storage.CacheDriver["endpoint"].(string)
|
|
region, _ := conf.Storage.CacheDriver["region"].(string)
|
|
|
|
imgTrustStore, err = imagetrust.NewAWSImageTrustStore(region, endpoint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Store secrets on the local disk
|
|
imgTrustStore, err = imagetrust.NewLocalImageTrustStore(conf.Storage.RootDirectory)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
metaDB.SetImageTrustStore(imgTrustStore)
|
|
|
|
return nil
|
|
}
|