0
Fork 0
mirror of https://github.com/project-zot/zot.git synced 2025-03-11 02:17:43 -05:00

Added support for searching fixed tag given cve and an image

This commit is contained in:
Shivam Mishra 2020-08-18 23:53:04 -07:00
parent 72ae02ca4b
commit ed254159a0
7 changed files with 543 additions and 3 deletions

View file

@ -35,4 +35,5 @@ var (
ErrNoURLProvided = errors.New("cli: no URL provided in argument or via config. see 'zot config -h'")
ErrIllegalConfigKey = errors.New("cli: given config key is not allowed")
ErrScanNotSupported = errors.New("search: scanning of image media type not supported")
ErrFixedTagNotFound = errors.New("search: no fixed tag found")
)

View file

@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path"
"sort"
"strings"
"github.com/anuvu/zot/errors"
@ -130,3 +131,96 @@ func getImageDir(imageName string) string {
return imageDir
}
// GetImageTagsWithTimestamp returns a list of image tags with timestamp available in the specified repository.
func (cveinfo CveInfo) GetImageTagsWithTimestamp(rootDir string, repo string) ([]TagInfo, error) {
dir := path.Join(rootDir, repo)
if !dirExists(dir) {
return nil, errors.ErrRepoNotFound
}
var digest godigest.Digest
buf, err := ioutil.ReadFile(path.Join(dir, "index.json"))
if err != nil {
cveinfo.Log.Error().Err(err).Str("dir", dir).Msg("failed to read index.json")
return nil, errors.ErrRepoNotFound
}
var index ispec.Index
if err := json.Unmarshal(buf, &index); err != nil {
cveinfo.Log.Error().Err(err).Str("dir", dir).Msg("invalid JSON")
return nil, errors.ErrRepoNotFound
}
tagsInfo := make([]TagInfo, 0)
var blobIndex ispec.Manifest
var layerIndex ispec.Image
for _, manifest := range index.Manifests {
digest = manifest.Digest
v, ok := manifest.Annotations[ispec.AnnotationRefName]
blobBuf, err := ioutil.ReadFile(path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded()))
if err != nil {
cveinfo.Log.Error().Err(err).Msg("Unable to open Image Metadata file")
return nil, err
}
if err := json.Unmarshal(blobBuf, &blobIndex); err != nil {
cveinfo.Log.Error().Err(err).Msg("Unable to marshal blob index")
return nil, err
}
digest = blobIndex.Config.Digest
blobBuf, err = ioutil.ReadFile(path.Join(dir, "blobs", digest.Algorithm().String(), digest.Encoded()))
if err != nil {
cveinfo.Log.Error().Err(err).Msg("Unable to open Image Layers file")
return nil, err
}
if err := json.Unmarshal(blobBuf, &layerIndex); err != nil {
cveinfo.Log.Error().Err(err).Msg("Unable to marshal blob index")
return nil, err
}
timeStamp := *layerIndex.History[0].Created
if ok {
tagsInfo = append(tagsInfo, TagInfo{Name: v, Timestamp: timeStamp})
}
}
return tagsInfo, nil
}
func GetFixedTags(allTags []TagInfo, infectedTags []TagInfo) []TagInfo {
sort.Slice(allTags, func(i, j int) bool {
return allTags[i].Timestamp.Before(allTags[j].Timestamp)
})
latestInfected := TagInfo{}
for _, tag := range infectedTags {
if !tag.Timestamp.Before(latestInfected.Timestamp) {
latestInfected = tag
}
}
var fixedTags []TagInfo
for _, tag := range allTags {
if tag.Timestamp.After(latestInfected.Timestamp) {
fixedTags = append(fixedTags, tag)
}
}
return fixedTags
}

View file

@ -2,6 +2,8 @@
package cveinfo
import (
"time"
"github.com/anuvu/zot/pkg/log"
config "github.com/aquasecurity/trivy/integration/config"
)
@ -11,3 +13,8 @@ type CveInfo struct {
Log log.Logger
CveTrivyConfig *config.Config
}
type TagInfo struct {
Name string
Timestamp time.Time
}

View file

