diff --git a/central/graphql/resolvers/vulnerabilities.go b/central/graphql/resolvers/vulnerabilities.go index 52fdea0686724..ae0499c8a2569 100644 --- a/central/graphql/resolvers/vulnerabilities.go +++ b/central/graphql/resolvers/vulnerabilities.go @@ -49,7 +49,7 @@ func init() { "suppressExpiry: Time", "activeState(query: String): ActiveState", "vulnerabilityState: String!", - "effectiveVulnerabilityRequest: VulnerabilityRequest!", + "effectiveVulnerabilityRequest: VulnerabilityRequest", }), schema.AddQuery("vulnerability(id: ID): EmbeddedVulnerability"), schema.AddQuery("vulnerabilities(query: String, scopeQuery: String, pagination: Pagination): [EmbeddedVulnerability!]!"), diff --git a/central/vulnerabilityrequest/cache/cache.go b/central/vulnerabilityrequest/cache/cache.go index 4228fe54e9d67..fc93886170536 100644 --- a/central/vulnerabilityrequest/cache/cache.go +++ b/central/vulnerabilityrequest/cache/cache.go @@ -10,6 +10,8 @@ type VulnReqCache interface { Remove(requestID string) bool RemoveMany(requestIDs ...string) bool GetVulnsWithState(registry, remote, tag string) map[string]storage.VulnerabilityState + // GetEffectiveVulnStateForImage returns the effective state of the vulnerabilities within the given image. + GetEffectiveVulnStateForImage(cves []string, registry, remote, tag string) map[string]storage.VulnerabilityState // GetEffectiveVulnState returns the effective state of the vulnerabilities within the given requests. GetEffectiveVulnState(cves []string, requestIDs ...string) map[string]storage.VulnerabilityState // GetEffectiveVulnReqID returns the vuln request in effect on given image+cve combination. diff --git a/central/vulnerabilityrequest/cache/cache_impl.go b/central/vulnerabilityrequest/cache/cache_impl.go index a7de8b8cb6935..0227c9d350693 100644 --- a/central/vulnerabilityrequest/cache/cache_impl.go +++ b/central/vulnerabilityrequest/cache/cache_impl.go @@ -125,6 +125,36 @@ func (c *vulnReqCacheImpl) GetEffectiveVulnState(cves []string, requestIDs ...st return ret } +func (c *vulnReqCacheImpl) GetEffectiveVulnStateForImage(cves []string, registry, remote, tag string) map[string]storage.VulnerabilityState { + if registry == "" || remote == "" || tag == "" { + return nil + } + ret := make(map[string]storage.VulnerabilityState) + for _, cve := range cves { + ret[cve] = storage.VulnerabilityState_OBSERVED + } + cvesSet := set.NewStringSet(cves...) + + c.lock.RLock() + defer c.lock.RUnlock() + + for _, scope := range []string{ + // Start with the smallest scope because req from the smallest scope takes precedence. + imageNameToScopeStr(registry, remote, tag), + imageNameToScopeStr(registry, remote, common.MatchAll), + common.MatchAll, + } { + reqMap := c.vulnReqByScope[scope] + if reqMap == nil { + continue + } + for _, req := range reqMap { + processSlimRequest(req, scope, cvesSet, ret) + } + } + return ret +} + func processSlimRequest(slimReq *slimRequest, scope string, cveSet set.StringSet, result map[string]storage.VulnerabilityState) { if slimReq == nil { return diff --git a/central/vulnerabilityrequest/cache/cache_test.go b/central/vulnerabilityrequest/cache/cache_test.go index 690a7f66c8c0b..94249037590c1 100644 --- a/central/vulnerabilityrequest/cache/cache_test.go +++ b/central/vulnerabilityrequest/cache/cache_test.go @@ -25,9 +25,13 @@ func TestCache(t *testing.T) { req3.Id = "req3" req4 := fixtures.GetImageScopeDeferralRequest(registry, remote, tag, vuln3) req4.Id = "req4" + req5 := fixtures.GetImageScopeDeferralRequest("reg-2", "fake", tag, vuln3) + req5.Id = "req5" + req6 := fixtures.GetImageScopeFPRequest(registry, remote, ".*", vuln3) + req5.Id = "req6" cache := New() - for _, req := range []*storage.VulnerabilityRequest{req1, req2, req3, req4} { + for _, req := range []*storage.VulnerabilityRequest{req1, req2, req3, req4, req5, req6} { assert.True(t, cache.Add(req)) } @@ -64,4 +68,48 @@ func TestCache(t *testing.T) { // Test that the vulnerability state is scoped to global. response = cache.GetEffectiveVulnState([]string{vuln1}) assert.Equal(t, storage.VulnerabilityState_FALSE_POSITIVE, response[vuln1]) + + // Remove one of the request for vuln3, and verify that for removed scope the cve state is taken from req6 which + // applies to all tags. + cache.Remove(req4.GetId()) + response = cache.GetEffectiveVulnStateForImage( + []string{vuln3}, + req4.GetScope().GetImageScope().GetRegistry(), + req4.GetScope().GetImageScope().GetRemote(), + req4.GetScope().GetImageScope().GetTag()) + assert.Equal(t, storage.VulnerabilityState_FALSE_POSITIVE, response[vuln3]) + + // Verify that for req5 scope cve is still deferred since the request still exists. + response = cache.GetEffectiveVulnStateForImage( + []string{vuln3}, + req5.GetScope().GetImageScope().GetRegistry(), + req5.GetScope().GetImageScope().GetRemote(), + req5.GetScope().GetImageScope().GetTag()) + assert.Equal(t, storage.VulnerabilityState_DEFERRED, response[vuln3]) + + // Verify that for req4 scope the cve is observed since the all tags request is removed. + cache.Remove(req6.GetId()) + response = cache.GetEffectiveVulnStateForImage( + []string{vuln3}, + req4.GetScope().GetImageScope().GetRegistry(), + req4.GetScope().GetImageScope().GetRemote(), + req4.GetScope().GetImageScope().GetTag()) + assert.Equal(t, storage.VulnerabilityState_OBSERVED, response[vuln3]) + + // Verify that for req5 scope, cves are still in deferred since the request still exists. + response = cache.GetEffectiveVulnStateForImage( + []string{vuln3}, + req5.GetScope().GetImageScope().GetRegistry(), + req5.GetScope().GetImageScope().GetRemote(), + req5.GetScope().GetImageScope().GetTag()) + assert.Equal(t, storage.VulnerabilityState_DEFERRED, response[vuln3]) + + // Verify that for req5 scope, cves are in observed since no request exists. + cache.Remove(req5.GetId()) + response = cache.GetEffectiveVulnStateForImage( + []string{vuln3}, + req5.GetScope().GetImageScope().GetRegistry(), + req5.GetScope().GetImageScope().GetRemote(), + req5.GetScope().GetImageScope().GetTag()) + assert.Equal(t, storage.VulnerabilityState_OBSERVED, response[vuln3]) } diff --git a/central/vulnerabilityrequest/cache/mocks/cache.go b/central/vulnerabilityrequest/cache/mocks/cache.go index 5c4834a6557b9..1f00ebc264507 100644 --- a/central/vulnerabilityrequest/cache/mocks/cache.go +++ b/central/vulnerabilityrequest/cache/mocks/cache.go @@ -97,6 +97,20 @@ func (mr *MockVulnReqCacheMockRecorder) GetEffectiveVulnState(cves interface{}, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEffectiveVulnState", reflect.TypeOf((*MockVulnReqCache)(nil).GetEffectiveVulnState), varargs...) } +// GetEffectiveVulnStateForImage mocks base method. +func (m *MockVulnReqCache) GetEffectiveVulnStateForImage(cves []string, registry, remote, tag string) map[string]storage.VulnerabilityState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEffectiveVulnStateForImage", cves, registry, remote, tag) + ret0, _ := ret[0].(map[string]storage.VulnerabilityState) + return ret0 +} + +// GetEffectiveVulnStateForImage indicates an expected call of GetEffectiveVulnStateForImage. +func (mr *MockVulnReqCacheMockRecorder) GetEffectiveVulnStateForImage(cves, registry, remote, tag interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEffectiveVulnStateForImage", reflect.TypeOf((*MockVulnReqCache)(nil).GetEffectiveVulnStateForImage), cves, registry, remote, tag) +} + // GetVulnsWithState mocks base method. func (m *MockVulnReqCache) GetVulnsWithState(registry, remote, tag string) map[string]storage.VulnerabilityState { m.ctrl.T.Helper() diff --git a/central/vulnerabilityrequest/manager/manager_impl.go b/central/vulnerabilityrequest/manager/manager_impl.go index 4c8b95d5f610c..fbfbdbccacbc5 100644 --- a/central/vulnerabilityrequest/manager/manager_impl.go +++ b/central/vulnerabilityrequest/manager/manager_impl.go @@ -91,13 +91,6 @@ func (m *managerImpl) SnoozeVulnerabilityOnRequest(_ context.Context, request *s m.pendingReqCache.Remove(request.GetId()) m.activeReqCache.Add(request) - // Get all the requests still active on the cves. - vulnReqIDs, err := m.getActiveVulnReqsForCVEs(request.GetCves().GetIds()...) - if err != nil { - return errors.Wrap(err, "could not search for vulnerability requests") - } - cveStateMap := m.activeReqCache.GetEffectiveVulnState(request.GetCves().GetIds(), vulnReqIDs...) - // Search for images matching the scope instead of image+cve combination. // Validation of image-cve existence is performed by the image-cve datastore. imageIDs, err := m.getImagesIDsForVulnRequest(request) @@ -108,9 +101,20 @@ func (m *managerImpl) SnoozeVulnerabilityOnRequest(_ context.Context, request *s return nil } - for _, cve := range request.GetCves().GetIds() { - if err := m.imageCVEEdges.UpdateVulnerabilityState(allAccessCtx, cve, imageIDs, cveStateMap[cve]); err != nil { - return errors.Wrapf(err, "could not snooze vulnerabilities for request %s", request.GetId()) + for _, imageID := range imageIDs { + img, found, err := m.images.GetImageMetadata(allAccessCtx, imageID) + if err != nil { + return errors.Wrapf(err, "could not un-snooze vulnerabilities for request %s", request.GetId()) + } + if !found { + continue + } + // Determine the effective for the cves in the image scope. + cveStateMap := m.activeReqCache.GetEffectiveVulnStateForImage(request.GetCves().GetIds(), img.GetName().GetRegistry(), img.GetName().GetRemote(), img.GetName().GetTag()) + for _, cve := range request.GetCves().GetIds() { + if err := m.imageCVEEdges.UpdateVulnerabilityState(allAccessCtx, cve, []string{imageID}, cveStateMap[cve]); err != nil { + return errors.Wrapf(err, "could not un-snooze vulnerabilities for request %s", request.GetId()) + } } } @@ -123,13 +127,6 @@ func (m *managerImpl) SnoozeVulnerabilityOnRequest(_ context.Context, request *s func (m *managerImpl) UnSnoozeVulnerabilityOnRequest(_ context.Context, request *storage.VulnerabilityRequest) error { m.activeReqCache.Remove(request.GetId()) - // Get all the requests still active on the cves. - vulnReqIDs, err := m.getActiveVulnReqsForCVEs(request.GetCves().GetIds()...) - if err != nil { - return errors.Wrap(err, "could not search for vulnerability requests") - } - cveStateMap := m.activeReqCache.GetEffectiveVulnState(request.GetCves().GetIds(), vulnReqIDs...) - // Search for images matching the scope instead of image+cve combination. // Validation of image-cve existence is performed by the image-cve datastore. imageIDs, err := m.getImagesIDsForVulnRequest(request) @@ -139,10 +136,22 @@ func (m *managerImpl) UnSnoozeVulnerabilityOnRequest(_ context.Context, request if len(imageIDs) == 0 { return nil } - for _, cve := range request.GetCves().GetIds() { - if err := m.imageCVEEdges.UpdateVulnerabilityState(allAccessCtx, cve, imageIDs, cveStateMap[cve]); err != nil { + + for _, imageID := range imageIDs { + img, found, err := m.images.GetImageMetadata(allAccessCtx, imageID) + if err != nil { return errors.Wrapf(err, "could not un-snooze vulnerabilities for request %s", request.GetId()) } + if !found { + continue + } + // Determine the effective for the cves in the image scope. + cveStateMap := m.activeReqCache.GetEffectiveVulnStateForImage(request.GetCves().GetIds(), img.GetName().GetRegistry(), img.GetName().GetRemote(), img.GetName().GetTag()) + for _, cve := range request.GetCves().GetIds() { + if err := m.imageCVEEdges.UpdateVulnerabilityState(allAccessCtx, cve, []string{imageID}, cveStateMap[cve]); err != nil { + return errors.Wrapf(err, "could not un-snooze vulnerabilities for request %s", request.GetId()) + } + } } go m.reprocessAffectedEntities(request.GetId(), imageIDs...) @@ -299,18 +308,6 @@ func (m *managerImpl) reprocessImage(requestID string, affectedImages ...string) return nil } -func (m *managerImpl) getActiveVulnReqsForCVEs(cves ...string) ([]string, error) { - // Get all the requests still active on the cves. - results, err := m.vulnReqs.Search(allAccessCtx, - search.NewQueryBuilder(). - AddExactMatches(search.CVE, cves...). - AddBools(search.ExpiredRequest, false).ProtoQuery()) - if err != nil { - return nil, err - } - return search.ResultsToIDs(results), nil -} - func (m *managerImpl) getAffectedDeployments(affectedImages ...string) (map[string][]string, error) { query := search.ConjunctionQuery( search.NewQueryBuilder().AddExactMatches(search.ImageSHA, affectedImages...).ProtoQuery(),