0
Fork 0
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:
Andreea Lupu 2023-05-24 19:46:16 +03:00 committed by GitHub
parent 6e6ffe800c
commit 970997f3a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 2053 additions and 31 deletions

View file

@ -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
View file

@ -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

View file

@ -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")
})
}

View file

@ -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
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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)
}) })
} }

View file

@ -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

View file

@ -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)
})
})
}) })
} }

View file

@ -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

View file

@ -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 {

View file

@ -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
} }

View file

@ -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()

View file

@ -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) {

View file

@ -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)
})
}

View 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
}

View 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
}

View 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
}
}

View 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, &notationSig)
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)
})
}

View file

@ -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,

View file

@ -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}

View file

@ -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 {