@ -8,6 +8,7 @@ import (
"errors"
"strconv"
"sync"
"time"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/introspection"
@ -61,6 +62,10 @@ type ComplexityRoot struct {
Tags func(childComplexity int) int
}
ImgResultForFixedCve struct {
Tags func(childComplexity int) int
}
PackageInfo struct {
FixedVersion func(childComplexity int) int
InstalledVersion func(childComplexity int) int
@ -68,14 +73,21 @@ type ComplexityRoot struct {
}
Query struct {
CVEListForImage func(childComplexity int, image string) int
ImageListForCve func(childComplexity int, id string) int
CVEListForImage func(childComplexity int, image string) int
ImageListForCve func(childComplexity int, id string) int
ImageListWithCVEFixed func(childComplexity int, id string, image string) int
}
TagInfo struct {
Name func(childComplexity int) int
Timestamp func(childComplexity int) int
}
}
type QueryResolver interface {
CVEListForImage(ctx context.Context, image string) (*CVEResultForImage, error)
ImageListForCve(ctx context.Context, id string) ([]*ImgResultForCve, error)
ImageListWithCVEFixed(ctx context.Context, id string, image string) (*ImgResultForFixedCve, error)
}
type executableSchema struct {
@ -156,6 +168,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ImgResultForCve.Tags(childComplexity), true
case "ImgResultForFixedCVE.Tags":
if e.complexity.ImgResultForFixedCve.Tags == nil {
break
}
return e.complexity.ImgResultForFixedCve.Tags(childComplexity), true
case "PackageInfo.FixedVersion":
if e.complexity.PackageInfo.FixedVersion == nil {
break
@ -201,6 +220,32 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.ImageListForCve(childComplexity, args["id"].(string)), true
case "Query.ImageListWithCVEFixed":
if e.complexity.Query.ImageListWithCVEFixed == nil {
break
}
args, err := ec.field_Query_ImageListWithCVEFixed_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.ImageListWithCVEFixed(childComplexity, args["id"].(string), args["image"].(string)), true
case "TagInfo.Name":
if e.complexity.TagInfo.Name == nil {
break
}
return e.complexity.TagInfo.Name(childComplexity), true
case "TagInfo.Timestamp":
if e.complexity.TagInfo.Timestamp == nil {
break
}
return e.complexity.TagInfo.Timestamp(childComplexity), true
}
return 0, false
}
@ -251,7 +296,9 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
}
var sources = []*ast.Source{
{Name: "schema.graphql", Input: `type CVEResultForImage {
{Name: "schema.graphql", Input: `scalar Time
type CVEResultForImage {
Tag: String
CVEList: [CVE]
}
@ -275,9 +322,19 @@ type ImgResultForCVE {
Tags: [String]
}
type ImgResultForFixedCVE {
Tags: [TagInfo]
}
type TagInfo {
Name: String
Timestamp: Time
}
type Query {
CVEListForImage(image: String!) :CVEResultForImage
ImageListForCVE(id: String!) :[ImgResultForCVE]
ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE
}`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@ -361,6 +418,30 @@ func (ec *executionContext) field_Query_ImageListForCVE_args(ctx context.Context
return args, nil
}
func (ec *executionContext) field_Query_ImageListWithCVEFixed_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 string
if tmp, ok := rawArgs["id"]; ok {
ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("id"))
arg0, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["id"] = arg0
var arg1 string
if tmp, ok := rawArgs["image"]; ok {
ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField("image"))
arg1, err = ec.unmarshalNString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["image"] = arg1
return args, nil
}
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -708,6 +789,34 @@ func (ec *executionContext) _ImgResultForCVE_Tags(ctx context.Context, field gra
return ec.marshalOString2ᚕᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _ImgResultForFixedCVE_Tags(ctx context.Context, field graphql.CollectedField, obj *ImgResultForFixedCve) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ImgResultForFixedCVE",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Tags, nil
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.([]*TagInfo)
fc.Result = res
return ec.marshalOTagInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx, field.Selections, res)
}
func (ec *executionContext) _PackageInfo_Name(ctx context.Context, field graphql.CollectedField, obj *PackageInfo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -862,6 +971,41 @@ func (ec *executionContext) _Query_ImageListForCVE(ctx context.Context, field gr
return ec.marshalOImgResultForCVE2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForCve(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_ImageListWithCVEFixed(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_ImageListWithCVEFixed_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp := ec._fieldMiddleware(ctx, nil, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().ImageListWithCVEFixed(rctx, args["id"].(string), args["image"].(string))
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*ImgResultForFixedCve)
fc.Result = res
return ec.marshalOImgResultForFixedCVE2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForFixedCve(ctx, field.Selections, res)
}
func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -925,6 +1069,62 @@ func (ec *executionContext) _Query___schema(ctx context.Context, field graphql.C
return ec.marshalO__Schema2ᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐSchema(ctx, field.Selections, res)
}
func (ec *executionContext) _TagInfo_Name(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TagInfo",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Name, nil
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _TagInfo_Timestamp(ctx context.Context, field graphql.CollectedField, obj *TagInfo) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "TagInfo",
Field: field,
Args: nil,
IsMethod: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp := ec._fieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Timestamp, nil
})
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*time.Time)
fc.Result = res
return ec.marshalOTime2ᚖtimeᚐTime(ctx, field.Selections, res)
}
func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -1976,6 +2176,30 @@ func (ec *executionContext) _ImgResultForCVE(ctx context.Context, sel ast.Select
return out
}
var imgResultForFixedCVEImplementors = []string{"ImgResultForFixedCVE"}
func (ec *executionContext) _ImgResultForFixedCVE(ctx context.Context, sel ast.SelectionSet, obj *ImgResultForFixedCve) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, imgResultForFixedCVEImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ImgResultForFixedCVE")
case "Tags":
out.Values[i] = ec._ImgResultForFixedCVE_Tags(ctx, field, obj)
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var packageInfoImplementors = []string{"PackageInfo"}
func (ec *executionContext) _PackageInfo(ctx context.Context, sel ast.SelectionSet, obj *PackageInfo) graphql.Marshaler {
@ -2041,6 +2265,17 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
res = ec._Query_ImageListForCVE(ctx, field)
return res
})
case "ImageListWithCVEFixed":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_ImageListWithCVEFixed(ctx, field)
return res
})
case "__type":
out.Values[i] = ec._Query___type(ctx, field)
case "__schema":
@ -2056,6 +2291,32 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return out
}
var tagInfoImplementors = []string{"TagInfo"}
func (ec *executionContext) _TagInfo(ctx context.Context, sel ast.SelectionSet, obj *TagInfo) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, tagInfoImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("TagInfo")
case "Name":
out.Values[i] = ec._TagInfo_Name(ctx, field, obj)
case "Timestamp":
out.Values[i] = ec._TagInfo_Timestamp(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"}
func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler {
@ -2685,6 +2946,13 @@ func (ec *executionContext) marshalOImgResultForCVE2ᚖgithubᚗcomᚋanuvuᚋzo
return ec._ImgResultForCVE(ctx, sel, v)
}
func (ec *executionContext) marshalOImgResultForFixedCVE2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐImgResultForFixedCve(ctx context.Context, sel ast.SelectionSet, v *ImgResultForFixedCve) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._ImgResultForFixedCVE(ctx, sel, v)
}
func (ec *executionContext) marshalOPackageInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐPackageInfo(ctx context.Context, sel ast.SelectionSet, v []*PackageInfo) graphql.Marshaler {
if v == nil {
return graphql.Null
@ -2792,6 +3060,68 @@ func (ec *executionContext) marshalOString2ᚖstring(ctx context.Context, sel as
return graphql.MarshalString(*v)
}
func (ec *executionContext) marshalOTagInfo2ᚕᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v []*TagInfo) 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.marshalOTagInfo2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx, sel, v[i])
}
if isLen1 {
f(i)
} else {
go f(i)
}
}
wg.Wait()
return ret
}
func (ec *executionContext) marshalOTagInfo2ᚖgithubᚗcomᚋanuvuᚋzotᚋpkgᚋextensionsᚋsearchᚐTagInfo(ctx context.Context, sel ast.SelectionSet, v *TagInfo) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return ec._TagInfo(ctx, sel, v)
}
func (ec *executionContext) unmarshalOTime2ᚖtimeᚐTime(ctx context.Context, v interface{}) (*time.Time, error) {
if v == nil {
return nil, nil
}
res, err := graphql.UnmarshalTime(v)
return &res, graphql.WrapErrorWithInputPath(ctx, err)
}
func (ec *executionContext) marshalOTime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler {
if v == nil {
return graphql.Null
}
return graphql.MarshalTime(*v)
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil {
return graphql.Null

View file

@ -2,6 +2,10 @@
package search
import (
"time"
)
type Cve struct {
ID *string `json:"Id"`
Title *string `json:"Title"`
@ -20,8 +24,17 @@ type ImgResultForCve struct {
Tags []*string `json:"Tags"`
}
type ImgResultForFixedCve struct {
Tags []*TagInfo `json:"Tags"`
}
type PackageInfo struct {
Name *string `json:"Name"`
InstalledVersion *string `json:"InstalledVersion"`
FixedVersion *string `json:"FixedVersion"`
}
type TagInfo struct {
Name *string `json:"Name"`
Timestamp *time.Time `json:"Timestamp"`
}

View file

@ -218,3 +218,86 @@ func (r *queryResolver) ImageListForCve(ctx context.Context, id string) ([]*ImgR
return cveResult, nil
}
func (r *queryResolver) ImageListWithCVEFixed(ctx context.Context, id string, image string) (*ImgResultForFixedCve, error) { // nolint: lll
imgResultForFixedCVE := &ImgResultForFixedCve{}
r.cveInfo.Log.Info().Str("Extracting list of tags available in image", image).Msg("")
isValidImage, err := r.cveInfo.IsValidImageFormat(path.Join(r.dir, image))
if !isValidImage {
r.cveInfo.Log.Debug().Msg("Image media type not supported for scanning")
return imgResultForFixedCVE, errors.ErrScanNotSupported
}
if err != nil {
return imgResultForFixedCVE, err
}
tagsInfo, err := r.cveInfo.GetImageTagsWithTimestamp(r.dir, image)
if err != nil {
r.cveInfo.Log.Error().Err(err).Msg("Error while readling image media type")
return imgResultForFixedCVE, err
}
infectedTags := make([]cveinfo.TagInfo, 0)
var hasCVE bool
for _, tag := range tagsInfo {
r.cveInfo.CveTrivyConfig.TrivyConfig.Input = path.Join(r.dir, image+":"+tag.Name)
r.cveInfo.Log.Info().Str("Scanning image", path.Join(r.dir, image+":"+tag.Name)).Msg("")
results, err := cveinfo.ScanImage(r.cveInfo.CveTrivyConfig)
if err != nil {
r.cveInfo.Log.Error().Err(err).Str("Error scanning image", image+":"+tag.Name).Msg("")
continue
}
hasCVE = false
for _, result := range results {
for _, vulnerability := range result.Vulnerabilities {
if vulnerability.VulnerabilityID == id {
hasCVE = true
break
}
}
}
if hasCVE {
infectedTags = append(infectedTags, cveinfo.TagInfo{Name: tag.Name, Timestamp: tag.Timestamp})
}
}
if len(infectedTags) != 0 {
r.cveInfo.Log.Info().Msg("Comparing fixed tags timestamp")
fixedTags := cveinfo.GetFixedTags(tagsInfo, infectedTags)
finalTagList := make([]*TagInfo, 0)
for _, tag := range fixedTags {
copyTag := tag.Name
copyTimeStamp := tag.Timestamp
finalTagList = append(finalTagList, &TagInfo{Name: &copyTag, Timestamp: &copyTimeStamp})
}
imgResultForFixedCVE = &ImgResultForFixedCve{Tags: finalTagList}
return imgResultForFixedCVE, nil
}
r.cveInfo.Log.Info().Msg("Input image does not contain any tag that does not have given cve")
imgResultForFixedCVE = &ImgResultForFixedCve{}
return imgResultForFixedCVE, errors.ErrFixedTagNotFound
}

View file

@ -1,3 +1,5 @@
scalar Time
type CVEResultForImage {
Tag: String
CVEList: [CVE]
@ -22,7 +24,17 @@ type ImgResultForCVE {
Tags: [String]
}
type ImgResultForFixedCVE {
Tags: [TagInfo]
}
type TagInfo {
Name: String
Timestamp: Time
}
type Query {
CVEListForImage(image: String!) :CVEResultForImage
ImageListForCVE(id: String!) :[ImgResultForCVE]
ImageListWithCVEFixed(id: String!, image: String!) :ImgResultForFixedCVE
}