clair/database/pgsql/vulnerability_test.go
2017-08-10 11:25:29 -04:00

344 lines
9.3 KiB
Go

// Copyright 2017 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pgsql
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/coreos/clair/database"
"github.com/coreos/clair/ext/versionfmt/dpkg"
)
func TestInsertVulnerabilities(t *testing.T) {
store, tx := openSessionForTest(t, "InsertVulnerabilities", true)
ns1 := database.Namespace{
Name: "name",
VersionFormat: "random stuff",
}
ns2 := database.Namespace{
Name: "debian:7",
VersionFormat: "dpkg",
}
// invalid vulnerability
v1 := database.Vulnerability{
Name: "invalid",
Namespace: ns1,
}
vwa1 := database.VulnerabilityWithAffected{
Vulnerability: v1,
}
// valid vulnerability
v2 := database.Vulnerability{
Name: "valid",
Namespace: ns2,
Severity: database.UnknownSeverity,
}
vwa2 := database.VulnerabilityWithAffected{
Vulnerability: v2,
}
// empty
err := tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{})
assert.Nil(t, err)
// invalid content: vwa1 is invalid
err = tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{vwa1, vwa2})
assert.NotNil(t, err)
tx = restartSession(t, store, tx, false)
// invalid content: duplicated input
err = tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{vwa2, vwa2})
assert.NotNil(t, err)
tx = restartSession(t, store, tx, false)
// valid content
err = tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{vwa2})
assert.Nil(t, err)
tx = restartSession(t, store, tx, true)
// ensure the content is in database
vulns, err := tx.FindVulnerabilities([]database.VulnerabilityID{{Name: "valid", Namespace: "debian:7"}})
if assert.Nil(t, err) && assert.Len(t, vulns, 1) {
assert.True(t, vulns[0].Valid)
}
tx = restartSession(t, store, tx, false)
// valid content: vwa2 removed and inserted
err = tx.DeleteVulnerabilities([]database.VulnerabilityID{{Name: vwa2.Name, Namespace: vwa2.Namespace.Name}})
assert.Nil(t, err)
err = tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{vwa2})
assert.Nil(t, err)
closeTest(t, store, tx)
}
func TestCachingVulnerable(t *testing.T) {
datastore, tx := openSessionForTest(t, "CachingVulnerable", true)
defer closeTest(t, datastore, tx)
ns := database.Namespace{
Name: "debian:8",
VersionFormat: dpkg.ParserName,
}
f := database.NamespacedFeature{
Feature: database.Feature{
Name: "openssl",
Version: "1.0",
VersionFormat: dpkg.ParserName,
},
Namespace: ns,
}
vuln := database.VulnerabilityWithAffected{
Vulnerability: database.Vulnerability{
Name: "CVE-YAY",
Namespace: ns,
Severity: database.HighSeverity,
},
Affected: []database.AffectedFeature{
{
Namespace: ns,
FeatureName: "openssl",
AffectedVersion: "2.0",
FixedInVersion: "2.1",
},
},
}
vuln2 := database.VulnerabilityWithAffected{
Vulnerability: database.Vulnerability{
Name: "CVE-YAY2",
Namespace: ns,
Severity: database.HighSeverity,
},
Affected: []database.AffectedFeature{
{
Namespace: ns,
FeatureName: "openssl",
AffectedVersion: "2.1",
FixedInVersion: "2.2",
},
},
}
vulnFixed1 := database.VulnerabilityWithFixedIn{
Vulnerability: database.Vulnerability{
Name: "CVE-YAY",
Namespace: ns,
Severity: database.HighSeverity,
},
FixedInVersion: "2.1",
}
vulnFixed2 := database.VulnerabilityWithFixedIn{
Vulnerability: database.Vulnerability{
Name: "CVE-YAY2",
Namespace: ns,
Severity: database.HighSeverity,
},
FixedInVersion: "2.2",
}
if !assert.Nil(t, tx.InsertVulnerabilities([]database.VulnerabilityWithAffected{vuln, vuln2})) {
t.FailNow()
}
r, err := tx.FindAffectedNamespacedFeatures([]database.NamespacedFeature{f})
assert.Nil(t, err)
assert.Len(t, r, 1)
for _, anf := range r {
if assert.True(t, anf.Valid) && assert.Len(t, anf.AffectedBy, 2) {
for _, a := range anf.AffectedBy {
if a.Name == "CVE-YAY" {
assert.Equal(t, vulnFixed1, a)
} else if a.Name == "CVE-YAY2" {
assert.Equal(t, vulnFixed2, a)
} else {
t.FailNow()
}
}
}
}
}
func TestFindVulnerabilities(t *testing.T) {
datastore, tx := openSessionForTest(t, "FindVulnerabilities", true)
defer closeTest(t, datastore, tx)
vuln, err := tx.FindVulnerabilities([]database.VulnerabilityID{
{Name: "CVE-OPENSSL-1-DEB7", Namespace: "debian:7"},
{Name: "CVE-NOPE", Namespace: "debian:7"},
{Name: "CVE-NOT HERE"},
})
ns := database.Namespace{
Name: "debian:7",
VersionFormat: "dpkg",
}
expectedExisting := []database.VulnerabilityWithAffected{
{
Vulnerability: database.Vulnerability{
Namespace: ns,
Name: "CVE-OPENSSL-1-DEB7",
Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0",
Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7",
Severity: database.HighSeverity,
},
Affected: []database.AffectedFeature{
{
FeatureName: "openssl",
AffectedVersion: "2.0",
FixedInVersion: "2.0",
Namespace: ns,
},
{
FeatureName: "libssl",
AffectedVersion: "1.9-abc",
FixedInVersion: "1.9-abc",
Namespace: ns,
},
},
},
{
Vulnerability: database.Vulnerability{
Namespace: ns,
Name: "CVE-NOPE",
Description: "A vulnerability affecting nothing",
Severity: database.UnknownSeverity,
},
},
}
expectedExistingMap := map[database.VulnerabilityID]database.VulnerabilityWithAffected{}
for _, v := range expectedExisting {
expectedExistingMap[database.VulnerabilityID{Name: v.Name, Namespace: v.Namespace.Name}] = v
}
nonexisting := database.VulnerabilityWithAffected{
Vulnerability: database.Vulnerability{Name: "CVE-NOT HERE"},
}
if assert.Nil(t, err) {
for _, v := range vuln {
if v.Valid {
key := database.VulnerabilityID{
Name: v.Name,
Namespace: v.Namespace.Name,
}
expected, ok := expectedExistingMap[key]
if assert.True(t, ok, "vulnerability not found: "+key.Name+":"+key.Namespace) {
assertVulnerabilityWithAffectedEqual(t, expected, v.VulnerabilityWithAffected)
}
} else if !assert.Equal(t, nonexisting, v.VulnerabilityWithAffected) {
t.FailNow()
}
}
}
// same vulnerability
r, err := tx.FindVulnerabilities([]database.VulnerabilityID{
{Name: "CVE-OPENSSL-1-DEB7", Namespace: "debian:7"},
{Name: "CVE-OPENSSL-1-DEB7", Namespace: "debian:7"},
})
if assert.Nil(t, err) {
for _, vuln := range r {
if assert.True(t, vuln.Valid) {
expected, _ := expectedExistingMap[database.VulnerabilityID{Name: "CVE-OPENSSL-1-DEB7", Namespace: "debian:7"}]
assertVulnerabilityWithAffectedEqual(t, expected, vuln.VulnerabilityWithAffected)
}
}
}
}
func TestDeleteVulnerabilities(t *testing.T) {
datastore, tx := openSessionForTest(t, "DeleteVulnerabilities", true)
defer closeTest(t, datastore, tx)
remove := []database.VulnerabilityID{}
// empty case
assert.Nil(t, tx.DeleteVulnerabilities(remove))
// invalid case
remove = append(remove, database.VulnerabilityID{})
assert.NotNil(t, tx.DeleteVulnerabilities(remove))
// valid case
validRemove := []database.VulnerabilityID{
{Name: "CVE-OPENSSL-1-DEB7", Namespace: "debian:7"},
{Name: "CVE-NOPE", Namespace: "debian:7"},
}
assert.Nil(t, tx.DeleteVulnerabilities(validRemove))
vuln, err := tx.FindVulnerabilities(validRemove)
if assert.Nil(t, err) {
for _, v := range vuln {
assert.False(t, v.Valid)
}
}
}
func TestFindVulnerabilityIDs(t *testing.T) {
store, tx := openSessionForTest(t, "FindVulnerabilityIDs", true)
defer closeTest(t, store, tx)
ids, err := tx.findLatestDeletedVulnerabilityIDs([]database.VulnerabilityID{{Name: "CVE-DELETED", Namespace: "debian:7"}})
if assert.Nil(t, err) {
if !(assert.Len(t, ids, 1) && assert.True(t, ids[0].Valid) && assert.Equal(t, 3, ids[0].Int64)) {
assert.Fail(t, "")
}
}
ids, err = tx.findNotDeletedVulnerabilityIDs([]database.VulnerabilityID{{Name: "CVE-NOPE", Namespace: "debian:7"}})
if assert.Nil(t, err) {
if !(assert.Len(t, ids, 1) && assert.True(t, ids[0].Valid) && assert.Equal(t, 2, ids[0].Int64)) {
assert.Fail(t, "")
}
}
}
func assertVulnerabilityWithAffectedEqual(t *testing.T, expected database.VulnerabilityWithAffected, actual database.VulnerabilityWithAffected) bool {
return assert.Equal(t, expected.Vulnerability, actual.Vulnerability) && assertAffectedFeaturesEqual(t, expected.Affected, actual.Affected)
}
func assertAffectedFeaturesEqual(t *testing.T, expected []database.AffectedFeature, actual []database.AffectedFeature) bool {
if assert.Len(t, actual, len(expected)) {
has := map[database.AffectedFeature]bool{}
for _, i := range expected {
has[i] = false
}
for _, i := range actual {
if visited, ok := has[i]; !ok {
return false
} else if visited {
return false
}
has[i] = true
}
return true
}
return false
}