mirror of
https://github.com/project-zot/zot.git
synced 2026-06-16 12:28:01 +08:00
refactor(extensions)!: refactor the extensions URLs and errors (#1636)
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>
This commit is contained in:
@@ -1,132 +0,0 @@
|
||||
# Verifying signatures
|
||||
|
||||
## How to configure zot for verifying signatures
|
||||
|
||||
In order to configure zot for verifying signatures, the user should provide:
|
||||
|
||||
1. public keys (which correspond to the private keys used to sign images with `cosign`)
|
||||
|
||||
or
|
||||
|
||||
2. certificates (used to sign images with `notation`)
|
||||
|
||||
These files could be uploaded using one of these requests:
|
||||
|
||||
1. upload a public key
|
||||
|
||||
***Example of request***
|
||||
```
|
||||
curl --data-binary @file.pub -X POST "http://localhost:8080/v2/_zot/ext/mgmt?resource=signatures&tool=cosign"
|
||||
```
|
||||
|
||||
2. upload a certificate
|
||||
|
||||
***Example of request***
|
||||
```
|
||||
curl --data-binary @filet.crt -X POST "http://localhost:8080/v2/_zot/ext/mgmt?resource=signatures&tool=notation&truststoreType=ca&truststoreName=upload-cert"
|
||||
```
|
||||
|
||||
Besides the requested files, the user should also specify the `tool` which should be :
|
||||
|
||||
- `cosign` for uploading public keys
|
||||
- `notation` for uploading certificates
|
||||
|
||||
Also, if the uploaded file is a certificate then the user should also specify the type of the truststore through `truststoreType` param and also its name through `truststoreName` param.
|
||||
|
||||
Based on the uploaded files, signatures verification will be performed for all the signed images. Then the information known about the signatures will be:
|
||||
|
||||
- the tool used to generate the signature (`cosign` or `notation`)
|
||||
- info about the trustworthiness of the signature (if there is a certificate or a public key which can successfully verify the signature)
|
||||
- the author of the signature which will be:
|
||||
|
||||
- the public key -> for signatures generated using `cosign`
|
||||
- the subject of the certificate -> for signatures generated using `notation`
|
||||
|
||||
**Example of GraphQL output**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"Image": {
|
||||
"Manifests": [
|
||||
{
|
||||
"Digest":"sha256:6c19fba547b87bde9a45df2f8563e0c61826d098dd30192a2c8b86da1e1a6360"
|
||||
}
|
||||
],
|
||||
"IsSigned": true,
|
||||
"Tag": "latest",
|
||||
"SignatureInfo":[
|
||||
{
|
||||
"Tool":"cosign",
|
||||
"IsTrusted":false,
|
||||
"Author":""
|
||||
},
|
||||
{
|
||||
"Tool":"cosign",
|
||||
"IsTrusted":false,
|
||||
"Author":""
|
||||
},
|
||||
{
|
||||
"Tool":"cosign",
|
||||
"IsTrusted": true,
|
||||
"Author":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9pN+/hGcFlh4YYaNvZxNvuh8Qyhl\npURz77qScOHe3DqdmiWiuqIseyhEdjEDwpL6fHRwu3a2Nd9wbKqm0la76w==\n-----END PUBLIC KEY-----\n"
|
||||
},
|
||||
{
|
||||
"Tool":"notation",
|
||||
"IsTrusted": false,
|
||||
"Author":"CN=v4-test,O=Notary,L=Seattle,ST=WA,C=US"
|
||||
},
|
||||
{
|
||||
"Tool":"notation",
|
||||
"IsTrusted": true,
|
||||
"Author":"CN=multipleSig,O=Notary,L=Seattle,ST=WA,C=US"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The files (public keys and certificates) uploaded using the exposed routes will be stored in some specific directories called `_cosign` and `_notation` under `$rootDir`.
|
||||
|
||||
- `_cosign` directory will contain the uploaded public keys
|
||||
```
|
||||
_cosign
|
||||
├── $publicKey1
|
||||
└── $publicKey2
|
||||
```
|
||||
|
||||
- `_notation` directory will have this structure:
|
||||
|
||||
```
|
||||
_notation
|
||||
├── trustpolicy.json
|
||||
└── truststore
|
||||
└── x509
|
||||
└── $truststoreType
|
||||
└── $truststoreName
|
||||
└── $certificate
|
||||
```
|
||||
|
||||
where `trustpolicy.json` file has this default content which can not be modified by the user and which is updated each time a new certificate is added to a new truststore:
|
||||
```
|
||||
{
|
||||
"version": "1.0",
|
||||
"trustPolicies": [
|
||||
{
|
||||
"name": "default-config",
|
||||
"registryScopes": [ "*" ],
|
||||
"signatureVerification": {
|
||||
"level" : "strict"
|
||||
},
|
||||
"trustStores": [],
|
||||
"trustedIdentities": [
|
||||
"*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"crypto"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
@@ -136,7 +137,7 @@ func UploadPublicKey(publicKeyContent []byte) error {
|
||||
func validatePublicKey(publicKeyContent []byte) (bool, error) {
|
||||
_, err := cryptoutils.UnmarshalPEMToPublicKey(publicKeyContent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidPublicKeyContent, err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
||||
@@ -302,7 +302,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
|
||||
// data may be in DER format
|
||||
derCerts, err := x509.ParseCertificates(certificateContent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
|
||||
}
|
||||
|
||||
certs = append(certs, derCerts...)
|
||||
@@ -311,7 +311,7 @@ func validateCertificate(certificateContent []byte) (bool, error) {
|
||||
for block != nil {
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return false, err
|
||||
return false, fmt.Errorf("%w: %w", zerr.ErrInvalidCertificateContent, err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
block, rest = pem.Decode(rest)
|
||||
@@ -319,7 +319,8 @@ func validateCertificate(certificateContent []byte) (bool, error) {
|
||||
}
|
||||
|
||||
if len(certs) == 0 {
|
||||
return false, zerr.ErrInvalidCertificateContent
|
||||
return false, fmt.Errorf("%w: no valid certificates found in payload",
|
||||
zerr.ErrInvalidCertificateContent)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package signatures
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
@@ -8,6 +9,9 @@ import (
|
||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/log"
|
||||
mTypes "zotregistry.io/zot/pkg/meta/types"
|
||||
"zotregistry.io/zot/pkg/scheduler"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -58,3 +62,84 @@ func VerifySignature(
|
||||
return "", time.Time{}, false, zerr.ErrInvalidSignatureType
|
||||
}
|
||||
}
|
||||
|
||||
func NewTaskGenerator(metaDB mTypes.MetaDB, log log.Logger) scheduler.TaskGenerator {
|
||||
return &sigValidityTaskGenerator{
|
||||
repos: []mTypes.RepoMetadata{},
|
||||
metaDB: metaDB,
|
||||
repoIndex: -1,
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
type sigValidityTaskGenerator struct {
|
||||
repos []mTypes.RepoMetadata
|
||||
metaDB mTypes.MetaDB
|
||||
repoIndex int
|
||||
done bool
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) Next() (scheduler.Task, error) {
|
||||
if len(gen.repos) == 0 {
|
||||
ctx := context.Background()
|
||||
|
||||
repos, err := gen.metaDB.GetMultipleRepoMeta(ctx, func(repoMeta mTypes.RepoMetadata) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gen.repos = repos
|
||||
}
|
||||
|
||||
gen.repoIndex++
|
||||
|
||||
if gen.repoIndex >= len(gen.repos) {
|
||||
gen.done = true
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return NewValidityTask(gen.metaDB, gen.repos[gen.repoIndex], gen.log), nil
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) IsDone() bool {
|
||||
return gen.done
|
||||
}
|
||||
|
||||
func (gen *sigValidityTaskGenerator) Reset() {
|
||||
gen.done = false
|
||||
gen.repoIndex = -1
|
||||
gen.repos = []mTypes.RepoMetadata{}
|
||||
}
|
||||
|
||||
type validityTask struct {
|
||||
metaDB mTypes.MetaDB
|
||||
repo mTypes.RepoMetadata
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewValidityTask(metaDB mTypes.MetaDB, repo mTypes.RepoMetadata, log log.Logger) *validityTask {
|
||||
return &validityTask{metaDB, repo, log}
|
||||
}
|
||||
|
||||
func (validityT *validityTask) DoWork() error {
|
||||
validityT.log.Info().Msg("updating signatures validity")
|
||||
|
||||
for signedManifest, sigs := range validityT.repo.Signatures {
|
||||
if len(sigs[CosignSignature]) != 0 || len(sigs[NotationSignature]) != 0 {
|
||||
err := validityT.metaDB.UpdateSignaturesValidity(validityT.repo.Name, godigest.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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user