mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 20:38:08 +08:00
feat: upload certificates and public keys for verifying signatures (#1485)
In order to verify signatures, users could upload their certificates and public keys using these routes: -> for public keys: /v2/_zot/ext/mgmt?resource=signatures&tool=cosign -> for certificates: /v2/_zot/ext/mgmt?resource=signatures&tool=notation&truststoreType=ca&truststoreName=name Then the public keys will be stored under $rootdir/_cosign and the certificates will be stored under $rootdir/_notation/truststore/x509/$truststoreType/$truststoreName. Also, for notation case, the "truststores" field of $rootir/_notation/trustpolicy.json file will be updated with a new entry "$truststoreType:$truststoreName". Also based on the uploaded files, the information about the signatures validity will be updated periodically. Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
@@ -4,15 +4,27 @@
|
||||
package extensions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"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"
|
||||
"zotregistry.io/zot/pkg/meta/signatures"
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
)
|
||||
|
||||
const (
|
||||
ConfigResource = "config"
|
||||
SignaturesResource = "signatures"
|
||||
)
|
||||
|
||||
type HTPasswd struct {
|
||||
@@ -70,23 +82,38 @@ type mgmt struct {
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
// mgmtHandler godoc
|
||||
// @Summary Get current server configuration
|
||||
// @Description Get current server configuration
|
||||
// @Router /v2/_zot/ext/mgmt [get]
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} extensions.StrippedConfig
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
func (mgmt *mgmt) handler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
sanitizedConfig := mgmt.config.Sanitize()
|
||||
buf, err := zcommon.MarshalThroughStruct(sanitizedConfig, &StrippedConfig{})
|
||||
if err != nil {
|
||||
mgmt.log.Error().Err(err).Msg("mgmt: couldn't marshal config response")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
var resource string
|
||||
|
||||
if queryHasParams(r.URL.Query(), []string{"resource"}) {
|
||||
resource = r.URL.Query().Get("resource")
|
||||
} else {
|
||||
resource = ConfigResource // default value of "resource" query param
|
||||
}
|
||||
|
||||
switch resource {
|
||||
case ConfigResource:
|
||||
if r.Method == http.MethodGet {
|
||||
mgmt.HandleGetConfig(w, r)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return
|
||||
case SignaturesResource:
|
||||
if r.Method == http.MethodPost {
|
||||
HandleCertificatesAndPublicKeysUploads(w, r) //nolint: contextcheck
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
|
||||
return
|
||||
default:
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(buf)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,7 +123,7 @@ func SetupMgmtRoutes(config *config.Config, router *mux.Router, log log.Logger)
|
||||
|
||||
mgmt := mgmt{config: config, log: log}
|
||||
|
||||
allowedMethods := zcommon.AllowedMethods(http.MethodGet)
|
||||
allowedMethods := zcommon.AllowedMethods(http.MethodGet, http.MethodPost)
|
||||
|
||||
mgmtRouter := router.PathPrefix(constants.ExtMgmt).Subrouter()
|
||||
mgmtRouter.Use(zcommon.ACHeadersHandler(allowedMethods...))
|
||||
@@ -104,3 +131,194 @@ func SetupMgmtRoutes(config *config.Config, router *mux.Router, log log.Logger)
|
||||
mgmtRouter.Methods(allowedMethods...).Handler(mgmt.handler())
|
||||
}
|
||||
}
|
||||
|
||||
// mgmtHandler godoc
|
||||
// @Summary Get current server configuration
|
||||
// @Description Get current server configuration
|
||||
// @Router /v2/_zot/ext/mgmt [get]
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param resource query string false "specify resource" Enums(config)
|
||||
// @Success 200 {object} extensions.StrippedConfig
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
func (mgmt *mgmt) HandleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
sanitizedConfig := mgmt.config.Sanitize()
|
||||
|
||||
buf, err := zcommon.MarshalThroughStruct(sanitizedConfig, &StrippedConfig{})
|
||||
if err != nil {
|
||||
mgmt.log.Error().Err(err).Msg("mgmt: couldn't marshal config response")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
_, _ = w.Write(buf)
|
||||
}
|
||||
|
||||
// mgmtHandler godoc
|
||||
// @Summary Upload certificates and public keys for verifying signatures
|
||||
// @Description Upload certificates and public keys for verifying signatures
|
||||
// @Router /v2/_zot/ext/mgmt [post]
|
||||
// @Accept octet-stream
|
||||
// @Produce json
|
||||
// @Param resource query string true "specify resource" Enums(signatures)
|
||||
// @Param tool query string true "specify signing tool" Enums(cosign, notation)
|
||||
// @Param truststoreType query string false "truststore type"
|
||||
// @Param truststoreName query string false "truststore name"
|
||||
// @Param requestBody body string true "Public key or Certificate content"
|
||||
// @Success 200 {string} string "ok"
|
||||
// @Failure 400 {string} string "bad request".
|
||||
// @Failure 500 {string} string "internal server error".
|
||||
func HandleCertificatesAndPublicKeysUploads(response http.ResponseWriter, request *http.Request) {
|
||||
if !queryHasParams(request.URL.Query(), []string{"tool"}) {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
tool := request.URL.Query().Get("tool")
|
||||
|
||||
switch tool {
|
||||
case signatures.CosignSignature:
|
||||
err := signatures.UploadPublicKey(body)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
case signatures.NotationSignature:
|
||||
var truststoreType string
|
||||
|
||||
if !queryHasParams(request.URL.Query(), []string{"truststoreName"}) {
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if 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
|
||||
}
|
||||
|
||||
err = signatures.UploadCertificate(body, truststoreType, truststoreName)
|
||||
if err != nil {
|
||||
response.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
default:
|
||||
response.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func EnablePeriodicSignaturesVerification(config *config.Config, taskScheduler *scheduler.Scheduler,
|
||||
repoDB repodb.RepoDB, log log.Logger,
|
||||
) {
|
||||
if config.Extensions.Search != nil && *config.Extensions.Search.Enable {
|
||||
ctx := context.Background()
|
||||
|
||||
repos, err := repoDB.GetMultipleRepoMeta(ctx, func(repoMeta repodb.RepoMetadata) bool {
|
||||
return true
|
||||
}, repodb.PageInput{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
generator := &taskGeneratorSigValidity{
|
||||
repos: repos,
|
||||
repoDB: repoDB,
|
||||
repoIndex: -1,
|
||||
log: log,
|
||||
}
|
||||
|
||||
numberOfHours := 2
|
||||
interval := time.Duration(numberOfHours) * time.Minute
|
||||
taskScheduler.SubmitGenerator(generator, interval, scheduler.MediumPriority)
|
||||
}
|
||||
}
|
||||
|
||||
type taskGeneratorSigValidity struct {
|
||||
repos []repodb.RepoMetadata
|
||||
repoDB repodb.RepoDB
|
||||
repoIndex int
|
||||
done bool
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (gen *taskGeneratorSigValidity) Next() (scheduler.Task, error) {
|
||||
gen.repoIndex++
|
||||
|
||||
if gen.repoIndex >= len(gen.repos) {
|
||||
gen.done = true
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewValidityTask(gen.repoDB, gen.repos[gen.repoIndex], gen.log), nil
|
||||
}
|
||||
|
||||
func (gen *taskGeneratorSigValidity) IsDone() bool {
|
||||
return gen.done
|
||||
}
|
||||
|
||||
func (gen *taskGeneratorSigValidity) Reset() {
|
||||
gen.done = false
|
||||
gen.repoIndex = -1
|
||||
ctx := context.Background()
|
||||
|
||||
repos, err := gen.repoDB.GetMultipleRepoMeta(ctx, func(repoMeta repodb.RepoMetadata) bool {
|
||||
return true
|
||||
}, repodb.PageInput{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
gen.repos = repos
|
||||
}
|
||||
|
||||
type validityTask struct {
|
||||
repoDB repodb.RepoDB
|
||||
repo repodb.RepoMetadata
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewValidityTask(repoDB repodb.RepoDB, repo repodb.RepoMetadata, log log.Logger) *validityTask {
|
||||
return &validityTask{repoDB, repo, log}
|
||||
}
|
||||
|
||||
func (validityT *validityTask) DoWork() error {
|
||||
validityT.log.Info().Msg("updating signatures validity")
|
||||
|
||||
for signedManifest, sigs := range validityT.repo.Signatures {
|
||||
if len(sigs[signatures.CosignSignature]) != 0 || len(sigs[signatures.NotationSignature]) != 0 {
|
||||
err := validityT.repoDB.UpdateSignaturesValidity(validityT.repo.Name, digest.Digest(signedManifest))
|
||||
if err != nil {
|
||||
validityT.log.Info().Msg("error while verifying signatures")
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validityT.log.Info().Msg("verifying signatures successfully completed")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/meta/repodb"
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
)
|
||||
|
||||
func IsBuiltWithMGMTExtension() bool {
|
||||
@@ -18,3 +20,10 @@ func SetupMgmtRoutes(config *config.Config, router *mux.Router, log log.Logger)
|
||||
log.Warn().Msg("skipping setting up mgmt routes because given zot binary doesn't include this feature," +
|
||||
"please build a binary that does so")
|
||||
}
|
||||
|
||||
func EnablePeriodicSignaturesVerification(config *config.Config, taskScheduler *scheduler.Scheduler,
|
||||
repoDB repodb.RepoDB, log log.Logger,
|
||||
) {
|
||||
log.Warn().Msg("skipping adding to the scheduler a generator for updating signatures validity because " +
|
||||
"given binary doesn't include this feature, please build a binary that does so")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//go:build !mgmt
|
||||
|
||||
package extensions_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
func TestMgmtExtension(t *testing.T) {
|
||||
Convey("periodic signature verification is skipped when binary doesn't include mgmt", t, func() {
|
||||
conf := config.New()
|
||||
port := test.GetFreePort()
|
||||
|
||||
globalDir := t.TempDir()
|
||||
defaultValue := true
|
||||
|
||||
logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
|
||||
So(err, ShouldBeNil)
|
||||
defer os.Remove(logFile.Name())
|
||||
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.RootDirectory = globalDir
|
||||
conf.Storage.Commit = true
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Mgmt = &extconf.MgmtConfig{
|
||||
BaseConfig: extconf.BaseConfig{
|
||||
Enable: &defaultValue,
|
||||
},
|
||||
}
|
||||
conf.Log.Level = "warn"
|
||||
conf.Log.Output = logFile.Name()
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlrManager := test.NewControllerManager(ctlr)
|
||||
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
data, err := os.ReadFile(logFile.Name())
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(string(data), ShouldContainSubstring,
|
||||
"skipping adding to the scheduler a generator for updating signatures validity because "+
|
||||
"given binary doesn't include this feature, please build a binary that does so")
|
||||
})
|
||||
}
|
||||
@@ -9,7 +9,9 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"gopkg.in/resty.v1"
|
||||
@@ -20,6 +22,10 @@ import (
|
||||
"zotregistry.io/zot/pkg/extensions"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
||||
"zotregistry.io/zot/pkg/extensions/monitoring"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
"zotregistry.io/zot/pkg/storage"
|
||||
"zotregistry.io/zot/pkg/storage/local"
|
||||
"zotregistry.io/zot/pkg/test"
|
||||
)
|
||||
|
||||
@@ -518,6 +524,158 @@ func TestMgmtExtension(t *testing.T) {
|
||||
data, _ := os.ReadFile(logFile.Name())
|
||||
So(string(data), ShouldContainSubstring, "setting up mgmt routes")
|
||||
})
|
||||
|
||||
Convey("Verify mgmt route enabled for uploading certificates and public keys", t, func() {
|
||||
globalDir := t.TempDir()
|
||||
conf := config.New()
|
||||
port := test.GetFreePort()
|
||||
conf.HTTP.Port = port
|
||||
baseURL := test.GetBaseURL(port)
|
||||
|
||||
logFile, err := os.CreateTemp(globalDir, "zot-log*.txt")
|
||||
So(err, ShouldBeNil)
|
||||
defaultValue := true
|
||||
|
||||
conf.Commit = "v1.0.0"
|
||||
|
||||
imageStore := local.NewImageStore(globalDir, false, 0, false, false,
|
||||
log.NewLogger("debug", logFile.Name()), monitoring.NewMetricsServer(false,
|
||||
log.NewLogger("debug", logFile.Name())), nil, nil)
|
||||
|
||||
storeController := storage.StoreController{
|
||||
DefaultStore: imageStore,
|
||||
}
|
||||
|
||||
config, layers, manifest, err := test.GetRandomImageComponents(10)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = test.WriteImageToFileSystem(
|
||||
test.Image{
|
||||
Manifest: manifest,
|
||||
Layers: layers,
|
||||
Config: config,
|
||||
Reference: "0.0.1",
|
||||
}, "repo", storeController,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
sigConfig, sigLayers, sigManifest, err := test.GetRandomImageComponents(10)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
ref, _ := test.GetCosignSignatureTagForManifest(manifest)
|
||||
err = test.WriteImageToFileSystem(
|
||||
test.Image{
|
||||
Manifest: sigManifest,
|
||||
Layers: sigLayers,
|
||||
Config: sigConfig,
|
||||
Reference: ref,
|
||||
}, "repo", storeController,
|
||||
)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
conf.Extensions = &extconf.ExtensionConfig{}
|
||||
conf.Extensions.Search = &extconf.SearchConfig{}
|
||||
conf.Extensions.Search.Enable = &defaultValue
|
||||
conf.Extensions.Mgmt = &extconf.MgmtConfig{
|
||||
BaseConfig: extconf.BaseConfig{
|
||||
Enable: &defaultValue,
|
||||
},
|
||||
}
|
||||
|
||||
conf.Log.Output = logFile.Name()
|
||||
defer os.Remove(logFile.Name()) // cleanup
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
|
||||
ctlr.Config.Storage.RootDirectory = globalDir
|
||||
|
||||
ctlrManager := test.NewControllerManager(ctlr)
|
||||
ctlrManager.StartAndWait(port)
|
||||
defer ctlrManager.StopServer()
|
||||
|
||||
rootDir := t.TempDir()
|
||||
|
||||
test.NotationPathLock.Lock()
|
||||
defer test.NotationPathLock.Unlock()
|
||||
|
||||
test.LoadNotationPath(rootDir)
|
||||
|
||||
// generate a keypair
|
||||
err = test.GenerateNotationCerts(rootDir, "test")
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
certificateContent, err := os.ReadFile(path.Join(rootDir, "notation/localkeys", "test.crt"))
|
||||
So(err, ShouldBeNil)
|
||||
So(certificateContent, ShouldNotBeNil)
|
||||
|
||||
client := resty.New()
|
||||
resp, err := client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "notation").SetQueryParam("truststoreName", "test").
|
||||
SetBody(certificateContent).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusOK)
|
||||
|
||||
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "notation").
|
||||
SetBody(certificateContent).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "notation").SetQueryParam("truststoreName", "").
|
||||
SetBody(certificateContent).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "notation").SetQueryParam("truststoreName", "test").
|
||||
SetQueryParam("truststoreType", "signatureAuthority").
|
||||
SetBody([]byte("wrong content")).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "invalidTool").
|
||||
SetBody(certificateContent).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetHeader("Content-type", "application/octet-stream").
|
||||
SetQueryParam("resource", "signatures").SetQueryParam("tool", "cosign").
|
||||
SetBody([]byte("wrong content")).Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusInternalServerError)
|
||||
|
||||
resp, err = client.R().SetQueryParam("resource", "signatures").SetQueryParam("tool", "cosign").
|
||||
Get(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetQueryParam("resource", "signatures").Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetQueryParam("resource", "config").Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
resp, err = client.R().SetQueryParam("resource", "invalid").Post(baseURL + constants.FullMgmtPrefix)
|
||||
So(err, ShouldBeNil)
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusBadRequest)
|
||||
|
||||
found, err := test.ReadLogFileAndSearchString(logFile.Name(), "setting up mgmt routes", time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
So(found, ShouldBeTrue)
|
||||
|
||||
found, err = test.ReadLogFileAndSearchString(logFile.Name(), "updating signatures validity", 10*time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
So(found, ShouldBeTrue)
|
||||
|
||||
found, err = test.ReadLogFileAndSearchString(logFile.Name(), "verifying signatures successfully completed",
|
||||
time.Second)
|
||||
So(err, ShouldBeNil)
|
||||
So(found, ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMgmtWithBearer(t *testing.T) {
|
||||
@@ -694,7 +852,7 @@ func TestAllowedMethodsHeaderMgmt(t *testing.T) {
|
||||
|
||||
resp, _ := resty.R().Options(baseURL + constants.FullMgmtPrefix)
|
||||
So(resp, ShouldNotBeNil)
|
||||
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,OPTIONS")
|
||||
So(resp.Header().Get("Access-Control-Allow-Methods"), ShouldResemble, "GET,POST,OPTIONS")
|
||||
So(resp.StatusCode(), ShouldEqual, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,12 @@ Response depends on the user privileges:
|
||||
| Supported queries | Input | Output | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [Get current configuration](#get-current-configuration) | None | config json | Get current zot configuration |
|
||||
| [Upload a certificate](#post-certificate) | certificate | None | Add certificate for verifying notation signatures|
|
||||
| [Upload a public key](#post-public-key) | public key | None | Add public key for verifying cosign signatures |
|
||||
|
||||
## General usage
|
||||
The mgmt endpoint accepts as a query parameter what `resource` is targeted by the request and then all other required parameters for the specified resource. The default value of this
|
||||
query parameter is `config`.
|
||||
|
||||
## Get current configuration
|
||||
|
||||
@@ -42,3 +47,34 @@ If ldap or htpasswd are enabled mgmt will return `{"htpasswd": {}}` indicating t
|
||||
|
||||
If any key is present under `'auth'` key, in the mgmt response, it means that particular authentication method is enabled.
|
||||
|
||||
## Configure zot for verifying signatures
|
||||
If the `resource` is `signatures` then the mgmt endpoint accepts as a query parameter the `tool` that corresponds to the uploaded file and then all other required parameters for the specified tool.
|
||||
|
||||
### Upload a certificate
|
||||
|
||||
**Sample request**
|
||||
|
||||
| Tool | Parameter | Parameter Type | Parameter Description |
|
||||
| --- | --- | --- | --- |
|
||||
| notation | truststoreType | string | The type of the truststore. This parameter is optional and its default value is `ca` |
|
||||
| | truststoreName | string | The name of the truststore |
|
||||
|
||||
```bash
|
||||
curl --data-binary @certificate.crt -X POST http://localhost:8080/v2/_zot/ext/mgmt?resource=signature&tool=notation&truststoreType=ca&truststoreName=newtruststore
|
||||
```
|
||||
As a result of this request, the uploaded file will be stored in `_notation/truststore/x509/{truststoreType}/{truststoreName}` directory under $rootDir. And `truststores` field from `_notation/trustpolicy.json` file will be updated.
|
||||
|
||||
### Upload a public key
|
||||
|
||||
**Sample request**
|
||||
|
||||
| Tool | Parameter | Parameter Type | Parameter Description |
|
||||
| --- | --- | --- | --- |
|
||||
| cosign |
|
||||
|
||||
|
||||
```bash
|
||||
curl --data-binary @publicKey.pub -X POST http://localhost:8080/v2/_zot/ext/mgmt?resource=signature&tool=cosign
|
||||
```
|
||||
|
||||
As a result of this request, the uploaded file will be stored in `_cosign` directory under $rootDir.
|
||||
|
||||
Reference in New Issue
Block a user