Files
zot/pkg/extensions/extension_image_trust.go
T
Andrei Aaron dfb5d1df54 fix: make config read/write thread safe (#3432)
* fix: make config read/write thread safe and fix some other similar issues

1. The config config has a lock, and safe methods to update and read the attributes
2. The config has methods to retrieve copies of specific attributes, such as the extyensions config, the auth config, and the authz config.
These are needed, as the config object may mutate in the middle of an auth/authz requests, and we avoid partial configuration being applied for that request.
3. Fix an issue with the monitoring server not stopping when the controller is shut down.
4. Fix an issue with the HTPasswdWatcher not stopping when the background tasks are supposed to finish.
5. Fix some tests using hardcoded ports.

Moved some of the methods which were on the main config to the auth, access control and extension configs

Signed-off-by: Andrei Aaron <andreifdaaron@gmail.com>
2025-10-18 11:20:58 +03:00

204 lines
6.9 KiB
Go

//go:build imagetrust
// +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
}
// 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).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)
}
// 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 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
}