Skip to content

Commit 87a2ba4

Browse files
committed
feat: allow disabling CVE independently from search
fixes issue #4096 Useful for airgapped environments and deployments which don't care about CVEs. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com>
1 parent a2d738c commit 87a2ba4

7 files changed

Lines changed: 256 additions & 38 deletions

File tree

examples/config-ui-no-cve.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"distSpecVersion": "1.1.1",
3+
"storage": {
4+
"rootDirectory": "/tmp/zot"
5+
},
6+
"http": {
7+
"address": "0.0.0.0",
8+
"port": "8080"
9+
},
10+
"log": {
11+
"level": "debug"
12+
},
13+
"extensions": {
14+
"search": {
15+
"cve": {
16+
"enable": false,
17+
"updateInterval": "2h"
18+
}
19+
},
20+
"ui": {
21+
"enable": true
22+
}
23+
}
24+
}

pkg/cli/server/extensions_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,41 @@ func TestVerifyExtensionsConfig(t *testing.T) {
100100
So(cli.NewServerRootCmd().Execute(), ShouldNotBeNil)
101101
})
102102

103+
Convey("Test verify CVE can be explicitly disabled for remote storage", t, func(c C) {
104+
content := fmt.Sprintf(`{
105+
"storage":{
106+
"rootDirectory":"%s",
107+
"dedupe":true,
108+
"remoteCache":false,
109+
"storageDriver":{
110+
"name":"s3",
111+
"rootdirectory":"/zot",
112+
"region":"us-east-2",
113+
"bucket":"zot-storage",
114+
"secure":true,
115+
"skipverify":false
116+
}
117+
},
118+
"http":{
119+
"address":"127.0.0.1",
120+
"port":"8080"
121+
},
122+
"extensions":{
123+
"search": {
124+
"enable": true,
125+
"cve": {
126+
"enable": false
127+
}
128+
}
129+
}
130+
}`, t.TempDir())
131+
132+
tmpfile := MakeTempFileWithContent(t, "zot-test.json", content)
133+
os.Args = []string{"cli_test", "verify", tmpfile}
134+
135+
So(cli.NewServerRootCmd().Execute(), ShouldBeNil)
136+
})
137+
103138
Convey("Test verify w/ sync and w/o filesystem storage", t, func(c C) {
104139
content := fmt.Sprintf(`{"storage":{"rootDirectory":"%s", "storageDriver": {"name": "s3"}},
105140
"http":{"address":"127.0.0.1","port":"8080","realm":"zot",
@@ -1099,6 +1134,41 @@ func TestServeSearchEnabledNoCVE(t *testing.T) {
10991134
So(found, ShouldBeTrue)
11001135
So(err, ShouldBeNil)
11011136
})
1137+
1138+
Convey("search explicitly enabled, and CVE explicitly disabled", t, func(c C) {
1139+
content := `{
1140+
"storage": {
1141+
"rootDirectory": "%s"
1142+
},
1143+
"http": {
1144+
"address": "127.0.0.1",
1145+
"port": "%s"
1146+
},
1147+
"log": {
1148+
"level": "debug",
1149+
"output": "%s"
1150+
},
1151+
"extensions": {
1152+
"search": {
1153+
"enable": true,
1154+
"cve": {
1155+
"enable": false
1156+
}
1157+
}
1158+
}
1159+
}`
1160+
1161+
logPath, _, err := runCLIWithConfig(t, content)
1162+
So(err, ShouldBeNil)
1163+
1164+
found, err := ReadLogFileAndSearchString(logPath, `"CVE":{"Enable":false`, readLogFileTimeout)
1165+
So(found, ShouldBeTrue)
1166+
So(err, ShouldBeNil)
1167+
1168+
found, err = ReadLogFileAndSearchString(logPath, "updating cve-db", readLogFileTimeout)
1169+
So(found, ShouldBeFalse)
1170+
So(err, ShouldBeNil)
1171+
})
11021172
}
11031173

11041174
func TestServeSearchDisabled(t *testing.T) {

pkg/cli/server/root.go

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -874,41 +874,43 @@ func applyDefaultValues(config *config.Config, viperInstance *viper.Viper, logge
874874
}
875875

876876
if *config.Extensions.Search.Enable && config.Extensions.Search.CVE != nil {
877-
defaultUpdateInterval, _ := time.ParseDuration("2h")
877+
if config.Extensions.Search.CVE.Enable == nil || *config.Extensions.Search.CVE.Enable {
878+
defaultUpdateInterval, _ := time.ParseDuration("2h")
878879

879-
if config.Extensions.Search.CVE.UpdateInterval < defaultUpdateInterval {
880-
config.Extensions.Search.CVE.UpdateInterval = defaultUpdateInterval
880+
if config.Extensions.Search.CVE.UpdateInterval < defaultUpdateInterval {
881+
config.Extensions.Search.CVE.UpdateInterval = defaultUpdateInterval
881882

882-
logger.Warn().Msg("cve update interval set to too-short interval < 2h, " +
883-
"changing update duration to 2 hours and continuing.")
884-
}
883+
logger.Warn().Msg("cve update interval set to too-short interval < 2h, " +
884+
"changing update duration to 2 hours and continuing.")
885+
}
885886

886-
if config.Extensions.Search.CVE.Trivy == nil {
887-
config.Extensions.Search.CVE.Trivy = &extconf.TrivyConfig{}
888-
}
887+
if config.Extensions.Search.CVE.Trivy == nil {
888+
config.Extensions.Search.CVE.Trivy = &extconf.TrivyConfig{}
889+
}
889890

890-
if config.Extensions.Search.CVE.Trivy.DBRepository == "" {
891-
defaultDBDownloadURL := "ghcr.io/aquasecurity/trivy-db"
892-
logger.Info().Str("url", defaultDBDownloadURL).Str("component", "config").
893-
Msg("using default trivy-db download URL.")
891+
if config.Extensions.Search.CVE.Trivy.DBRepository == "" {
892+
defaultDBDownloadURL := "ghcr.io/aquasecurity/trivy-db"
893+
logger.Info().Str("url", defaultDBDownloadURL).Str("component", "config").
894+
Msg("using default trivy-db download URL.")
894895

895-
config.Extensions.Search.CVE.Trivy.DBRepository = defaultDBDownloadURL
896-
}
896+
config.Extensions.Search.CVE.Trivy.DBRepository = defaultDBDownloadURL
897+
}
897898

898-
if config.Extensions.Search.CVE.Trivy.JavaDBRepository == "" {
899-
defaultJavaDBDownloadURL := "ghcr.io/aquasecurity/trivy-java-db"
900-
logger.Info().Str("url", defaultJavaDBDownloadURL).Str("component", "config").
901-
Msg("using default trivy-java-db download URL.")
899+
if config.Extensions.Search.CVE.Trivy.JavaDBRepository == "" {
900+
defaultJavaDBDownloadURL := "ghcr.io/aquasecurity/trivy-java-db"
901+
logger.Info().Str("url", defaultJavaDBDownloadURL).Str("component", "config").
902+
Msg("using default trivy-java-db download URL.")
902903

903-
config.Extensions.Search.CVE.Trivy.JavaDBRepository = defaultJavaDBDownloadURL
904-
}
904+
config.Extensions.Search.CVE.Trivy.JavaDBRepository = defaultJavaDBDownloadURL
905+
}
905906

906-
if len(config.Extensions.Search.CVE.Trivy.VulnSeveritySources) == 0 {
907-
defaultVulnSeveritySources := []string{"auto"}
908-
logger.Info().Strs("vulnSeveritySources", defaultVulnSeveritySources).Str("component", "config").
909-
Msg("using default trivy vulnerability severity sources.")
907+
if len(config.Extensions.Search.CVE.Trivy.VulnSeveritySources) == 0 {
908+
defaultVulnSeveritySources := []string{"auto"}
909+
logger.Info().Strs("vulnSeveritySources", defaultVulnSeveritySources).Str("component", "config").
910+
Msg("using default trivy vulnerability severity sources.")
910911

911-
config.Extensions.Search.CVE.Trivy.VulnSeveritySources = defaultVulnSeveritySources
912+
config.Extensions.Search.CVE.Trivy.VulnSeveritySources = defaultVulnSeveritySources
913+
}
912914
}
913915
}
914916
}

pkg/extensions/config/config.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ type SearchConfig struct {
5353
}
5454

5555
type CVEConfig struct {
56+
// Enable controls whether CVE scanning is active for search.
57+
// If omitted, this field defaults to enabled when the CVE config block is present and search is enabled.
58+
Enable *bool `mapstructure:"enable,omitempty"`
5659
UpdateInterval time.Duration // should be 2 hours or more, if not specified default be kept as 2 hours
5760
Trivy *TrivyConfig
5861
}
@@ -109,8 +112,15 @@ func (e *ExtensionConfig) IsCveScanningEnabled() bool {
109112
return false
110113
}
111114

112-
return e.Search != nil && e.Search.Enable != nil && *e.Search.Enable &&
113-
e.Search.CVE != nil && e.Search.CVE.Trivy != nil
115+
if e.Search == nil || e.Search.Enable == nil || !*e.Search.Enable || e.Search.CVE == nil {
116+
return false
117+
}
118+
119+
if e.Search.CVE.Enable != nil && !*e.Search.CVE.Enable {
120+
return false
121+
}
122+
123+
return e.Search.CVE.Trivy != nil
114124
}
115125

116126
// IsEventRecorderEnabled checks if event recording is enabled in this extensions config.

pkg/extensions/config/config_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ func buildSearchConfigWithCVE(enabled bool) *config.ExtensionConfig {
4444
return ext
4545
}
4646

47+
func buildSearchConfigWithCVEDisabled(enabled bool) *config.ExtensionConfig {
48+
disabled := false
49+
ext := &config.ExtensionConfig{}
50+
ext.Search = &config.SearchConfig{
51+
BaseConfig: config.BaseConfig{
52+
Enable: &enabled,
53+
},
54+
CVE: &config.CVEConfig{
55+
Enable: &disabled,
56+
Trivy: &config.TrivyConfig{},
57+
},
58+
}
59+
60+
return ext
61+
}
62+
4763
func buildEventsConfig(enabled bool) *config.ExtensionConfig {
4864
ext := &config.ExtensionConfig{}
4965
ext.Events = &events.Config{
@@ -302,6 +318,12 @@ func TestExtensionConfig(t *testing.T) {
302318
testMethodWithNilEnable("Search", (*config.ExtensionConfig).IsCveScanningEnabled)
303319
testMethodWithDisabledEnable("Search", (*config.ExtensionConfig).IsCveScanningEnabled, buildSearchConfig)
304320
testMethodWithEnabledEnable("Search", (*config.ExtensionConfig).IsCveScanningEnabled, buildSearchConfigWithCVE)
321+
322+
Convey("Test with search enabled but cve explicitly disabled", func() {
323+
enabled := true
324+
extensionConfig := buildSearchConfigWithCVEDisabled(enabled)
325+
So(extensionConfig.IsCveScanningEnabled(), ShouldBeFalse)
326+
})
305327
})
306328

307329
Convey("Test IsEventRecorderEnabled()", func() {

pkg/extensions/search/resolver_test.go

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -695,8 +695,8 @@ func TestGetReferrers(t *testing.T) {
695695
})
696696
}
697697

698-
func TestQueryResolverErrors(t *testing.T) {
699-
Convey("Errors", t, func() {
698+
func TestQueryResolverErrorsAndEmptyResults(t *testing.T) {
699+
Convey("Errors and empty results", t, func() {
700700
log := log.NewTestLogger()
701701
ctx := graphql.WithResponseContext(context.Background(), graphql.DefaultErrorPresenter,
702702
graphql.DefaultRecover)
@@ -735,7 +735,10 @@ func TestQueryResolverErrors(t *testing.T) {
735735
So(err, ShouldNotBeNil)
736736
})
737737

738-
Convey("CVEDiffListForImages nill cveinfo", func() {
738+
Convey("CVEDiffListForImages nil cveinfo returns empty result", func() {
739+
minuend := gql_generated.ImageInput{Repo: "repo1", Tag: "1.0.0", Digest: ref("sha256:minuend")}
740+
subtrahend := gql_generated.ImageInput{Repo: "repo2", Tag: "2.0.0", Digest: ref("sha256:subtrahend")}
741+
739742
resolverConfig := NewResolver(
740743
log,
741744
storage.StoreController{
@@ -752,9 +755,75 @@ func TestQueryResolverErrors(t *testing.T) {
752755

753756
qr := queryResolver{resolverConfig}
754757

755-
_, err := qr.CVEDiffListForImages(ctx, gql_generated.ImageInput{}, gql_generated.ImageInput{},
758+
result, err := qr.CVEDiffListForImages(ctx, minuend, subtrahend,
756759
&gql_generated.PageInput{}, nil, nil)
757-
So(err, ShouldNotBeNil)
760+
So(err, ShouldBeNil)
761+
So(result, ShouldNotBeNil)
762+
So(result.Minuend, ShouldNotBeNil)
763+
So(result.Minuend.Repo, ShouldEqual, minuend.Repo)
764+
So(result.Minuend.Tag, ShouldEqual, minuend.Tag)
765+
So(result.Minuend.Digest, ShouldEqual, minuend.Digest)
766+
So(result.Subtrahend, ShouldNotBeNil)
767+
So(result.Subtrahend.Repo, ShouldEqual, subtrahend.Repo)
768+
So(result.Subtrahend.Tag, ShouldEqual, subtrahend.Tag)
769+
So(result.Subtrahend.Digest, ShouldEqual, subtrahend.Digest)
770+
So(result.Page, ShouldNotBeNil)
771+
So(result.CVEList, ShouldNotBeNil)
772+
So(len(result.CVEList), ShouldEqual, 0)
773+
})
774+
775+
Convey("CVEListForImage nil cveinfo returns empty result", func() {
776+
resolverConfig := NewResolver(
777+
log,
778+
storage.StoreController{DefaultStore: mocks.MockedImageStore{}},
779+
mocks.MetaDBMock{},
780+
nil,
781+
)
782+
783+
qr := queryResolver{resolverConfig}
784+
785+
result, err := qr.CVEListForImage(ctx, "repo1:1.0.0", &gql_generated.PageInput{}, nil, nil, nil)
786+
So(err, ShouldBeNil)
787+
So(result, ShouldNotBeNil)
788+
So(result.Page, ShouldNotBeNil)
789+
So(result.CVEList, ShouldNotBeNil)
790+
So(len(result.CVEList), ShouldEqual, 0)
791+
})
792+
793+
Convey("ImageListForCve nil cveinfo returns empty result", func() {
794+
resolverConfig := NewResolver(
795+
log,
796+
storage.StoreController{DefaultStore: mocks.MockedImageStore{}},
797+
mocks.MetaDBMock{},
798+
nil,
799+
)
800+
801+
qr := queryResolver{resolverConfig}
802+
803+
result, err := qr.ImageListForCve(ctx, "CVE-123", &gql_generated.Filter{}, &gql_generated.PageInput{})
804+
So(err, ShouldBeNil)
805+
So(result, ShouldNotBeNil)
806+
So(result.Page, ShouldNotBeNil)
807+
So(result.Results, ShouldNotBeNil)
808+
So(len(result.Results), ShouldEqual, 0)
809+
})
810+
811+
Convey("ImageListWithCVEFixed nil cveinfo returns empty result", func() {
812+
resolverConfig := NewResolver(
813+
log,
814+
storage.StoreController{DefaultStore: mocks.MockedImageStore{}},
815+
mocks.MetaDBMock{},
816+
nil,
817+
)
818+
819+
qr := queryResolver{resolverConfig}
820+
821+
result, err := qr.ImageListWithCVEFixed(ctx, "CVE-123", "repo1", &gql_generated.Filter{}, &gql_generated.PageInput{})
822+
So(err, ShouldBeNil)
823+
So(result, ShouldNotBeNil)
824+
So(result.Page, ShouldNotBeNil)
825+
So(result.Results, ShouldNotBeNil)
826+
So(len(result.Results), ShouldEqual, 0)
758827
})
759828

760829
Convey("CVEDiffListForImages error", func() {

0 commit comments

Comments
 (0)