mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
feat(graphql & repodb): add info about signature validity (#1344)
Signed-off-by: Andreea-Lupu <andreealupu1470@yahoo.com>
This commit is contained in:
parent
6e6ffe800c
commit
970997f3a8
24 changed files with 2053 additions and 31 deletions
|
@ -89,4 +89,7 @@ var (
|
||||||
ErrUserDataNotAllowed = errors.New("repodb: user data operations are not allowed")
|
ErrUserDataNotAllowed = errors.New("repodb: user data operations are not allowed")
|
||||||
ErrCouldNotPersistData = errors.New("repodb: could not persist to db")
|
ErrCouldNotPersistData = errors.New("repodb: could not persist to db")
|
||||||
ErrDedupeRebuild = errors.New("dedupe: couldn't rebuild dedupe index")
|
ErrDedupeRebuild = errors.New("dedupe: couldn't rebuild dedupe index")
|
||||||
|
ErrSignConfigDirNotSet = errors.New("signatures: signature config dir not set")
|
||||||
|
ErrBadManifestDigest = errors.New("signatures: bad manifest digest")
|
||||||
|
ErrInvalidSignatureType = errors.New("signatures: invalid signature type")
|
||||||
)
|
)
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -384,7 +384,7 @@ require (
|
||||||
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
github.com/shibumi/go-pathspec v1.3.0 // indirect
|
||||||
github.com/sigstore/fulcio v1.2.0 // indirect
|
github.com/sigstore/fulcio v1.2.0 // indirect
|
||||||
github.com/sigstore/rekor v1.1.1 // indirect
|
github.com/sigstore/rekor v1.1.1 // indirect
|
||||||
github.com/sigstore/sigstore v1.6.3 // indirect
|
github.com/sigstore/sigstore v1.6.3
|
||||||
github.com/sirupsen/logrus v1.9.0 // indirect
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||||
github.com/smartystreets/assertions v1.13.1 // indirect
|
github.com/smartystreets/assertions v1.13.1 // indirect
|
||||||
|
|
|
@ -385,3 +385,43 @@ func TestLabels(t *testing.T) {
|
||||||
So(vendor, ShouldEqual, "zot-vendor")
|
So(vendor, ShouldEqual, "zot-vendor")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetSignaturesInfo(t *testing.T) {
|
||||||
|
Convey("Test get signatures info - cosign", t, func() {
|
||||||
|
indexDigest := godigest.FromString("123")
|
||||||
|
repoMeta := repodb.RepoMetadata{
|
||||||
|
Signatures: map[string]repodb.ManifestSignatures{string(indexDigest): {"cosign": []repodb.SignatureInfo{{
|
||||||
|
LayersInfo: []repodb.LayerInfo{{LayerContent: []byte{}, LayerDigest: "", SignatureKey: "", Signer: "author"}},
|
||||||
|
}}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
signaturesSummary := convert.GetSignaturesInfo(true, repoMeta, indexDigest)
|
||||||
|
So(signaturesSummary, ShouldNotBeEmpty)
|
||||||
|
So(*signaturesSummary[0].Author, ShouldEqual, "author")
|
||||||
|
So(*signaturesSummary[0].IsTrusted, ShouldEqual, true)
|
||||||
|
So(*signaturesSummary[0].Tool, ShouldEqual, "cosign")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Test get signatures info - notation", t, func() {
|
||||||
|
indexDigest := godigest.FromString("123")
|
||||||
|
repoMeta := repodb.RepoMetadata{
|
||||||
|
Signatures: map[string]repodb.ManifestSignatures{string(indexDigest): {"notation": []repodb.SignatureInfo{{
|
||||||
|
LayersInfo: []repodb.LayerInfo{
|
||||||
|
{
|
||||||
|
LayerContent: []byte{},
|
||||||
|
LayerDigest: "",
|
||||||
|
SignatureKey: "",
|
||||||
|
Signer: "author",
|
||||||
|
Date: time.Now().AddDate(0, 0, -1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}}},
|
||||||
|
}
|
||||||
|
|
||||||
|
signaturesSummary := convert.GetSignaturesInfo(true, repoMeta, indexDigest)
|
||||||
|
So(signaturesSummary, ShouldNotBeEmpty)
|
||||||
|
So(*signaturesSummary[0].Author, ShouldEqual, "author")
|
||||||
|
So(*signaturesSummary[0].IsTrusted, ShouldEqual, false)
|
||||||
|
So(*signaturesSummary[0].Tool, ShouldEqual, "notation")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -243,6 +243,8 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
|
||||||
|
|
||||||
annotations := GetAnnotations(indexContent.Annotations, map[string]string{})
|
annotations := GetAnnotations(indexContent.Annotations, map[string]string{})
|
||||||
|
|
||||||
|
signaturesInfo := GetSignaturesInfo(isSigned, repoMeta, indexDigest)
|
||||||
|
|
||||||
indexSummary := gql_generated.ImageSummary{
|
indexSummary := gql_generated.ImageSummary{
|
||||||
RepoName: &repo,
|
RepoName: &repo,
|
||||||
Tag: &tag,
|
Tag: &tag,
|
||||||
|
@ -251,6 +253,7 @@ func ImageIndex2ImageSummary(ctx context.Context, repo, tag string, indexDigest
|
||||||
Manifests: manifestSummaries,
|
Manifests: manifestSummaries,
|
||||||
LastUpdated: &indexLastUpdated,
|
LastUpdated: &indexLastUpdated,
|
||||||
IsSigned: &isSigned,
|
IsSigned: &isSigned,
|
||||||
|
SignatureInfo: signaturesInfo,
|
||||||
Size: &indexSize,
|
Size: &indexSize,
|
||||||
DownloadCount: &totalDownloadCount,
|
DownloadCount: &totalDownloadCount,
|
||||||
Description: &annotations.Description,
|
Description: &annotations.Description,
|
||||||
|
@ -354,6 +357,8 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signaturesInfo := GetSignaturesInfo(isSigned, repoMeta, digest)
|
||||||
|
|
||||||
imageSummary := gql_generated.ImageSummary{
|
imageSummary := gql_generated.ImageSummary{
|
||||||
RepoName: &repoName,
|
RepoName: &repoName,
|
||||||
Tag: &tag,
|
Tag: &tag,
|
||||||
|
@ -366,6 +371,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
|
||||||
LastUpdated: &imageLastUpdated,
|
LastUpdated: &imageLastUpdated,
|
||||||
Size: &imageSize,
|
Size: &imageSize,
|
||||||
IsSigned: &isSigned,
|
IsSigned: &isSigned,
|
||||||
|
SignatureInfo: signaturesInfo,
|
||||||
Platform: &platform,
|
Platform: &platform,
|
||||||
DownloadCount: &downloadCount,
|
DownloadCount: &downloadCount,
|
||||||
Layers: getLayersSummaries(manifestContent),
|
Layers: getLayersSummaries(manifestContent),
|
||||||
|
@ -380,6 +386,7 @@ func ImageManifest2ImageSummary(ctx context.Context, repo, tag string, digest go
|
||||||
},
|
},
|
||||||
LastUpdated: &imageLastUpdated,
|
LastUpdated: &imageLastUpdated,
|
||||||
IsSigned: &isSigned,
|
IsSigned: &isSigned,
|
||||||
|
SignatureInfo: signaturesInfo,
|
||||||
Size: &imageSize,
|
Size: &imageSize,
|
||||||
DownloadCount: &downloadCount,
|
DownloadCount: &downloadCount,
|
||||||
Description: &annotations.Description,
|
Description: &annotations.Description,
|
||||||
|
@ -511,6 +518,8 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signaturesInfo := GetSignaturesInfo(isSigned, repoMeta, digest)
|
||||||
|
|
||||||
manifestSummary := gql_generated.ManifestSummary{
|
manifestSummary := gql_generated.ManifestSummary{
|
||||||
Digest: &manifestDigestStr,
|
Digest: &manifestDigestStr,
|
||||||
ConfigDigest: &configDigest,
|
ConfigDigest: &configDigest,
|
||||||
|
@ -521,6 +530,7 @@ func ImageManifest2ManifestSummary(ctx context.Context, repo, tag string, descri
|
||||||
Layers: getLayersSummaries(manifestContent),
|
Layers: getLayersSummaries(manifestContent),
|
||||||
History: historyEntries,
|
History: historyEntries,
|
||||||
IsSigned: &isSigned,
|
IsSigned: &isSigned,
|
||||||
|
SignatureInfo: signaturesInfo,
|
||||||
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
|
Vulnerabilities: &gql_generated.ImageVulnerabilitySummary{
|
||||||
MaxSeverity: &imageCveSummary.MaxSeverity,
|
MaxSeverity: &imageCveSummary.MaxSeverity,
|
||||||
Count: &imageCveSummary.Count,
|
Count: &imageCveSummary.Count,
|
||||||
|
@ -744,3 +754,44 @@ func GetPreloadString(prefix, name string) string {
|
||||||
|
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSignaturesInfo(isSigned bool, repoMeta repodb.RepoMetadata, indexDigest godigest.Digest,
|
||||||
|
) []*gql_generated.SignatureSummary {
|
||||||
|
signaturesInfo := []*gql_generated.SignatureSummary{}
|
||||||
|
|
||||||
|
if !isSigned {
|
||||||
|
return signaturesInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
for sigType, signatures := range repoMeta.Signatures[indexDigest.String()] {
|
||||||
|
for _, sig := range signatures {
|
||||||
|
for _, layer := range sig.LayersInfo {
|
||||||
|
var (
|
||||||
|
isTrusted bool
|
||||||
|
author string
|
||||||
|
tool string
|
||||||
|
)
|
||||||
|
|
||||||
|
if layer.Signer != "" {
|
||||||
|
author = layer.Signer
|
||||||
|
|
||||||
|
if !layer.Date.IsZero() && time.Now().After(layer.Date) {
|
||||||
|
isTrusted = false
|
||||||
|
} else {
|
||||||
|
isTrusted = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isTrusted = false
|
||||||
|
author = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
tool = sigType
|
||||||
|
|
||||||
|
signaturesInfo = append(signaturesInfo,
|
||||||
|
&gql_generated.SignatureSummary{Tool: &tool, IsTrusted: &isTrusted, Author: &author})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signaturesInfo
|
||||||
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ type ComplexityRoot struct {
|
||||||
MediaType func(childComplexity int) int
|
MediaType func(childComplexity int) int
|
||||||
Referrers func(childComplexity int) int
|
Referrers func(childComplexity int) int
|
||||||
RepoName func(childComplexity int) int
|
RepoName func(childComplexity int) int
|
||||||
|
SignatureInfo func(childComplexity int) int
|
||||||
Size func(childComplexity int) int
|
Size func(childComplexity int) int
|
||||||
Source func(childComplexity int) int
|
Source func(childComplexity int) int
|
||||||
Tag func(childComplexity int) int
|
Tag func(childComplexity int) int
|
||||||
|
@ -125,6 +126,7 @@ type ComplexityRoot struct {
|
||||||
Layers func(childComplexity int) int
|
Layers func(childComplexity int) int
|
||||||
Platform func(childComplexity int) int
|
Platform func(childComplexity int) int
|
||||||
Referrers func(childComplexity int) int
|
Referrers func(childComplexity int) int
|
||||||
|
SignatureInfo func(childComplexity int) int
|
||||||
Size func(childComplexity int) int
|
Size func(childComplexity int) int
|
||||||
Vulnerabilities func(childComplexity int) int
|
Vulnerabilities func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
@ -197,6 +199,12 @@ type ComplexityRoot struct {
|
||||||
StarCount func(childComplexity int) int
|
StarCount func(childComplexity int) int
|
||||||
Vendors func(childComplexity int) int
|
Vendors func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignatureSummary struct {
|
||||||
|
Author func(childComplexity int) int
|
||||||
|
IsTrusted func(childComplexity int) int
|
||||||
|
Tool func(childComplexity int) int
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryResolver interface {
|
type QueryResolver interface {
|
||||||
|
@ -455,6 +463,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.ImageSummary.RepoName(childComplexity), true
|
return e.complexity.ImageSummary.RepoName(childComplexity), true
|
||||||
|
|
||||||
|
case "ImageSummary.SignatureInfo":
|
||||||
|
if e.complexity.ImageSummary.SignatureInfo == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.ImageSummary.SignatureInfo(childComplexity), true
|
||||||
|
|
||||||
case "ImageSummary.Size":
|
case "ImageSummary.Size":
|
||||||
if e.complexity.ImageSummary.Size == nil {
|
if e.complexity.ImageSummary.Size == nil {
|
||||||
break
|
break
|
||||||
|
@ -609,6 +624,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.ManifestSummary.Referrers(childComplexity), true
|
return e.complexity.ManifestSummary.Referrers(childComplexity), true
|
||||||
|
|
||||||
|
case "ManifestSummary.SignatureInfo":
|
||||||
|
if e.complexity.ManifestSummary.SignatureInfo == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.ManifestSummary.SignatureInfo(childComplexity), true
|
||||||
|
|
||||||
case "ManifestSummary.Size":
|
case "ManifestSummary.Size":
|
||||||
if e.complexity.ManifestSummary.Size == nil {
|
if e.complexity.ManifestSummary.Size == nil {
|
||||||
break
|
break
|
||||||
|
@ -987,6 +1009,27 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.RepoSummary.Vendors(childComplexity), true
|
return e.complexity.RepoSummary.Vendors(childComplexity), true
|
||||||
|
|
||||||
|
case "SignatureSummary.Author":
|
||||||
|
if e.complexity.SignatureSummary.Author == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.SignatureSummary.Author(childComplexity), true
|
||||||
|
|
||||||
|
case "SignatureSummary.IsTrusted":
|
||||||
|
if e.complexity.SignatureSummary.IsTrusted == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.SignatureSummary.IsTrusted(childComplexity), true
|
||||||
|
|
||||||
|
case "SignatureSummary.Tool":
|
||||||
|
if e.complexity.SignatureSummary.Tool == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.SignatureSummary.Tool(childComplexity), true
|
||||||
|
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
@ -1200,6 +1243,10 @@ type ImageSummary {
|
||||||
"""
|
"""
|
||||||
IsSigned: Boolean
|
IsSigned: Boolean
|
||||||
"""
|
"""
|
||||||
|
Info about signature validity
|
||||||
|
"""
|
||||||
|
SignatureInfo: [SignatureSummary]
|
||||||
|
"""
|
||||||
License(s) under which contained software is distributed as an SPDX License Expression
|
License(s) under which contained software is distributed as an SPDX License Expression
|
||||||
"""
|
"""
|
||||||
Licenses: String # The value of the annotation if present, 'unknown' otherwise).
|
Licenses: String # The value of the annotation if present, 'unknown' otherwise).
|
||||||
|
@ -1262,6 +1309,10 @@ type ManifestSummary {
|
||||||
"""
|
"""
|
||||||
IsSigned: Boolean
|
IsSigned: Boolean
|
||||||
"""
|
"""
|
||||||
|
Info about signature validity
|
||||||
|
"""
|
||||||
|
SignatureInfo: [SignatureSummary]
|
||||||
|
"""
|
||||||
OS and architecture supported by this image
|
OS and architecture supported by this image
|
||||||
"""
|
"""
|
||||||
Platform: Platform
|
Platform: Platform
|
||||||
|
@ -1466,6 +1517,24 @@ type Platform {
|
||||||
Arch: String
|
Arch: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Contains details about the signature
|
||||||
|
"""
|
||||||
|
type SignatureSummary {
|
||||||
|
"""
|
||||||
|
Tool is the tool used for signing image
|
||||||
|
"""
|
||||||
|
Tool: String
|
||||||
|
"""
|
||||||
|
True if the signature is trusted, false otherwise
|
||||||
|
"""
|
||||||
|
IsTrusted: Boolean
|
||||||
|
"""
|
||||||
|
Author is the author of the signature
|
||||||
|
"""
|
||||||
|
Author: String
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
All sort criteria usable with pagination, some of these criteria applies only
|
All sort criteria usable with pagination, some of these criteria applies only
|
||||||
to certain queries. For example sort by severity is available for CVEs but not
|
to certain queries. For example sort by severity is available for CVEs but not
|
||||||
|
@ -2698,6 +2767,8 @@ func (ec *executionContext) fieldContext_GlobalSearchResult_Images(ctx context.C
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
case "Labels":
|
case "Labels":
|
||||||
|
@ -3248,6 +3319,8 @@ func (ec *executionContext) fieldContext_ImageSummary_Manifests(ctx context.Cont
|
||||||
return ec.fieldContext_ManifestSummary_Size(ctx, field)
|
return ec.fieldContext_ManifestSummary_Size(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ManifestSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ManifestSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ManifestSummary_SignatureInfo(ctx, field)
|
||||||
case "Platform":
|
case "Platform":
|
||||||
return ec.fieldContext_ManifestSummary_Platform(ctx, field)
|
return ec.fieldContext_ManifestSummary_Platform(ctx, field)
|
||||||
case "DownloadCount":
|
case "DownloadCount":
|
||||||
|
@ -3474,6 +3547,55 @@ func (ec *executionContext) fieldContext_ImageSummary_IsSigned(ctx context.Conte
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _ImageSummary_SignatureInfo(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.SignatureInfo, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.([]*SignatureSummary)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOSignatureSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSignatureSummary(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_ImageSummary_SignatureInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "ImageSummary",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
switch field.Name {
|
||||||
|
case "Tool":
|
||||||
|
return ec.fieldContext_SignatureSummary_Tool(ctx, field)
|
||||||
|
case "IsTrusted":
|
||||||
|
return ec.fieldContext_SignatureSummary_IsTrusted(ctx, field)
|
||||||
|
case "Author":
|
||||||
|
return ec.fieldContext_SignatureSummary_Author(ctx, field)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no field named %q was found under type SignatureSummary", field.Name)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _ImageSummary_Licenses(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
|
func (ec *executionContext) _ImageSummary_Licenses(ctx context.Context, field graphql.CollectedField, obj *ImageSummary) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
fc, err := ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4330,6 +4452,55 @@ func (ec *executionContext) fieldContext_ManifestSummary_IsSigned(ctx context.Co
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _ManifestSummary_SignatureInfo(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_ManifestSummary_SignatureInfo(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.SignatureInfo, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.([]*SignatureSummary)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOSignatureSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSignatureSummary(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_ManifestSummary_SignatureInfo(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "ManifestSummary",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
switch field.Name {
|
||||||
|
case "Tool":
|
||||||
|
return ec.fieldContext_SignatureSummary_Tool(ctx, field)
|
||||||
|
case "IsTrusted":
|
||||||
|
return ec.fieldContext_SignatureSummary_IsTrusted(ctx, field)
|
||||||
|
case "Author":
|
||||||
|
return ec.fieldContext_SignatureSummary_Author(ctx, field)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no field named %q was found under type SignatureSummary", field.Name)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _ManifestSummary_Platform(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
|
func (ec *executionContext) _ManifestSummary_Platform(ctx context.Context, field graphql.CollectedField, obj *ManifestSummary) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_ManifestSummary_Platform(ctx, field)
|
fc, err := ec.fieldContext_ManifestSummary_Platform(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4970,6 +5141,8 @@ func (ec *executionContext) fieldContext_PaginatedImagesResult_Results(ctx conte
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
case "Labels":
|
case "Labels":
|
||||||
|
@ -5865,6 +6038,8 @@ func (ec *executionContext) fieldContext_Query_Image(ctx context.Context, field
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
case "Labels":
|
case "Labels":
|
||||||
|
@ -6489,6 +6664,8 @@ func (ec *executionContext) fieldContext_RepoInfo_Images(ctx context.Context, fi
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
case "Labels":
|
case "Labels":
|
||||||
|
@ -6844,6 +7021,8 @@ func (ec *executionContext) fieldContext_RepoSummary_NewestImage(ctx context.Con
|
||||||
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
return ec.fieldContext_ImageSummary_Description(ctx, field)
|
||||||
case "IsSigned":
|
case "IsSigned":
|
||||||
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
return ec.fieldContext_ImageSummary_IsSigned(ctx, field)
|
||||||
|
case "SignatureInfo":
|
||||||
|
return ec.fieldContext_ImageSummary_SignatureInfo(ctx, field)
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
return ec.fieldContext_ImageSummary_Licenses(ctx, field)
|
||||||
case "Labels":
|
case "Labels":
|
||||||
|
@ -7033,6 +7212,129 @@ func (ec *executionContext) fieldContext_RepoSummary_IsStarred(ctx context.Conte
|
||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SignatureSummary_Tool(ctx context.Context, field graphql.CollectedField, obj *SignatureSummary) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_SignatureSummary_Tool(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Tool, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_SignatureSummary_Tool(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "SignatureSummary",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type String does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SignatureSummary_IsTrusted(ctx context.Context, field graphql.CollectedField, obj *SignatureSummary) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_SignatureSummary_IsTrusted(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.IsTrusted, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*bool)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOBoolean2ᚖbool(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_SignatureSummary_IsTrusted(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "SignatureSummary",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type Boolean does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SignatureSummary_Author(ctx context.Context, field graphql.CollectedField, obj *SignatureSummary) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_SignatureSummary_Author(ctx, field)
|
||||||
|
if err != nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = graphql.Null
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||||
|
ctx = rctx // use context from middleware stack in children
|
||||||
|
return obj.Author, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ec.Error(ctx, err)
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
if resTmp == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
res := resTmp.(*string)
|
||||||
|
fc.Result = res
|
||||||
|
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) fieldContext_SignatureSummary_Author(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "SignatureSummary",
|
||||||
|
Field: field,
|
||||||
|
IsMethod: false,
|
||||||
|
IsResolver: false,
|
||||||
|
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||||
|
return nil, errors.New("field of type String does not have child fields")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return fc, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext___Directive_name(ctx, field)
|
fc, err := ec.fieldContext___Directive_name(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -9157,6 +9459,10 @@ func (ec *executionContext) _ImageSummary(ctx context.Context, sel ast.Selection
|
||||||
|
|
||||||
out.Values[i] = ec._ImageSummary_IsSigned(ctx, field, obj)
|
out.Values[i] = ec._ImageSummary_IsSigned(ctx, field, obj)
|
||||||
|
|
||||||
|
case "SignatureInfo":
|
||||||
|
|
||||||
|
out.Values[i] = ec._ImageSummary_SignatureInfo(ctx, field, obj)
|
||||||
|
|
||||||
case "Licenses":
|
case "Licenses":
|
||||||
|
|
||||||
out.Values[i] = ec._ImageSummary_Licenses(ctx, field, obj)
|
out.Values[i] = ec._ImageSummary_Licenses(ctx, field, obj)
|
||||||
|
@ -9321,6 +9627,10 @@ func (ec *executionContext) _ManifestSummary(ctx context.Context, sel ast.Select
|
||||||
|
|
||||||
out.Values[i] = ec._ManifestSummary_IsSigned(ctx, field, obj)
|
out.Values[i] = ec._ManifestSummary_IsSigned(ctx, field, obj)
|
||||||
|
|
||||||
|
case "SignatureInfo":
|
||||||
|
|
||||||
|
out.Values[i] = ec._ManifestSummary_SignatureInfo(ctx, field, obj)
|
||||||
|
|
||||||
case "Platform":
|
case "Platform":
|
||||||
|
|
||||||
out.Values[i] = ec._ManifestSummary_Platform(ctx, field, obj)
|
out.Values[i] = ec._ManifestSummary_Platform(ctx, field, obj)
|
||||||
|
@ -10019,6 +10329,39 @@ func (ec *executionContext) _RepoSummary(ctx context.Context, sel ast.SelectionS
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var signatureSummaryImplementors = []string{"SignatureSummary"}
|
||||||
|
|
||||||
|
func (ec *executionContext) _SignatureSummary(ctx context.Context, sel ast.SelectionSet, obj *SignatureSummary) graphql.Marshaler {
|
||||||
|
fields := graphql.CollectFields(ec.OperationContext, sel, signatureSummaryImplementors)
|
||||||
|
out := graphql.NewFieldSet(fields)
|
||||||
|
var invalids uint32
|
||||||
|
for i, field := range fields {
|
||||||
|
switch field.Name {
|
||||||
|
case "__typename":
|
||||||
|
out.Values[i] = graphql.MarshalString("SignatureSummary")
|
||||||
|
case "Tool":
|
||||||
|
|
||||||
|
out.Values[i] = ec._SignatureSummary_Tool(ctx, field, obj)
|
||||||
|
|
||||||
|
case "IsTrusted":
|
||||||
|
|
||||||
|
out.Values[i] = ec._SignatureSummary_IsTrusted(ctx, field, obj)
|
||||||
|
|
||||||
|
case "Author":
|
||||||
|
|
||||||
|
out.Values[i] = ec._SignatureSummary_Author(ctx, field, obj)
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.Dispatch()
|
||||||
|
if invalids > 0 {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
var __DirectiveImplementors = []string{"__Directive"}
|
var __DirectiveImplementors = []string{"__Directive"}
|
||||||
|
|
||||||
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
|
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
|
||||||
|
@ -11411,6 +11754,54 @@ func (ec *executionContext) marshalORepoSummary2ᚖzotregistryᚗioᚋzotᚋpkg
|
||||||
return ec._RepoSummary(ctx, sel, v)
|
return ec._RepoSummary(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOSignatureSummary2ᚕᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSignatureSummary(ctx context.Context, sel ast.SelectionSet, v []*SignatureSummary) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
ret := make(graphql.Array, len(v))
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
isLen1 := len(v) == 1
|
||||||
|
if !isLen1 {
|
||||||
|
wg.Add(len(v))
|
||||||
|
}
|
||||||
|
for i := range v {
|
||||||
|
i := i
|
||||||
|
fc := &graphql.FieldContext{
|
||||||
|
Index: &i,
|
||||||
|
Result: &v[i],
|
||||||
|
}
|
||||||
|
ctx := graphql.WithFieldContext(ctx, fc)
|
||||||
|
f := func(i int) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
ec.Error(ctx, ec.Recover(ctx, r))
|
||||||
|
ret = nil
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
if !isLen1 {
|
||||||
|
defer wg.Done()
|
||||||
|
}
|
||||||
|
ret[i] = ec.marshalOSignatureSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSignatureSummary(ctx, sel, v[i])
|
||||||
|
}
|
||||||
|
if isLen1 {
|
||||||
|
f(i)
|
||||||
|
} else {
|
||||||
|
go f(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) marshalOSignatureSummary2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSignatureSummary(ctx context.Context, sel ast.SelectionSet, v *SignatureSummary) graphql.Marshaler {
|
||||||
|
if v == nil {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
return ec._SignatureSummary(ctx, sel, v)
|
||||||
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalOSortCriteria2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSortCriteria(ctx context.Context, v interface{}) (*SortCriteria, error) {
|
func (ec *executionContext) unmarshalOSortCriteria2ᚖzotregistryᚗioᚋzotᚋpkgᚋextensionsᚋsearchᚋgql_generatedᚐSortCriteria(ctx context.Context, v interface{}) (*SortCriteria, error) {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
|
@ -111,6 +111,8 @@ type ImageSummary struct {
|
||||||
Description *string `json:"Description,omitempty"`
|
Description *string `json:"Description,omitempty"`
|
||||||
// True if the image has a signature associated with it, false otherwise
|
// True if the image has a signature associated with it, false otherwise
|
||||||
IsSigned *bool `json:"IsSigned,omitempty"`
|
IsSigned *bool `json:"IsSigned,omitempty"`
|
||||||
|
// Info about signature validity
|
||||||
|
SignatureInfo []*SignatureSummary `json:"SignatureInfo,omitempty"`
|
||||||
// License(s) under which contained software is distributed as an SPDX License Expression
|
// License(s) under which contained software is distributed as an SPDX License Expression
|
||||||
Licenses *string `json:"Licenses,omitempty"`
|
Licenses *string `json:"Licenses,omitempty"`
|
||||||
// Labels associated with this image
|
// Labels associated with this image
|
||||||
|
@ -168,6 +170,8 @@ type ManifestSummary struct {
|
||||||
Size *string `json:"Size,omitempty"`
|
Size *string `json:"Size,omitempty"`
|
||||||
// True if the manifest has a signature associated with it, false otherwise
|
// True if the manifest has a signature associated with it, false otherwise
|
||||||
IsSigned *bool `json:"IsSigned,omitempty"`
|
IsSigned *bool `json:"IsSigned,omitempty"`
|
||||||
|
// Info about signature validity
|
||||||
|
SignatureInfo []*SignatureSummary `json:"SignatureInfo,omitempty"`
|
||||||
// OS and architecture supported by this image
|
// OS and architecture supported by this image
|
||||||
Platform *Platform `json:"Platform,omitempty"`
|
Platform *Platform `json:"Platform,omitempty"`
|
||||||
// Total numer of image manifest downloads from this repository
|
// Total numer of image manifest downloads from this repository
|
||||||
|
@ -291,6 +295,16 @@ type RepoSummary struct {
|
||||||
IsStarred *bool `json:"IsStarred,omitempty"`
|
IsStarred *bool `json:"IsStarred,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains details about the signature
|
||||||
|
type SignatureSummary struct {
|
||||||
|
// Tool is the tool used for signing image
|
||||||
|
Tool *string `json:"Tool,omitempty"`
|
||||||
|
// True if the signature is trusted, false otherwise
|
||||||
|
IsTrusted *bool `json:"IsTrusted,omitempty"`
|
||||||
|
// Author is the author of the signature
|
||||||
|
Author *string `json:"Author,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// All sort criteria usable with pagination, some of these criteria applies only
|
// All sort criteria usable with pagination, some of these criteria applies only
|
||||||
// to certain queries. For example sort by severity is available for CVEs but not
|
// to certain queries. For example sort by severity is available for CVEs but not
|
||||||
// for repositories
|
// for repositories
|
||||||
|
|
|
@ -156,6 +156,10 @@ type ImageSummary {
|
||||||
"""
|
"""
|
||||||
IsSigned: Boolean
|
IsSigned: Boolean
|
||||||
"""
|
"""
|
||||||
|
Info about signature validity
|
||||||
|
"""
|
||||||
|
SignatureInfo: [SignatureSummary]
|
||||||
|
"""
|
||||||
License(s) under which contained software is distributed as an SPDX License Expression
|
License(s) under which contained software is distributed as an SPDX License Expression
|
||||||
"""
|
"""
|
||||||
Licenses: String # The value of the annotation if present, 'unknown' otherwise).
|
Licenses: String # The value of the annotation if present, 'unknown' otherwise).
|
||||||
|
@ -218,6 +222,10 @@ type ManifestSummary {
|
||||||
"""
|
"""
|
||||||
IsSigned: Boolean
|
IsSigned: Boolean
|
||||||
"""
|
"""
|
||||||
|
Info about signature validity
|
||||||
|
"""
|
||||||
|
SignatureInfo: [SignatureSummary]
|
||||||
|
"""
|
||||||
OS and architecture supported by this image
|
OS and architecture supported by this image
|
||||||
"""
|
"""
|
||||||
Platform: Platform
|
Platform: Platform
|
||||||
|
@ -422,6 +430,24 @@ type Platform {
|
||||||
Arch: String
|
Arch: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Contains details about the signature
|
||||||
|
"""
|
||||||
|
type SignatureSummary {
|
||||||
|
"""
|
||||||
|
Tool is the tool used for signing image
|
||||||
|
"""
|
||||||
|
Tool: String
|
||||||
|
"""
|
||||||
|
True if the signature is trusted, false otherwise
|
||||||
|
"""
|
||||||
|
IsTrusted: Boolean
|
||||||
|
"""
|
||||||
|
Author is the author of the signature
|
||||||
|
"""
|
||||||
|
Author: String
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
All sort criteria usable with pagination, some of these criteria applies only
|
All sort criteria usable with pagination, some of these criteria applies only
|
||||||
to certain queries. For example sort by severity is available for CVEs but not
|
to certain queries. For example sort by severity is available for CVEs but not
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,7 +195,7 @@ func (sig *signaturesCopier) syncCosignSignature(localRepo, remoteRepo, digestSt
|
||||||
Msg("trying to sync cosign signature for repo digest")
|
Msg("trying to sync cosign signature for repo digest")
|
||||||
|
|
||||||
err := sig.repoDB.AddManifestSignature(localRepo, godigest.Digest(digestStr), repodb.SignatureMetadata{
|
err := sig.repoDB.AddManifestSignature(localRepo, godigest.Digest(digestStr), repodb.SignatureMetadata{
|
||||||
SignatureType: repodb.CosignType,
|
SignatureType: signatures.CosignSignature,
|
||||||
SignatureDigest: signatureDigest.String(),
|
SignatureDigest: signatureDigest.String(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -274,7 +275,7 @@ func (sig *signaturesCopier) syncORASRefs(localRepo, remoteRepo, digestStr strin
|
||||||
Msg("trying to sync oras artifact for digest")
|
Msg("trying to sync oras artifact for digest")
|
||||||
|
|
||||||
err := sig.repoDB.AddManifestSignature(localRepo, godigest.Digest(digestStr), repodb.SignatureMetadata{
|
err := sig.repoDB.AddManifestSignature(localRepo, godigest.Digest(digestStr), repodb.SignatureMetadata{
|
||||||
SignatureType: repodb.NotationType,
|
SignatureType: signatures.NotationSignature,
|
||||||
SignatureDigest: signatureDigest.String(),
|
SignatureDigest: signatureDigest.String(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -41,7 +41,7 @@ import (
|
||||||
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
syncconf "zotregistry.io/zot/pkg/extensions/config/sync"
|
||||||
"zotregistry.io/zot/pkg/extensions/sync"
|
"zotregistry.io/zot/pkg/extensions/sync"
|
||||||
logger "zotregistry.io/zot/pkg/log"
|
logger "zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
"zotregistry.io/zot/pkg/storage/local"
|
"zotregistry.io/zot/pkg/storage/local"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
@ -3722,10 +3722,10 @@ func TestSyncedSignaturesRepoDB(t *testing.T) {
|
||||||
So(repoMeta.Signatures, ShouldContainKey, signedImageDigest.String())
|
So(repoMeta.Signatures, ShouldContainKey, signedImageDigest.String())
|
||||||
|
|
||||||
imageSignatures := repoMeta.Signatures[signedImageDigest.String()]
|
imageSignatures := repoMeta.Signatures[signedImageDigest.String()]
|
||||||
So(imageSignatures, ShouldContainKey, repodb.CosignType)
|
So(imageSignatures, ShouldContainKey, signatures.CosignSignature)
|
||||||
So(len(imageSignatures[repodb.CosignType]), ShouldEqual, 1)
|
So(len(imageSignatures[signatures.CosignSignature]), ShouldEqual, 1)
|
||||||
So(imageSignatures, ShouldContainKey, repodb.NotationType)
|
So(imageSignatures, ShouldContainKey, signatures.NotationSignature)
|
||||||
So(len(imageSignatures[repodb.NotationType]), ShouldEqual, 1)
|
So(len(imageSignatures[signatures.NotationSignature]), ShouldEqual, 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
"zotregistry.io/zot/pkg/meta/common"
|
"zotregistry.io/zot/pkg/meta/common"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/meta/version"
|
"zotregistry.io/zot/pkg/meta/version"
|
||||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||||
)
|
)
|
||||||
|
@ -726,6 +727,102 @@ func (bdw *DBWrapper) IncrementImageDownloads(repo string, reference string) err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bdw *DBWrapper) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
|
||||||
|
err := bdw.DB.Update(func(transaction *bbolt.Tx) error {
|
||||||
|
// get ManifestData of signed manifest
|
||||||
|
manifestBuck := transaction.Bucket([]byte(bolt.ManifestDataBucket))
|
||||||
|
mdBlob := manifestBuck.Get([]byte(manifestDigest))
|
||||||
|
|
||||||
|
var blob []byte
|
||||||
|
|
||||||
|
if len(mdBlob) != 0 {
|
||||||
|
var manifestData repodb.ManifestData
|
||||||
|
|
||||||
|
err := json.Unmarshal(mdBlob, &manifestData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("repodb: %w error while unmashaling manifest meta for digest %s", err, manifestDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
blob = manifestData.ManifestBlob
|
||||||
|
} else {
|
||||||
|
var indexData repodb.IndexData
|
||||||
|
|
||||||
|
indexBuck := transaction.Bucket([]byte(bolt.IndexDataBucket))
|
||||||
|
idBlob := indexBuck.Get([]byte(manifestDigest))
|
||||||
|
|
||||||
|
if len(idBlob) == 0 {
|
||||||
|
// manifest meta not found, updating signatures with details about validity and author will not be performed
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(idBlob, &indexData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("repodb: %w error while unmashaling index meta for digest %s", err, manifestDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
blob = indexData.IndexBlob
|
||||||
|
}
|
||||||
|
|
||||||
|
// update signatures with details about validity and author
|
||||||
|
repoBuck := transaction.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||||
|
|
||||||
|
repoMetaBlob := repoBuck.Get([]byte(repo))
|
||||||
|
if repoMetaBlob == nil {
|
||||||
|
return zerr.ErrRepoMetaNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var repoMeta repodb.RepoMetadata
|
||||||
|
|
||||||
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestSignatures := repodb.ManifestSignatures{}
|
||||||
|
for sigType, sigs := range repoMeta.Signatures[manifestDigest.String()] {
|
||||||
|
signaturesInfo := []repodb.SignatureInfo{}
|
||||||
|
|
||||||
|
for _, sigInfo := range sigs {
|
||||||
|
layersInfo := []repodb.LayerInfo{}
|
||||||
|
|
||||||
|
for _, layerInfo := range sigInfo.LayersInfo {
|
||||||
|
author, date, isTrusted, _ := signatures.VerifySignature(sigType, layerInfo.LayerContent, layerInfo.SignatureKey,
|
||||||
|
manifestDigest, blob, repo)
|
||||||
|
|
||||||
|
if isTrusted {
|
||||||
|
layerInfo.Signer = author
|
||||||
|
}
|
||||||
|
|
||||||
|
if !date.IsZero() {
|
||||||
|
layerInfo.Signer = author
|
||||||
|
layerInfo.Date = date
|
||||||
|
}
|
||||||
|
|
||||||
|
layersInfo = append(layersInfo, layerInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
signaturesInfo = append(signaturesInfo, repodb.SignatureInfo{
|
||||||
|
SignatureManifestDigest: sigInfo.SignatureManifestDigest,
|
||||||
|
LayersInfo: layersInfo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestSignatures[sigType] = signaturesInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
repoMeta.Signatures[manifestDigest.String()] = manifestSignatures
|
||||||
|
|
||||||
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return repoBuck.Put([]byte(repo), repoMetaBlob)
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (bdw *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
func (bdw *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
||||||
sygMeta repodb.SignatureMetadata,
|
sygMeta repodb.SignatureMetadata,
|
||||||
) error {
|
) error {
|
||||||
|
@ -780,10 +877,17 @@ func (bdw *DBWrapper) AddManifestSignature(repo string, signedManifestDigest god
|
||||||
|
|
||||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||||
LayersInfo: sygMeta.LayersInfo,
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||||
})
|
LayersInfo: sygMeta.LayersInfo,
|
||||||
|
})
|
||||||
|
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||||
|
signatureSlice = []repodb.SignatureInfo{{
|
||||||
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||||
|
LayersInfo: sygMeta.LayersInfo,
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
||||||
|
|
|
@ -3,8 +3,15 @@ package bolt_test
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/notaryproject/notation-core-go/signature/jws"
|
||||||
|
"github.com/notaryproject/notation-go"
|
||||||
|
"github.com/notaryproject/notation-go/signer"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
@ -14,6 +21,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
)
|
)
|
||||||
|
@ -308,6 +316,20 @@ func TestWrapperErrors(t *testing.T) {
|
||||||
})
|
})
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||||
|
repodb.SignatureMetadata{
|
||||||
|
SignatureType: "cosign",
|
||||||
|
SignatureDigest: "digest2",
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(len(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature]),
|
||||||
|
ShouldEqual, 1)
|
||||||
|
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].SignatureManifestDigest,
|
||||||
|
ShouldEqual, "digest2")
|
||||||
|
|
||||||
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||||
repodb.SignatureMetadata{
|
repodb.SignatureMetadata{
|
||||||
SignatureType: "notation",
|
SignatureType: "notation",
|
||||||
|
@ -930,6 +952,231 @@ func TestWrapperErrors(t *testing.T) {
|
||||||
_, err := boltdbWrapper.GetUserRepoMeta(ctx, "repo")
|
_, err := boltdbWrapper.GetUserRepoMeta(ctx, "repo")
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("UpdateSignaturesValidity", func() {
|
||||||
|
Convey("manifestMeta of signed manifest not found", func() {
|
||||||
|
err := boltdbWrapper.UpdateSignaturesValidity("repo", digest.FromString("dig"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("repoMeta of signed manifest not found", func() {
|
||||||
|
// repo Meta not found
|
||||||
|
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||||
|
ManifestBlob: []byte("Bad Manifest"),
|
||||||
|
ConfigBlob: []byte("Bad Manifest"),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo", digest.FromString("dig"))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("manifest - bad content", func() {
|
||||||
|
err := boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||||
|
dataBuck := tx.Bucket([]byte(bolt.ManifestDataBucket))
|
||||||
|
|
||||||
|
return dataBuck.Put([]byte("digest1"), []byte("wrong json"))
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo1", "digest1")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("index - bad content", func() {
|
||||||
|
err := boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||||
|
dataBuck := tx.Bucket([]byte(bolt.IndexDataBucket))
|
||||||
|
|
||||||
|
return dataBuck.Put([]byte("digest1"), []byte("wrong json"))
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo1", "digest1")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("repo - bad content", func() {
|
||||||
|
// repo Meta not found
|
||||||
|
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||||
|
ManifestBlob: []byte("Bad Manifest"),
|
||||||
|
ConfigBlob: []byte("Bad Manifest"),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||||
|
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||||
|
|
||||||
|
return repoBuck.Put([]byte("repo1"), []byte("wrong json"))
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo1", digest.FromString("dig"))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("VerifySignature -> untrusted signature", func() {
|
||||||
|
err := boltdbWrapper.SetManifestData(digest.FromString("dig"), repodb.ManifestData{
|
||||||
|
ManifestBlob: []byte("Bad Manifest"),
|
||||||
|
ConfigBlob: []byte("Bad Manifest"),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||||
|
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||||
|
|
||||||
|
return repoBuck.Put([]byte("repo1"), repoMetaBlob)
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
layerInfo := repodb.LayerInfo{LayerDigest: "", LayerContent: []byte{}, SignatureKey: ""}
|
||||||
|
|
||||||
|
err = boltdbWrapper.AddManifestSignature("repo1", digest.FromString("dig"),
|
||||||
|
repodb.SignatureMetadata{
|
||||||
|
SignatureType: signatures.CosignSignature,
|
||||||
|
SignatureDigest: string(digest.FromString("signature digest")),
|
||||||
|
LayersInfo: []repodb.LayerInfo{layerInfo},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo1", digest.FromString("dig"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repoData, err := boltdbWrapper.GetRepoMeta("repo1")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].LayersInfo[0].Signer,
|
||||||
|
ShouldBeEmpty)
|
||||||
|
So(repoData.Signatures[string(digest.FromString("dig"))][signatures.CosignSignature][0].LayersInfo[0].Date,
|
||||||
|
ShouldBeZeroValue)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("VerifySignature -> trusted signature", func() {
|
||||||
|
_, _, manifest, _ := test.GetRandomImageComponents(10)
|
||||||
|
manifestContent, _ := json.Marshal(manifest)
|
||||||
|
manifestDigest := digest.FromBytes(manifestContent)
|
||||||
|
|
||||||
|
err := boltdbWrapper.SetManifestData(manifestDigest, repodb.ManifestData{
|
||||||
|
ManifestBlob: manifestContent,
|
||||||
|
ConfigBlob: []byte("configContent"),
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.DB.Update(func(tx *bbolt.Tx) error {
|
||||||
|
repoBuck := tx.Bucket([]byte(bolt.RepoMetadataBucket))
|
||||||
|
|
||||||
|
return repoBuck.Put([]byte("repo"), repoMetaBlob)
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
mediaType := jws.MediaTypeEnvelope
|
||||||
|
|
||||||
|
signOpts := notation.SignerSignOptions{
|
||||||
|
SignatureMediaType: mediaType,
|
||||||
|
PluginConfig: map[string]string{},
|
||||||
|
ExpiryDuration: 24 * time.Hour,
|
||||||
|
}
|
||||||
|
|
||||||
|
tdir := t.TempDir()
|
||||||
|
keyName := "notation-sign-test"
|
||||||
|
|
||||||
|
test.NotationPathLock.Lock()
|
||||||
|
defer test.NotationPathLock.Unlock()
|
||||||
|
|
||||||
|
test.LoadNotationPath(tdir)
|
||||||
|
|
||||||
|
err = test.GenerateNotationCerts(tdir, keyName)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// getSigner
|
||||||
|
var newSigner notation.Signer
|
||||||
|
|
||||||
|
// ResolveKey
|
||||||
|
signingKeys, err := test.LoadNotationSigningkeys(tdir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
idx := test.Index(signingKeys.Keys, keyName)
|
||||||
|
So(idx, ShouldBeGreaterThanOrEqualTo, 0)
|
||||||
|
|
||||||
|
key := signingKeys.Keys[idx]
|
||||||
|
|
||||||
|
if key.X509KeyPair != nil {
|
||||||
|
newSigner, err = signer.NewFromFiles(key.X509KeyPair.KeyPath, key.X509KeyPair.CertificatePath)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
descToSign := ispec.Descriptor{
|
||||||
|
MediaType: manifest.MediaType,
|
||||||
|
Digest: manifestDigest,
|
||||||
|
Size: int64(len(manifestContent)),
|
||||||
|
}
|
||||||
|
sig, _, err := newSigner.Sign(ctx, descToSign, signOpts)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
layerInfo := repodb.LayerInfo{
|
||||||
|
LayerDigest: string(digest.FromBytes(sig)),
|
||||||
|
LayerContent: sig, SignatureKey: mediaType,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = boltdbWrapper.AddManifestSignature("repo", manifestDigest,
|
||||||
|
repodb.SignatureMetadata{
|
||||||
|
SignatureType: signatures.NotationSignature,
|
||||||
|
SignatureDigest: string(digest.FromString("signature digest")),
|
||||||
|
LayersInfo: []repodb.LayerInfo{layerInfo},
|
||||||
|
})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitNotationDir(tdir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
trustpolicyPath := path.Join(tdir, "_notation/trustpolicy.json")
|
||||||
|
|
||||||
|
if _, err := os.Stat(trustpolicyPath); errors.Is(err, os.ErrNotExist) {
|
||||||
|
trustPolicy := `
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"trustPolicies": [
|
||||||
|
{
|
||||||
|
"name": "notation-sign-test",
|
||||||
|
"registryScopes": [ "*" ],
|
||||||
|
"signatureVerification": {
|
||||||
|
"level" : "strict"
|
||||||
|
},
|
||||||
|
"trustStores": ["ca:notation-sign-test"],
|
||||||
|
"trustedIdentities": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
file, err := os.Create(trustpolicyPath)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.WriteString(trustPolicy)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
truststore := "_notation/truststore/x509/ca/notation-sign-test"
|
||||||
|
truststoreSrc := "notation/truststore/x509/ca/notation-sign-test"
|
||||||
|
err = os.MkdirAll(path.Join(tdir, truststore), 0o755)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.CopyFile(path.Join(tdir, truststoreSrc, "notation-sign-test.crt"),
|
||||||
|
path.Join(tdir, truststore, "notation-sign-test.crt"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = boltdbWrapper.UpdateSignaturesValidity("repo", manifestDigest) //nolint:contextcheck
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
repoData, err := boltdbWrapper.GetRepoMeta("repo")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(repoData.Signatures[string(manifestDigest)][signatures.NotationSignature][0].LayersInfo[0].Signer,
|
||||||
|
ShouldNotBeEmpty)
|
||||||
|
So(repoData.Signatures[string(manifestDigest)][signatures.NotationSignature][0].LayersInfo[0].Date,
|
||||||
|
ShouldNotBeZeroValue)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/meta/common"
|
"zotregistry.io/zot/pkg/meta/common"
|
||||||
"zotregistry.io/zot/pkg/meta/dynamo"
|
"zotregistry.io/zot/pkg/meta/dynamo"
|
||||||
"zotregistry.io/zot/pkg/meta/repodb" //nolint:go-staticcheck
|
"zotregistry.io/zot/pkg/meta/repodb" //nolint:go-staticcheck
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/meta/version"
|
"zotregistry.io/zot/pkg/meta/version"
|
||||||
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
||||||
)
|
)
|
||||||
|
@ -616,6 +617,10 @@ func (dwr *DBWrapper) IncrementImageDownloads(repo string, reference string) err
|
||||||
return dwr.SetRepoMeta(repo, repoMeta)
|
return dwr.SetRepoMeta(repo, repoMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dwr *DBWrapper) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (dwr *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
func (dwr *DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
||||||
sygMeta repodb.SignatureMetadata,
|
sygMeta repodb.SignatureMetadata,
|
||||||
) error {
|
) error {
|
||||||
|
@ -656,10 +661,17 @@ func (dwr *DBWrapper) AddManifestSignature(repo string, signedManifestDigest god
|
||||||
|
|
||||||
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
||||||
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
||||||
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
if sygMeta.SignatureType == signatures.NotationSignature {
|
||||||
SignatureManifestDigest: sygMeta.SignatureDigest,
|
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
||||||
LayersInfo: sygMeta.LayersInfo,
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||||
})
|
LayersInfo: sygMeta.LayersInfo,
|
||||||
|
})
|
||||||
|
} else if sygMeta.SignatureType == signatures.CosignSignature {
|
||||||
|
signatureSlice = []repodb.SignatureInfo{{
|
||||||
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
||||||
|
LayersInfo: sygMeta.LayersInfo,
|
||||||
|
}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
||||||
|
|
|
@ -7,13 +7,6 @@ import (
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
SignaturesDirPath = "/tmp/zot/signatures"
|
|
||||||
SigKey = "dev.cosignproject.cosign/signature"
|
|
||||||
NotationType = "notation"
|
|
||||||
CosignType = "cosign"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Used to model changes to an object after a call to the DB.
|
// Used to model changes to an object after a call to the DB.
|
||||||
type ToggleState int
|
type ToggleState int
|
||||||
|
|
||||||
|
@ -97,6 +90,9 @@ type RepoDB interface { //nolint:interfacebloat
|
||||||
// DeleteSignature delets signature metadata to a given manifest from the database
|
// DeleteSignature delets signature metadata to a given manifest from the database
|
||||||
DeleteSignature(repo string, signedManifestDigest godigest.Digest, sm SignatureMetadata) error
|
DeleteSignature(repo string, signedManifestDigest godigest.Digest, sm SignatureMetadata) error
|
||||||
|
|
||||||
|
// UpdateSignaturesValidity checks and updates signatures validity of a given manifest
|
||||||
|
UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error
|
||||||
|
|
||||||
// SearchRepos searches for repos given a search string
|
// SearchRepos searches for repos given a search string
|
||||||
SearchRepos(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) (
|
SearchRepos(ctx context.Context, searchText string, filter Filter, requestedPage PageInput) (
|
||||||
[]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
|
[]RepoMetadata, map[string]ManifestMetadata, map[string]IndexData, PageInfo, error)
|
||||||
|
@ -183,6 +179,7 @@ type LayerInfo struct {
|
||||||
LayerContent []byte
|
LayerContent []byte
|
||||||
SignatureKey string
|
SignatureKey string
|
||||||
Signer string
|
Signer string
|
||||||
|
Date time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type SignatureInfo struct {
|
type SignatureInfo struct {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
boltdb_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||||
dynamodb_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
dynamodb_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
)
|
)
|
||||||
|
|
||||||
func New(storageConfig config.StorageConfig, log log.Logger) (repodb.RepoDB, error) {
|
func New(storageConfig config.StorageConfig, log log.Logger) (repodb.RepoDB, error) {
|
||||||
|
@ -34,6 +35,11 @@ func New(storageConfig config.StorageConfig, log log.Logger) (repodb.RepoDB, err
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = signatures.InitCosignAndNotationDirs(params.RootDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return Create("boltdb", driver, params, log) //nolint:contextcheck
|
return Create("boltdb", driver, params, log) //nolint:contextcheck
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,13 @@ package repodbfactory_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
"zotregistry.io/zot/pkg/meta/bolt"
|
"zotregistry.io/zot/pkg/meta/bolt"
|
||||||
"zotregistry.io/zot/pkg/meta/dynamo"
|
"zotregistry.io/zot/pkg/meta/dynamo"
|
||||||
|
@ -73,6 +75,31 @@ func TestCreateBoltDB(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
Convey("InitCosignAndNotationDirs fails", t, func() {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
|
||||||
|
var storageConfig config.StorageConfig
|
||||||
|
|
||||||
|
storageConfig.RootDirectory = rootDir
|
||||||
|
storageConfig.RemoteCache = false
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
_, err := os.Create(path.Join(rootDir, "repo.db"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = os.Chmod(rootDir, 0o555)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
newRepodb, err := repodbfactory.New(storageConfig, log)
|
||||||
|
So(newRepodb, ShouldBeNil)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
err = os.Chmod(rootDir, 0o777)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func skipDynamo(t *testing.T) {
|
func skipDynamo(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
zerr "zotregistry.io/zot/errors"
|
zerr "zotregistry.io/zot/errors"
|
||||||
zcommon "zotregistry.io/zot/pkg/common"
|
zcommon "zotregistry.io/zot/pkg/common"
|
||||||
"zotregistry.io/zot/pkg/log"
|
"zotregistry.io/zot/pkg/log"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -105,15 +106,30 @@ func ParseRepo(repo string, repoDB RepoDB, storeController storage.StoreControll
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSignature {
|
if isSignature {
|
||||||
err := repoDB.AddManifestSignature(repo, signedManifestDigest,
|
layers, err := GetSignatureLayersInfo(repo, tag, manifest.Digest.String(), signatureType, manifestBlob,
|
||||||
|
imageStore, log)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repoDB.AddManifestSignature(repo, signedManifestDigest,
|
||||||
SignatureMetadata{
|
SignatureMetadata{
|
||||||
SignatureType: signatureType,
|
SignatureType: signatureType,
|
||||||
SignatureDigest: digest.String(),
|
SignatureDigest: digest.String(),
|
||||||
|
LayersInfo: layers,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
log.Error().Err(err).Str("repository", repo).Str("tag", tag).
|
||||||
Str("manifestDigest", signedManifestDigest.String()).
|
Str("manifestDigest", signedManifestDigest.String()).
|
||||||
Msg("load-repo: failed set signature meta for signed image manifest digest")
|
Msg("load-repo: failed set signature meta for signed image")
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repoDB.UpdateSignaturesValidity(repo, signedManifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("digest", signedManifestDigest.String()).Msg(
|
||||||
|
"load-repo: failed verify signatures validity for signed image")
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -199,6 +215,98 @@ func isManifestMetaPresent(repo string, manifest ispec.Descriptor, repoDB RepoDB
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetSignatureLayersInfo(
|
||||||
|
repo, tag, manifestDigest, signatureType string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||||
|
) ([]LayerInfo, error) {
|
||||||
|
switch signatureType {
|
||||||
|
case signatures.CosignSignature:
|
||||||
|
return getCosignSignatureLayersInfo(repo, tag, manifestDigest, manifestBlob, imageStore, log)
|
||||||
|
case signatures.NotationSignature:
|
||||||
|
return getNotationSignatureLayersInfo(repo, manifestDigest, manifestBlob, imageStore, log)
|
||||||
|
default:
|
||||||
|
return []LayerInfo{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCosignSignatureLayersInfo(
|
||||||
|
repo, tag, manifestDigest string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||||
|
) ([]LayerInfo, error) {
|
||||||
|
layers := []LayerInfo{}
|
||||||
|
|
||||||
|
var manifestContent ispec.Manifest
|
||||||
|
if err := json.Unmarshal(manifestBlob, &manifestContent); err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("digest", manifestDigest).Msg(
|
||||||
|
"load-repo: unable to marshal blob index")
|
||||||
|
|
||||||
|
return layers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, layer := range manifestContent.Layers {
|
||||||
|
layerContent, err := imageStore.GetBlobContent(repo, layer.Digest)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("layerDigest", layer.Digest.String()).Msg(
|
||||||
|
"load-repo: unable to get cosign signature layer content")
|
||||||
|
|
||||||
|
return layers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
layerSigKey, ok := layer.Annotations[signatures.CosignSigKey]
|
||||||
|
if !ok {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", tag).Str("layerDigest", layer.Digest.String()).Msg(
|
||||||
|
"load-repo: unable to get specific annotation of cosign signature")
|
||||||
|
}
|
||||||
|
|
||||||
|
layers = append(layers, LayerInfo{
|
||||||
|
LayerDigest: layer.Digest.String(),
|
||||||
|
LayerContent: layerContent,
|
||||||
|
SignatureKey: layerSigKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNotationSignatureLayersInfo(
|
||||||
|
repo, manifestDigest string, manifestBlob []byte, imageStore storage.ImageStore, log log.Logger,
|
||||||
|
) ([]LayerInfo, error) {
|
||||||
|
layers := []LayerInfo{}
|
||||||
|
|
||||||
|
var manifestContent ispec.Manifest
|
||||||
|
if err := json.Unmarshal(manifestBlob, &manifestContent); err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", manifestDigest).Msg(
|
||||||
|
"load-repo: unable to marshal blob index")
|
||||||
|
|
||||||
|
return layers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(manifestContent.Layers) != 1 {
|
||||||
|
log.Error().Err(zerr.ErrBadManifest).Str("repository", repo).Str("reference", manifestDigest).
|
||||||
|
Msg("load-repo: notation signature manifest requires exactly one layer but it does not")
|
||||||
|
|
||||||
|
return layers, zerr.ErrBadManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
layer := manifestContent.Layers[0].Digest
|
||||||
|
|
||||||
|
layerContent, err := imageStore.GetBlobContent(repo, layer)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", manifestDigest).Str("layerDigest", layer.String()).Msg(
|
||||||
|
"load-repo: unable to get notation signature blob content")
|
||||||
|
|
||||||
|
return layers, err
|
||||||
|
}
|
||||||
|
|
||||||
|
layerSigKey := manifestContent.Layers[0].MediaType
|
||||||
|
|
||||||
|
layers = append(layers, LayerInfo{
|
||||||
|
LayerDigest: layer.String(),
|
||||||
|
LayerContent: layerContent,
|
||||||
|
SignatureKey: layerSigKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
return layers, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewManifestMeta takes raw data about an image and createa a new ManifestMetadate object.
|
// NewManifestMeta takes raw data about an image and createa a new ManifestMetadate object.
|
||||||
func NewManifestData(repoName string, manifestBlob []byte, imageStore storage.ImageStore,
|
func NewManifestData(repoName string, manifestBlob []byte, imageStore storage.ImageStore,
|
||||||
) (ManifestData, error) {
|
) (ManifestData, error) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"zotregistry.io/zot/pkg/meta/repodb"
|
"zotregistry.io/zot/pkg/meta/repodb"
|
||||||
bolt_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
bolt_wrapper "zotregistry.io/zot/pkg/meta/repodb/boltdb-wrapper"
|
||||||
dynamo_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
dynamo_wrapper "zotregistry.io/zot/pkg/meta/repodb/dynamodb-wrapper"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
"zotregistry.io/zot/pkg/storage"
|
"zotregistry.io/zot/pkg/storage"
|
||||||
"zotregistry.io/zot/pkg/storage/local"
|
"zotregistry.io/zot/pkg/storage/local"
|
||||||
"zotregistry.io/zot/pkg/test"
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
@ -242,6 +243,7 @@ func TestParseStorageErrors(t *testing.T) {
|
||||||
Digest: "123",
|
Digest: "123",
|
||||||
},
|
},
|
||||||
ArtifactType: "application/vnd.cncf.notary.signature",
|
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||||
|
Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
|
||||||
}
|
}
|
||||||
|
|
||||||
manifestBlob, err := json.Marshal(manifestContent)
|
manifestBlob, err := json.Marshal(manifestContent)
|
||||||
|
@ -259,6 +261,100 @@ func TestParseStorageErrors(t *testing.T) {
|
||||||
|
|
||||||
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||||
So(err, ShouldNotBeNil)
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
repoDB.AddManifestSignatureFn = func(repo string, signedManifestDigest godigest.Digest,
|
||||||
|
sm repodb.SignatureMetadata,
|
||||||
|
) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repoDB.UpdateSignaturesValidityFn = func(repo string, signedManifestDigest godigest.Digest,
|
||||||
|
) error {
|
||||||
|
return ErrTestError
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetSignatureLayersInfo errors", func() {
|
||||||
|
// get notation signature layers info
|
||||||
|
badNotationManifestContent := ispec.Manifest{
|
||||||
|
Subject: &ispec.Descriptor{
|
||||||
|
Digest: "123",
|
||||||
|
},
|
||||||
|
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||||
|
}
|
||||||
|
|
||||||
|
badNotationManifestBlob, err := json.Marshal(badNotationManifestContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return badNotationManifestBlob, "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrong number of layers of notation manifest
|
||||||
|
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
notationManifestContent := ispec.Manifest{
|
||||||
|
Subject: &ispec.Descriptor{
|
||||||
|
Digest: "123",
|
||||||
|
},
|
||||||
|
ArtifactType: "application/vnd.cncf.notary.signature",
|
||||||
|
Layers: []ispec.Descriptor{{MediaType: ispec.MediaTypeImageLayer}},
|
||||||
|
}
|
||||||
|
|
||||||
|
notationManifestBlob, err := json.Marshal(notationManifestContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return notationManifestBlob, "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
imageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||||
|
return []byte{}, ErrTestError
|
||||||
|
}
|
||||||
|
|
||||||
|
// unable to get layer content
|
||||||
|
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, _, cosignManifestContent, _ := test.GetRandomImageComponents(10)
|
||||||
|
_, _, signedManifest, _ := test.GetRandomImageComponents(10)
|
||||||
|
signatureTag, err := test.GetCosignSignatureTagForManifest(signedManifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cosignManifestContent.Annotations = map[string]string{ispec.AnnotationRefName: signatureTag}
|
||||||
|
|
||||||
|
cosignManifestBlob, err := json.Marshal(cosignManifestContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return cosignManifestBlob, "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
indexContent := ispec.Index{
|
||||||
|
Manifests: []ispec.Descriptor{
|
||||||
|
{
|
||||||
|
Digest: godigest.FromString("cosignSig"),
|
||||||
|
MediaType: ispec.MediaTypeImageManifest,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
ispec.AnnotationRefName: signatureTag,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
indexBlob, err := json.Marshal(indexContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetIndexContentFn = func(repo string) ([]byte, error) {
|
||||||
|
return indexBlob, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unable to get layer content
|
||||||
|
err = repodb.ParseRepo("repo", repoDB, storeController, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -534,3 +630,22 @@ func skipIt(t *testing.T) {
|
||||||
t.Skip("Skipping testing without AWS S3 mock server")
|
t.Skip("Skipping testing without AWS S3 mock server")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetSignatureLayersInfo(t *testing.T) {
|
||||||
|
Convey("wrong signature type", t, func() {
|
||||||
|
layers, err := repodb.GetSignatureLayersInfo("repo", "tag", "123", "wrong signature type", []byte{},
|
||||||
|
nil, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(layers, ShouldBeEmpty)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("error while unmarshaling manifest content", t, func() {
|
||||||
|
_, err := repodb.GetSignatureLayersInfo("repo", "tag", "123", signatures.CosignSignature, []byte("bad manifest"),
|
||||||
|
nil, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
_, err = repodb.GetSignatureLayersInfo("repo", "tag", "123", signatures.NotationSignature, []byte("bad manifest"),
|
||||||
|
nil, log.NewLogger("debug", ""))
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
113
pkg/meta/signatures/cosign.go
Normal file
113
pkg/meta/signatures/cosign.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package signatures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
"github.com/sigstore/cosign/v2/pkg/cosign/pkcs11key"
|
||||||
|
sigs "github.com/sigstore/cosign/v2/pkg/signature"
|
||||||
|
"github.com/sigstore/sigstore/pkg/signature/options"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CosignSigKey = "dev.cosignproject.cosign/signature"
|
||||||
|
cosignDirRelativePath = "_cosign"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cosignDir = "" //nolint:gochecknoglobals
|
||||||
|
|
||||||
|
func InitCosignDir(rootDir string) error {
|
||||||
|
dir := path.Join(rootDir, cosignDirRelativePath)
|
||||||
|
|
||||||
|
_, err := os.Stat(dir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(dir, defaultDirPerms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
cosignDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCosignDirPath() (string, error) {
|
||||||
|
if cosignDir != "" {
|
||||||
|
return cosignDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", zerr.ErrSignConfigDirNotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyCosignSignature(
|
||||||
|
repo string, digest godigest.Digest, signatureKey string, layerContent []byte,
|
||||||
|
) (string, bool, error) {
|
||||||
|
cosignDir, err := GetCosignDirPath()
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := os.ReadDir(cosignDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if !file.IsDir() {
|
||||||
|
// cosign verify the image
|
||||||
|
ctx := context.Background()
|
||||||
|
keyRef := path.Join(cosignDir, file.Name())
|
||||||
|
hashAlgorithm := crypto.SHA256
|
||||||
|
|
||||||
|
pubKey, err := sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, hashAlgorithm)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkcs11Key, ok := pubKey.(*pkcs11key.Key)
|
||||||
|
if ok {
|
||||||
|
defer pkcs11Key.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
verifier := pubKey
|
||||||
|
|
||||||
|
b64sig := signatureKey
|
||||||
|
|
||||||
|
signature, err := base64.StdEncoding.DecodeString(b64sig)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed := io.NopCloser(bytes.NewReader(layerContent))
|
||||||
|
|
||||||
|
payload, err := io.ReadAll(compressed)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = verifier.VerifySignature(bytes.NewReader(signature), bytes.NewReader(payload), options.WithContext(ctx))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
publicKey, err := os.ReadFile(keyRef)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(publicKey), true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false, nil
|
||||||
|
}
|
167
pkg/meta/signatures/notation.go
Normal file
167
pkg/meta/signatures/notation.go
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
package signatures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/notaryproject/notation-go"
|
||||||
|
"github.com/notaryproject/notation-go/dir"
|
||||||
|
"github.com/notaryproject/notation-go/plugin"
|
||||||
|
"github.com/notaryproject/notation-go/verifier"
|
||||||
|
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||||
|
"github.com/notaryproject/notation-go/verifier/truststore"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const notationDirRelativePath = "_notation"
|
||||||
|
|
||||||
|
var notationDir = "" //nolint:gochecknoglobals
|
||||||
|
|
||||||
|
func InitNotationDir(rootDir string) error {
|
||||||
|
dir := path.Join(rootDir, notationDirRelativePath)
|
||||||
|
|
||||||
|
_, err := os.Stat(dir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(dir, defaultDirPerms)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
notationDir = dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNotationDirPath() (string, error) {
|
||||||
|
if notationDir != "" {
|
||||||
|
return notationDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", zerr.ErrSignConfigDirNotSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equivalent function for trustpolicy.LoadDocument() but using a specific SysFS not the one returned by ConfigFS().
|
||||||
|
func LoadTrustPolicyDocument(notationDir string) (*trustpolicy.Document, error) {
|
||||||
|
jsonFile, err := dir.NewSysFS(notationDir).Open(dir.PathTrustPolicy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer jsonFile.Close()
|
||||||
|
|
||||||
|
policyDocument := &trustpolicy.Document{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(jsonFile).Decode(policyDocument)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return policyDocument, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromConfig returns a verifier based on local file system.
|
||||||
|
// Equivalent function for verifier.NewFromConfig()
|
||||||
|
// but using LoadTrustPolicyDocumnt() function instead of trustpolicy.LoadDocument() function.
|
||||||
|
func NewFromConfig() (notation.Verifier, error) {
|
||||||
|
notationDir, err := GetNotationDirPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load trust policy.
|
||||||
|
policyDocument, err := LoadTrustPolicyDocument(notationDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load trust store.
|
||||||
|
x509TrustStore := truststore.NewX509TrustStore(dir.NewSysFS(notationDir))
|
||||||
|
|
||||||
|
return verifier.New(policyDocument, x509TrustStore,
|
||||||
|
plugin.NewCLIManager(dir.NewSysFS(path.Join(notationDir, dir.PathPlugins))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifyNotationSignature(
|
||||||
|
artifactDescriptor ispec.Descriptor, artifactReference string, rawSignature []byte, signatureMediaType string,
|
||||||
|
) (string, time.Time, bool, error) {
|
||||||
|
var (
|
||||||
|
date time.Time
|
||||||
|
author string
|
||||||
|
)
|
||||||
|
|
||||||
|
// If there's no signature associated with the reference.
|
||||||
|
if len(rawSignature) == 0 {
|
||||||
|
return author, date, false, notation.ErrorSignatureRetrievalFailed{
|
||||||
|
Msg: fmt.Sprintf("no signature associated with %q is provided, make sure the image was signed successfully",
|
||||||
|
artifactReference),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize verifier.
|
||||||
|
verifier, err := NewFromConfig()
|
||||||
|
if err != nil {
|
||||||
|
return author, date, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Set VerifyOptions.
|
||||||
|
opts := notation.VerifierVerifyOptions{
|
||||||
|
// ArtifactReference is important to validate registry scope format
|
||||||
|
// If "registryScopes" field from trustpolicy.json file is not wildcard then "domain:80/repo@" should not be hardcoded
|
||||||
|
ArtifactReference: "domain:80/repo@" + artifactReference,
|
||||||
|
SignatureMediaType: signatureMediaType,
|
||||||
|
PluginConfig: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the notation signature which should be associated with the artifactDescriptor.
|
||||||
|
outcome, err := verifier.Verify(ctx, artifactDescriptor, rawSignature, opts)
|
||||||
|
if outcome.EnvelopeContent != nil {
|
||||||
|
author = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].Subject.String()
|
||||||
|
|
||||||
|
if outcome.VerificationLevel == trustpolicy.LevelStrict && (err == nil ||
|
||||||
|
CheckExpiryErr(outcome.VerificationResults, outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter, err)) {
|
||||||
|
expiry := outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
|
||||||
|
if !expiry.IsZero() && expiry.Before(outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter) {
|
||||||
|
date = outcome.EnvelopeContent.SignerInfo.SignedAttributes.Expiry
|
||||||
|
} else {
|
||||||
|
date = outcome.EnvelopeContent.SignerInfo.CertificateChain[0].NotAfter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return author, date, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verification Succeeded.
|
||||||
|
return author, date, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckExpiryErr(verificationResults []*notation.ValidationResult, notAfter time.Time, err error) bool {
|
||||||
|
for _, result := range verificationResults {
|
||||||
|
if result.Type == trustpolicy.TypeExpiry {
|
||||||
|
if errors.Is(err, result.Error) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if result.Type == trustpolicy.TypeAuthenticTimestamp {
|
||||||
|
if errors.Is(err, result.Error) && time.Now().After(notAfter) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
59
pkg/meta/signatures/signatures.go
Normal file
59
pkg/meta/signatures/signatures.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package signatures
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
godigest "github.com/opencontainers/go-digest"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CosignSignature = "cosign"
|
||||||
|
NotationSignature = "notation"
|
||||||
|
defaultDirPerms = 0o700
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitCosignAndNotationDirs(rootDir string) error {
|
||||||
|
err := InitCosignDir(rootDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = InitNotationDir(rootDir)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func VerifySignature(
|
||||||
|
signatureType string, rawSignature []byte, sigKey string, manifestDigest godigest.Digest, manifestContent []byte,
|
||||||
|
repo string,
|
||||||
|
) (string, time.Time, bool, error) {
|
||||||
|
var manifest ispec.Manifest
|
||||||
|
if err := json.Unmarshal(manifestContent, &manifest); err != nil {
|
||||||
|
return "", time.Time{}, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := ispec.Descriptor{
|
||||||
|
MediaType: manifest.MediaType,
|
||||||
|
Digest: manifestDigest,
|
||||||
|
Size: int64(len(manifestContent)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifestDigest.String() == "" {
|
||||||
|
return "", time.Time{}, false, zerr.ErrBadManifestDigest
|
||||||
|
}
|
||||||
|
|
||||||
|
switch signatureType {
|
||||||
|
case CosignSignature:
|
||||||
|
author, isValid, err := VerifyCosignSignature(repo, manifestDigest, sigKey, rawSignature)
|
||||||
|
|
||||||
|
return author, time.Time{}, isValid, err
|
||||||
|
case NotationSignature:
|
||||||
|
return VerifyNotationSignature(desc, manifestDigest.String(), rawSignature, sigKey)
|
||||||
|
default:
|
||||||
|
return "", time.Time{}, false, zerr.ErrInvalidSignatureType
|
||||||
|
}
|
||||||
|
}
|
439
pkg/meta/signatures/signatures_test.go
Normal file
439
pkg/meta/signatures/signatures_test.go
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
package signatures_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/notaryproject/notation-go"
|
||||||
|
"github.com/notaryproject/notation-go/verifier/trustpolicy"
|
||||||
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/sigstore/cosign/v2/cmd/cosign/cli/generate"
|
||||||
|
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
|
||||||
|
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
|
||||||
|
zerr "zotregistry.io/zot/errors"
|
||||||
|
"zotregistry.io/zot/pkg/api"
|
||||||
|
"zotregistry.io/zot/pkg/api/config"
|
||||||
|
"zotregistry.io/zot/pkg/meta/signatures"
|
||||||
|
"zotregistry.io/zot/pkg/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errExpiryError = errors.New("expiry err")
|
||||||
|
|
||||||
|
func TestInitCosignAndNotationDirs(t *testing.T) {
|
||||||
|
Convey("InitCosignDir error", t, func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
err := os.Chmod(dir, 0o000)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitCosignAndNotationDirs(dir)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
err = os.Chmod(dir, 0o500)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitCosignAndNotationDirs(dir)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
cosignDir, err := signatures.GetCosignDirPath()
|
||||||
|
So(cosignDir, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("InitNotationDir error", t, func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
err := os.Chmod(dir, 0o000)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitCosignAndNotationDirs(dir)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
err = os.Chmod(dir, 0o500)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitCosignAndNotationDirs(dir)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitNotationDir(dir)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
|
||||||
|
notationDir, err := signatures.GetNotationDirPath()
|
||||||
|
So(notationDir, ShouldBeEmpty)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifySignatures(t *testing.T) {
|
||||||
|
Convey("wrong manifest content", t, func() {
|
||||||
|
manifestContent := []byte("wrong json")
|
||||||
|
|
||||||
|
_, _, _, err := signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("empty manifest digest", t, func() {
|
||||||
|
image, err := test.GetRandomImage("image")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestContent, err := json.Marshal(image.Manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, _, err = signatures.VerifySignature("", []byte(""), "", "", manifestContent, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrBadManifestDigest)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("wrong signature type", t, func() {
|
||||||
|
image, err := test.GetRandomImage("image")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestContent, err := json.Marshal(image.Manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest, err := image.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, _, err = signatures.VerifySignature("wrongType", []byte(""), "", manifestDigest, manifestContent, "repo")
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrInvalidSignatureType)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("verify cosign signature", t, func() {
|
||||||
|
repo := "repo"
|
||||||
|
tag := "test"
|
||||||
|
image, err := test.GetRandomImage(tag)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestContent, err := json.Marshal(image.Manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest, err := image.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("cosignDir is not set", func() {
|
||||||
|
_, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("cosignDir does not have read permissions", func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
err := signatures.InitCosignDir(dir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cosignDir, err := signatures.GetCosignDirPath()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
err = os.Chmod(cosignDir, 0o300)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, _, err = signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("no valid public key", func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
err := signatures.InitCosignDir(dir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cosignDir, err := signatures.GetCosignDirPath()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.WriteFileWithPermission(path.Join(cosignDir, "file"), []byte("not a public key"), 0o600, false)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, isTrusted, err := signatures.VerifySignature("cosign", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(isTrusted, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("signature is trusted", func() {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
conf.Storage.GC = false
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = rootDir
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitCosignDir(rootDir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cosignDir, err := signatures.GetCosignDirPath()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_ = os.Chdir(cosignDir)
|
||||||
|
|
||||||
|
// generate a keypair
|
||||||
|
os.Setenv("COSIGN_PASSWORD", "")
|
||||||
|
err = generate.GenerateKeyPairCmd(context.TODO(), "", "cosign", nil)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_ = os.Chdir(cwd)
|
||||||
|
|
||||||
|
// sign the image
|
||||||
|
err = sign.SignCmd(&options.RootOptions{Verbose: true, Timeout: 1 * time.Minute},
|
||||||
|
options.KeyOpts{KeyRef: path.Join(cosignDir, "cosign.key"), PassFunc: generate.GetPass},
|
||||||
|
options.SignOptions{
|
||||||
|
Registry: options.RegistryOptions{AllowInsecure: true},
|
||||||
|
AnnotationOptions: options.AnnotationOptions{Annotations: []string{fmt.Sprintf("tag=%s", tag)}},
|
||||||
|
Upload: true,
|
||||||
|
},
|
||||||
|
[]string{fmt.Sprintf("localhost:%s/%s@%s", port, repo, manifestDigest.String())})
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = os.Remove(path.Join(cosignDir, "cosign.key"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var index ispec.Index
|
||||||
|
err = json.Unmarshal(indexContent, &index)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var rawSignature []byte
|
||||||
|
var sigKey string
|
||||||
|
|
||||||
|
for _, manifest := range index.Manifests {
|
||||||
|
if manifest.Digest != manifestDigest {
|
||||||
|
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var cosignSig ispec.Manifest
|
||||||
|
|
||||||
|
err = json.Unmarshal(blobContent, &cosignSig)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
sigKey = cosignSig.Layers[0].Annotations[signatures.CosignSigKey]
|
||||||
|
|
||||||
|
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, cosignSig.Layers[0].Digest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature is trusted
|
||||||
|
author, _, isTrusted, err := signatures.VerifySignature("cosign", rawSignature, sigKey, manifestDigest,
|
||||||
|
manifestContent, repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(isTrusted, ShouldBeTrue)
|
||||||
|
So(author, ShouldNotBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("verify notation signature", t, func() {
|
||||||
|
repo := "repo"
|
||||||
|
tag := "test"
|
||||||
|
image, err := test.GetRandomImage(tag)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestContent, err := json.Marshal(image.Manifest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
manifestDigest, err := image.Digest()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
Convey("notationDir is not set", func() {
|
||||||
|
_, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(err, ShouldEqual, zerr.ErrSignConfigDirNotSet)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("no signature provided", func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
err := signatures.InitNotationDir(dir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, isTrusted, err := signatures.VerifySignature("notation", []byte(""), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(isTrusted, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("trustpolicy.json does not exist", func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
err := signatures.InitNotationDir(dir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("trustpolicy.json has invalid content", func() {
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
err := signatures.InitNotationDir(dir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
notationDir, err := signatures.GetNotationDirPath()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte("invalid content"),
|
||||||
|
0o600, false)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
_, _, _, err = signatures.VerifySignature("notation", []byte("signature"), "", manifestDigest, manifestContent,
|
||||||
|
repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("signature is trusted", func() {
|
||||||
|
rootDir := t.TempDir()
|
||||||
|
|
||||||
|
port := test.GetFreePort()
|
||||||
|
baseURL := test.GetBaseURL(port)
|
||||||
|
conf := config.New()
|
||||||
|
conf.HTTP.Port = port
|
||||||
|
conf.Storage.GC = false
|
||||||
|
ctlr := api.NewController(conf)
|
||||||
|
ctlr.Config.Storage.RootDirectory = rootDir
|
||||||
|
|
||||||
|
cm := test.NewControllerManager(ctlr)
|
||||||
|
cm.StartAndWait(conf.HTTP.Port)
|
||||||
|
defer cm.StopServer()
|
||||||
|
|
||||||
|
err := test.UploadImage(image, baseURL, repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = signatures.InitNotationDir(rootDir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
notationDir, err := signatures.GetNotationDirPath()
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
test.NotationPathLock.Lock()
|
||||||
|
defer test.NotationPathLock.Unlock()
|
||||||
|
|
||||||
|
test.LoadNotationPath(notationDir)
|
||||||
|
|
||||||
|
// generate a keypair
|
||||||
|
err = test.GenerateNotationCerts(notationDir, "notation-sign-test")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// sign the image
|
||||||
|
image := fmt.Sprintf("localhost:%s/%s", port, fmt.Sprintf("%s:%s", repo, tag))
|
||||||
|
|
||||||
|
err = test.SignWithNotation("notation-sign-test", image, notationDir)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = test.CopyFiles(path.Join(notationDir, "notation", "truststore"), path.Join(notationDir, "truststore"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
err = os.RemoveAll(path.Join(notationDir, "notation"))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
trustPolicy := `
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"trustPolicies": [
|
||||||
|
{
|
||||||
|
"name": "notation-sign-test",
|
||||||
|
"registryScopes": [ "*" ],
|
||||||
|
"signatureVerification": {
|
||||||
|
"level" : "strict"
|
||||||
|
},
|
||||||
|
"trustStores": ["ca:notation-sign-test"],
|
||||||
|
"trustedIdentities": [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
err = test.WriteFileWithPermission(path.Join(notationDir, "trustpolicy.json"), []byte(trustPolicy), 0o600, false)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
indexContent, err := ctlr.StoreController.DefaultStore.GetIndexContent(repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var index ispec.Index
|
||||||
|
err = json.Unmarshal(indexContent, &index)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var rawSignature []byte
|
||||||
|
var sigKey string
|
||||||
|
|
||||||
|
for _, manifest := range index.Manifests {
|
||||||
|
if manifest.Digest != manifestDigest {
|
||||||
|
blobContent, err := ctlr.StoreController.DefaultStore.GetBlobContent(repo, manifest.Digest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
var notationSig ispec.Manifest
|
||||||
|
|
||||||
|
err = json.Unmarshal(blobContent, ¬ationSig)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
sigKey = notationSig.Layers[0].MediaType
|
||||||
|
|
||||||
|
rawSignature, err = ctlr.StoreController.DefaultStore.GetBlobContent(repo, notationSig.Layers[0].Digest)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// signature is trusted
|
||||||
|
author, _, isTrusted, err := signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
|
||||||
|
manifestContent, repo)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(isTrusted, ShouldBeTrue)
|
||||||
|
So(author, ShouldNotBeEmpty)
|
||||||
|
|
||||||
|
err = os.Truncate(path.Join(notationDir, "truststore/x509/ca/notation-sign-test/notation-sign-test.crt"), 0)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// signature is not trusted
|
||||||
|
author, _, isTrusted, err = signatures.VerifySignature("notation", rawSignature, sigKey, manifestDigest,
|
||||||
|
manifestContent, repo)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
So(isTrusted, ShouldBeFalse)
|
||||||
|
So(author, ShouldNotBeEmpty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckExpiryErr(t *testing.T) {
|
||||||
|
Convey("no expiry err", t, func() {
|
||||||
|
isExpiryErr := signatures.CheckExpiryErr([]*notation.ValidationResult{{Error: nil, Type: "wrongtype"}}, time.Now(),
|
||||||
|
nil)
|
||||||
|
So(isExpiryErr, ShouldBeFalse)
|
||||||
|
|
||||||
|
isExpiryErr = signatures.CheckExpiryErr([]*notation.ValidationResult{{
|
||||||
|
Error: nil, Type: trustpolicy.TypeAuthenticTimestamp,
|
||||||
|
}}, time.Now(), errExpiryError)
|
||||||
|
So(isExpiryErr, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("expiry err", t, func() {
|
||||||
|
isExpiryErr := signatures.CheckExpiryErr([]*notation.ValidationResult{
|
||||||
|
{Error: errExpiryError, Type: trustpolicy.TypeExpiry},
|
||||||
|
}, time.Now(), errExpiryError)
|
||||||
|
So(isExpiryErr, ShouldBeTrue)
|
||||||
|
|
||||||
|
isExpiryErr = signatures.CheckExpiryErr([]*notation.ValidationResult{
|
||||||
|
{Error: errExpiryError, Type: trustpolicy.TypeAuthenticTimestamp},
|
||||||
|
}, time.Now().AddDate(0, 0, -1), errExpiryError)
|
||||||
|
So(isExpiryErr, ShouldBeTrue)
|
||||||
|
})
|
||||||
|
}
|
|
@ -34,13 +34,28 @@ func OnUpdateManifest(repo, reference, mediaType string, digest godigest.Digest,
|
||||||
metadataSuccessfullySet := true
|
metadataSuccessfullySet := true
|
||||||
|
|
||||||
if isSignature {
|
if isSignature {
|
||||||
err = repoDB.AddManifestSignature(repo, signedManifestDigest, repodb.SignatureMetadata{
|
layersInfo, errGetLayers := repodb.GetSignatureLayersInfo(repo, reference, digest.String(), signatureType, body,
|
||||||
SignatureType: signatureType,
|
imgStore, log)
|
||||||
SignatureDigest: digest.String(),
|
if errGetLayers != nil {
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("repodb: error while putting repo meta")
|
|
||||||
metadataSuccessfullySet = false
|
metadataSuccessfullySet = false
|
||||||
|
err = errGetLayers
|
||||||
|
} else {
|
||||||
|
err = repoDB.AddManifestSignature(repo, signedManifestDigest, repodb.SignatureMetadata{
|
||||||
|
SignatureType: signatureType,
|
||||||
|
SignatureDigest: digest.String(),
|
||||||
|
LayersInfo: layersInfo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("repodb: error while putting repo meta")
|
||||||
|
metadataSuccessfullySet = false
|
||||||
|
} else {
|
||||||
|
err = repoDB.UpdateSignaturesValidity(repo, signedManifestDigest)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Str("repository", repo).Str("reference", reference).Str("digest",
|
||||||
|
signedManifestDigest.String()).Msg("repodb: failed verify signatures validity for signed image")
|
||||||
|
metadataSuccessfullySet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err := repodb.SetImageMetaFromInput(repo, reference, mediaType, digest, body,
|
err := repodb.SetImageMetaFromInput(repo, reference, mediaType, digest, body,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
notreg "github.com/notaryproject/notation-go/registry"
|
||||||
godigest "github.com/opencontainers/go-digest"
|
godigest "github.com/opencontainers/go-digest"
|
||||||
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
@ -92,6 +93,82 @@ func TestOnUpdateManifest(t *testing.T) {
|
||||||
|
|
||||||
func TestUpdateErrors(t *testing.T) {
|
func TestUpdateErrors(t *testing.T) {
|
||||||
Convey("Update operations", t, func() {
|
Convey("Update operations", t, func() {
|
||||||
|
Convey("On UpdateManifest", func() {
|
||||||
|
imageStore := mocks.MockedImageStore{}
|
||||||
|
storeController := storage.StoreController{DefaultStore: &imageStore}
|
||||||
|
repoDB := mocks.RepoDBMock{}
|
||||||
|
log := log.NewLogger("debug", "")
|
||||||
|
|
||||||
|
Convey("CheckIsImageSignature errors", func() {
|
||||||
|
badManifestBlob := []byte("bad")
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return []byte{}, "", "", zerr.ErrManifestNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
imageStore.DeleteImageManifestFn = func(repo, reference string, detectCollision bool) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := meta.OnUpdateManifest("repo", "tag1", "digest", "media", badManifestBlob,
|
||||||
|
storeController, repoDB, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("GetSignatureLayersInfo errors", func() {
|
||||||
|
// get notation signature layers info
|
||||||
|
badNotationManifestContent := ispec.Manifest{
|
||||||
|
Subject: &ispec.Descriptor{
|
||||||
|
Digest: "123",
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{MediaType: notreg.ArtifactTypeNotation},
|
||||||
|
}
|
||||||
|
|
||||||
|
badNotationManifestBlob, err := json.Marshal(badNotationManifestContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return badNotationManifestBlob, "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = meta.OnUpdateManifest("repo", "tag1", "", "digest", badNotationManifestBlob,
|
||||||
|
storeController, repoDB, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("UpdateSignaturesValidity", func() {
|
||||||
|
notationManifestContent := ispec.Manifest{
|
||||||
|
Subject: &ispec.Descriptor{
|
||||||
|
Digest: "123",
|
||||||
|
},
|
||||||
|
Config: ispec.Descriptor{MediaType: notreg.ArtifactTypeNotation},
|
||||||
|
Layers: []ispec.Descriptor{{
|
||||||
|
MediaType: ispec.MediaTypeImageLayer,
|
||||||
|
Digest: godigest.FromString("blob digest"),
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
notationManifestBlob, err := json.Marshal(notationManifestContent)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
imageStore.GetImageManifestFn = func(repo, reference string) ([]byte, godigest.Digest, string, error) {
|
||||||
|
return notationManifestBlob, "", "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
imageStore.GetBlobContentFn = func(repo string, digest godigest.Digest) ([]byte, error) {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repoDB.UpdateSignaturesValidityFn = func(repo string, manifestDigest godigest.Digest) error {
|
||||||
|
return ErrTestError
|
||||||
|
}
|
||||||
|
|
||||||
|
err = meta.OnUpdateManifest("repo", "tag1", "", "digest", notationManifestBlob,
|
||||||
|
storeController, repoDB, log)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Convey("On DeleteManifest", func() {
|
Convey("On DeleteManifest", func() {
|
||||||
imageStore := mocks.MockedImageStore{}
|
imageStore := mocks.MockedImageStore{}
|
||||||
storeController := storage.StoreController{DefaultStore: &imageStore}
|
storeController := storage.StoreController{DefaultStore: &imageStore}
|
||||||
|
|
|
@ -55,6 +55,8 @@ type RepoDBMock struct {
|
||||||
|
|
||||||
IncrementImageDownloadsFn func(repo string, reference string) error
|
IncrementImageDownloadsFn func(repo string, reference string) error
|
||||||
|
|
||||||
|
UpdateSignaturesValidityFn func(repo string, manifestDigest godigest.Digest) error
|
||||||
|
|
||||||
AddManifestSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
|
AddManifestSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
|
||||||
|
|
||||||
DeleteSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
|
DeleteSignatureFn func(repo string, signedManifestDigest godigest.Digest, sm repodb.SignatureMetadata) error
|
||||||
|
@ -219,6 +221,14 @@ func (sdm RepoDBMock) IncrementImageDownloads(repo string, reference string) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sdm RepoDBMock) UpdateSignaturesValidity(repo string, manifestDigest godigest.Digest) error {
|
||||||
|
if sdm.UpdateSignaturesValidityFn != nil {
|
||||||
|
return sdm.UpdateSignaturesValidityFn(repo, manifestDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (sdm RepoDBMock) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
func (sdm RepoDBMock) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
||||||
sm repodb.SignatureMetadata,
|
sm repodb.SignatureMetadata,
|
||||||
) error {
|
) error {
|
||||||
|
|
Loading…
Reference in a new issue