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
+1
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")
)
+94
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
}
+7
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
}
+331 -1
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
@@ -70,12 +75,19 @@ type ComplexityRoot struct {
Query struct {
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
+13
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"`
}
+83
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
}
+12
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
}