diff --git a/database/pgsql/queries.go b/database/pgsql/queries.go index e1088765..af77993e 100644 --- a/database/pgsql/queries.go +++ b/database/pgsql/queries.go @@ -167,6 +167,11 @@ func init() { UPDATE Vulnerability_FixedIn_Feature SET version = $3 WHERE vulnerability_id = $1 AND feature_id = $2 + RETURNING id` + + queries["r_vulnerability_fixedin_feature"] = ` + DELETE FROM Vulnerability_FixedIn_Feature + WHERE vulnerability_id = $1 AND feature_id = $2 RETURNING id` queries["r_vulnerability_affects_featureversion"] = ` diff --git a/database/pgsql/testdata/data.sql b/database/pgsql/testdata/data.sql index 3033533c..ab0a5a98 100644 --- a/database/pgsql/testdata/data.sql +++ b/database/pgsql/testdata/data.sql @@ -1,32 +1,44 @@ -INSERT INTO namespace (id, name) VALUES (1, 'debian:7'); -INSERT INTO namespace (id, name) VALUES (2, 'debian:8'); - -INSERT INTO feature (id, namespace_id, name) VALUES (1, 1, 'wechat'); -INSERT INTO feature (id, namespace_id, name) VALUES (2, 1, 'openssl'); -INSERT INTO feature (id, namespace_id, name) VALUES (4, 1, 'libssl'); -INSERT INTO feature (id, namespace_id, name) VALUES (3, 2, 'openssl'); -INSERT INTO featureversion (id, feature_id, version) VALUES (1, 1, '0.5'); -INSERT INTO featureversion (id, feature_id, version) VALUES (2, 2, '1.0'); -INSERT INTO featureversion (id, feature_id, version) VALUES (3, 2, '2.0'); -INSERT INTO featureversion (id, feature_id, version) VALUES (4, 3, '1.0'); - -INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES (1, 'layer-0', 1, NULL, NULL); -INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES (2, 'layer-1', 1, 1, 1); -INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES (3, 'layer-2', 1, 2, 1); -INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES (4, 'layer-3a', 1, 3, 1); -INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES (5, 'layer-3b', 1, 3, 2); -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (1, 2, 1, 'add'); -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (2, 2, 2, 'add'); -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (3, 3, 2, 'del'); -- layer-2: Update Debian:7 OpenSSL 1.0 -> 2.0 -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (4, 3, 3, 'add'); -- ^ -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (5, 5, 3, 'del'); -- layer-3b: Delete Debian:7 OpenSSL 2.0 -INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES (6, 5, 4, 'add'); -- layer-3b: Add Debian:8 OpenSSL 1.0 - -INSERT INTO vulnerability (id, namespace_id, name, description, link, severity) VALUES (1, 1, 'CVE-OPENSSL-1-DEB7', 'A vulnerability affecting OpenSSL < 2.0 on Debian 7.0', 'http://google.com/#q=CVE-OPENSSL-1-DEB7', 'High'); -INSERT INTO vulnerability_fixedin_feature (id, vulnerability_id, feature_id, version) VALUES (1, 1, 2, '2.0'); -INSERT INTO vulnerability_fixedin_feature (id, vulnerability_id, feature_id, version) VALUES (2, 1, 4, '1.9-abc'); -INSERT INTO vulnerability_affects_featureversion (id, vulnerability_id, featureversion_id, fixedin_id) VALUES (1, 1, 2, 1); -- CVE-OPENSSL-1-DEB7 affects Debian:7 OpenSSL 1.0 -INSERT INTO vulnerability (id, namespace_id, name, description, link, severity) VALUES (2, 1, 'CVE-NOPE', 'A vulnerability affecting nothing', '', 'Unknown'); +INSERT INTO namespace (id, name) VALUES + (1, 'debian:7'), + (2, 'debian:8'); + +INSERT INTO feature (id, namespace_id, name) VALUES + (1, 1, 'wechat'), + (2, 1, 'openssl'), + (4, 1, 'libssl'), + (3, 2, 'openssl'); + +INSERT INTO featureversion (id, feature_id, version) VALUES + (1, 1, '0.5'), + (2, 2, '1.0'), + (3, 2, '2.0'), + (4, 3, '1.0'); + +INSERT INTO layer (id, name, engineversion, parent_id, namespace_id) VALUES + (1, 'layer-0', 1, NULL, NULL), + (2, 'layer-1', 1, 1, 1), + (3, 'layer-2', 1, 2, 1), + (4, 'layer-3a', 1, 3, 1), + (5, 'layer-3b', 1, 3, 2); + +INSERT INTO layer_diff_featureversion (id, layer_id, featureversion_id, modification) VALUES + (1, 2, 1, 'add'), + (2, 2, 2, 'add'), + (3, 3, 2, 'del'), -- layer-2: Update Debian:7 OpenSSL 1.0 -> 2.0 + (4, 3, 3, 'add'), -- ^ + (5, 5, 3, 'del'), -- layer-3b: Delete Debian:7 OpenSSL 2.0 + (6, 5, 4, 'add'); -- layer-3b: Add Debian:8 OpenSSL 1.0 + +INSERT INTO vulnerability (id, namespace_id, name, description, link, severity) VALUES + (1, 1, 'CVE-OPENSSL-1-DEB7', 'A vulnerability affecting OpenSSL < 2.0 on Debian 7.0', 'http://google.com/#q=CVE-OPENSSL-1-DEB7', 'High'), + (2, 1, 'CVE-NOPE', 'A vulnerability affecting nothing', '', 'Unknown'); + +INSERT INTO vulnerability_fixedin_feature (id, vulnerability_id, feature_id, version) VALUES + (1, 1, 2, '2.0'), + (2, 1, 4, '1.9-abc'); + +INSERT INTO vulnerability_affects_featureversion (id, vulnerability_id, featureversion_id, fixedin_id) VALUES + (1, 1, 2, 1); -- CVE-OPENSSL-1-DEB7 affects Debian:7 OpenSSL 1.0 SELECT pg_catalog.setval(pg_get_serial_sequence('namespace', 'id'), (SELECT MAX(id) FROM namespace)+1); SELECT pg_catalog.setval(pg_get_serial_sequence('feature', 'id'), (SELECT MAX(id) FROM feature)+1); diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index cc7d4f85..4588bfed 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -64,6 +64,7 @@ func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vuln } // FixedIn.Namespace are not necessary, they are overwritten by the vuln. +// By setting the fixed version to minVersion, we can say that the vuln does'nt affect anymore. func (pgSQL *pgSQL) InsertVulnerabilities(vulnerabilities []database.Vulnerability) error { for _, vulnerability := range vulnerabilities { err := pgSQL.insertVulnerability(vulnerability) @@ -224,10 +225,6 @@ func createFeatureVersionNameMap(features []database.FeatureVersion) (map[string return m, s } -// TODO(Quentin-M): Add support for removing Vulnerability_FixedIn_Feature when Version = MinVersion. -// We should then update the vulnerability fetcher to do it. -// Also maybe we would delete a Vulnerability if it hasn't any FixedIn. -// --> And affects func (pgSQL *pgSQL) updateVulnerabilityFeatureVersions(tx *sql.Tx, vulnerability, existingVulnerability *database.Vulnerability, newFixedInFeatureVersions, updatedFixedInFeatureVersions []database.FeatureVersion) error { var fixedInID int @@ -247,23 +244,39 @@ func (pgSQL *pgSQL) updateVulnerabilityFeatureVersions(tx *sql.Tx, vulnerability } for _, fv := range updatedFixedInFeatureVersions { - // Update Vulnerability_FixedIn_Feature. - err := tx.QueryRow(getQuery("u_vulnerability_fixedin_feature"), vulnerability.ID, fv.ID, - &fv.Version).Scan(&fixedInID) - if err != nil { - return handleError("u_vulnerability_fixedin_feature", err) - } + if fv.Version != types.MinVersion { + // Update Vulnerability_FixedIn_Feature. + err := tx.QueryRow(getQuery("u_vulnerability_fixedin_feature"), vulnerability.ID, fv.ID, + &fv.Version).Scan(&fixedInID) + if err != nil { + return handleError("u_vulnerability_fixedin_feature", err) + } - // Drop all old Vulnerability_Affects_FeatureVersion. - _, err = tx.Exec(getQuery("r_vulnerability_affects_featureversion"), fixedInID) - if err != nil { - return handleError("r_vulnerability_affects_featureversion", err) - } + // Drop all old Vulnerability_Affects_FeatureVersion. + _, err = tx.Exec(getQuery("r_vulnerability_affects_featureversion"), fixedInID) + if err != nil { + return handleError("r_vulnerability_affects_featureversion", err) + } - // Insert Vulnerability_Affects_FeatureVersion. - err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerability.ID, fv.ID, fv.Version) - if err != nil { - return err + // Insert Vulnerability_Affects_FeatureVersion. + err = linkVulnerabilityToFeatureVersions(tx, fixedInID, vulnerability.ID, fv.ID, fv.Version) + if err != nil { + return err + } + } else { + // Updating FixedIn by saying that the fixed version is the lowest possible version, it + // basically means that the vulnerability doesn't affect the feature (anymore). + // Drop it from Vulnerability_FixedIn_Feature and Vulnerability_Affects_FeatureVersion. + err := tx.QueryRow(getQuery("r_vulnerability_fixedin_feature"), vulnerability.ID, fv.ID). + Scan(&fixedInID) + if err != nil { + return handleError("r_vulnerability_fixedin_feature", err) + } + + _, err = tx.Exec(getQuery("r_vulnerability_affects_featureversion"), fixedInID) + if err != nil { + return handleError("r_vulnerability_affects_featureversion", err) + } } } diff --git a/database/pgsql/vulnerability_test.go b/database/pgsql/vulnerability_test.go index 690a9670..d118a515 100644 --- a/database/pgsql/vulnerability_test.go +++ b/database/pgsql/vulnerability_test.go @@ -27,6 +27,7 @@ func TestFindVulnerability(t *testing.T) { Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", Link: "http://google.com/#q=CVE-OPENSSL-1-DEB7", Severity: types.High, + Namespace: database.Namespace{Name: "debian:7"}, FixedIn: []database.FeatureVersion{ database.FeatureVersion{ Feature: database.Feature{Name: "openssl"}, @@ -46,8 +47,10 @@ func TestFindVulnerability(t *testing.T) { // Find a vulnerability that has no link, no severity and no FixedIn. v2 := database.Vulnerability{ - Name: "CVE-OPENSSL-1-DEB7", - Description: "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", + Name: "CVE-NOPE", + Description: "A vulnerability affecting nothing", + Namespace: database.Namespace{Name: "debian:7"}, + Severity: types.Unknown, } v2f, err := datastore.FindVulnerability("debian:7", "CVE-NOPE") @@ -106,6 +109,18 @@ func TestInsertVulnerability(t *testing.T) { }, Version: types.NewVersionUnsafe("0.1"), } + f7 := database.FeatureVersion{ + Feature: database.Feature{ + Name: "TestInsertVulnerabilityFeatureVersion5", + }, + Version: types.MaxVersion, + } + f8 := database.FeatureVersion{ + Feature: database.Feature{ + Name: "TestInsertVulnerabilityFeatureVersion5", + }, + Version: types.MinVersion, + } // Insert invalid vulnerabilities. for _, vulnerability := range []database.Vulnerability{ @@ -147,7 +162,7 @@ func TestInsertVulnerability(t *testing.T) { v1 := database.Vulnerability{ Name: "TestInsertVulnerability1", Namespace: n1, - FixedIn: []database.FeatureVersion{f1, f3, f6}, + FixedIn: []database.FeatureVersion{f1, f3, f6, f7}, Severity: types.Low, Description: "TestInsertVulnerabilityDescription1", Link: "TestInsertVulnerabilityLink1", @@ -164,9 +179,9 @@ func TestInsertVulnerability(t *testing.T) { v1.Description = "TestInsertVulnerabilityLink2" v1.Link = "TestInsertVulnerabilityLink2" v1.Severity = types.High - // Update f3 by f4, add fixed by f5, add fixed by f6 which already exists. - // TODO(Quentin-M): Remove FixedIn. - v1.FixedIn = []database.FeatureVersion{f4, f5, f6} + // Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, removes fixed in f7 by + // adding f8 which is f7 but with MinVersion. + v1.FixedIn = []database.FeatureVersion{f4, f5, f6, f8} err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}) if assert.Nil(t, err) { @@ -175,6 +190,14 @@ func TestInsertVulnerability(t *testing.T) { // We already had f1 before the update. // Add it to the struct for comparison. v1.FixedIn = append(v1.FixedIn, f1) + + // Removes f8 from the struct for comparison as it was just here to cancel f7. + for i := 0; i < len(v1.FixedIn); i++ { + if v1.FixedIn[i].Feature.Name == f8.Feature.Name { + v1.FixedIn = append(v1.FixedIn[:i], v1.FixedIn[i+1:]...) + } + } + equalsVuln(t, &v1, &v1f) } } @@ -206,126 +229,6 @@ func equalsVuln(t *testing.T, expected, actual *database.Vulnerability) { // TODO Test Affects in Feature_Version and here. -// -// // Some data -// vuln1 := &database.Vulnerability{ID: "test1", Link: "link1", Priority: types.Medium, Description: "testDescription1", FixedInNodes: []string{"pkg1"}} -// vuln2 := &database.Vulnerability{ID: "test2", Link: "link2", Priority: types.High, Description: "testDescription2", FixedInNodes: []string{"pkg1", "pkg2"}} -// vuln3 := &database.Vulnerability{ID: "test3", Link: "link3", Priority: types.High, FixedInNodes: []string{"pkg3"}} // Empty description -// -// // Insert some vulnerabilities -// _, err := InsertVulnerabilities([]*database.Vulnerability{vuln1, vuln2, vuln3}) -// if assert.Nil(t, err) { -// // Find one of the vulnerabilities we just inserted and verify its content -// v1, err := FindOnedatabase.Vulnerability(vuln1.ID, Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.NotNil(t, v1) { -// assert.Equal(t, vuln1.ID, v1.ID) -// assert.Equal(t, vuln1.Link, v1.Link) -// assert.Equal(t, vuln1.Priority, v1.Priority) -// assert.Equal(t, vuln1.Description, v1.Description) -// if assert.Len(t, v1.FixedInNodes, 1) { -// assert.Equal(t, vuln1.FixedInNodes[0], v1.FixedInNodes[0]) -// } -// } -// } -// -// // Update a database.Vulnerability and verify its new content -// pkg1 := &Package{OS: "testOS", Name: "testpkg1", Version: types.NewVersionUnsafe("1.0")} -// InsertPackages([]*Package{pkg1}) -// vuln5 := &database.Vulnerability{ID: "test5", Link: "link5", Priority: types.Medium, Description: "testDescription5", FixedInNodes: []string{pkg1.Node}} -// -// _, err = InsertVulnerabilities([]*database.Vulnerability{vuln5}) -// if assert.Nil(t, err) { -// // Partial updates -// // # Just a field update -// vuln5b := &database.Vulnerability{ID: "test5", Priority: types.High} -// _, err := InsertVulnerabilities([]*database.Vulnerability{vuln5b}) -// if assert.Nil(t, err) { -// v5b, err := FindOnedatabase.Vulnerability(vuln5b.ID, Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.NotNil(t, v5b) { -// assert.Equal(t, vuln5b.ID, v5b.ID) -// assert.Equal(t, vuln5b.Priority, v5b.Priority) -// -// if assert.Len(t, v5b.FixedInNodes, 1) { -// assert.Contains(t, v5b.FixedInNodes, pkg1.Node) -// } -// } -// } -// -// // # Just a field update, twice in the same transaction -// vuln5b1 := &database.Vulnerability{ID: "test5", Link: "http://foo.bar"} -// vuln5b2 := &database.Vulnerability{ID: "test5", Link: "http://bar.foo"} -// _, err = InsertVulnerabilities([]*database.Vulnerability{vuln5b1, vuln5b2}) -// if assert.Nil(t, err) { -// v5b2, err := FindOnedatabase.Vulnerability(vuln5b2.ID, Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.NotNil(t, v5b2) { -// assert.Equal(t, vuln5b2.Link, v5b2.Link) -// } -// } -// -// // # All fields except fixedIn update -// vuln5c := &database.Vulnerability{ID: "test5", Link: "link5c", Priority: types.Critical, Description: "testDescription5c"} -// _, err = InsertVulnerabilities([]*database.Vulnerability{vuln5c}) -// if assert.Nil(t, err) { -// v5c, err := FindOnedatabase.Vulnerability(vuln5c.ID, Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.NotNil(t, v5c) { -// assert.Equal(t, vuln5c.ID, v5c.ID) -// assert.Equal(t, vuln5c.Link, v5c.Link) -// assert.Equal(t, vuln5c.Priority, v5c.Priority) -// assert.Equal(t, vuln5c.Description, v5c.Description) -// -// if assert.Len(t, v5c.FixedInNodes, 1) { -// assert.Contains(t, v5c.FixedInNodes, pkg1.Node) -// } -// } -// } -// -// // Complete update -// pkg2 := &Package{OS: "testOS", Name: "testpkg1", Version: types.NewVersionUnsafe("1.1")} -// pkg3 := &Package{OS: "testOS", Name: "testpkg2", Version: types.NewVersionUnsafe("1.0")} -// InsertPackages([]*Package{pkg2, pkg3}) -// vuln5d := &database.Vulnerability{ID: "test5", Link: "link5d", Priority: types.Low, Description: "testDescription5d", FixedInNodes: []string{pkg2.Node, pkg3.Node}} -// -// _, err = InsertVulnerabilities([]*database.Vulnerability{vuln5d}) -// if assert.Nil(t, err) { -// v5d, err := FindOnedatabase.Vulnerability(vuln5d.ID, Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.NotNil(t, v5d) { -// assert.Equal(t, vuln5d.ID, v5d.ID) -// assert.Equal(t, vuln5d.Link, v5d.Link) -// assert.Equal(t, vuln5d.Priority, v5d.Priority) -// assert.Equal(t, vuln5d.Description, v5d.Description) -// -// // Here, we ensure that a database.Vulnerability can only be fixed by one package of a given branch at a given time -// // And that we can add new fixed packages as well -// if assert.Len(t, v5d.FixedInNodes, 2) { -// assert.NotContains(t, v5d.FixedInNodes, pkg1.Node) -// } -// } -// } -// } -// -// // Create and update a database.Vulnerability's packages (and from the same branch) in the same batch -// pkg1 = &Package{OS: "testOS", Name: "testpkg1", Version: types.NewVersionUnsafe("1.0")} -// pkg1b := &Package{OS: "testOS", Name: "testpkg1", Version: types.NewVersionUnsafe("1.1")} -// InsertPackages([]*Package{pkg1, pkg1b}) -// -// // # Two updates of the same database.Vulnerability in the same batch with packages of the same branch -// pkg0 := &Package{OS: "testOS", Name: "testpkg0", Version: types.NewVersionUnsafe("1.0")} -// InsertPackages([]*Package{pkg0}) -// _, err = InsertVulnerabilities([]*database.Vulnerability{&database.Vulnerability{ID: "test7", Link: "link7", Priority: types.Medium, Description: "testDescription7", FixedInNodes: []string{pkg0.Node}}}) -// if assert.Nil(t, err) { -// vuln7b := &database.Vulnerability{ID: "test7", FixedInNodes: []string{pkg1.Node}} -// vuln7c := &database.Vulnerability{ID: "test7", FixedInNodes: []string{pkg1b.Node}} -// _, err = InsertVulnerabilities([]*database.Vulnerability{vuln7b, vuln7c}) -// if assert.Nil(t, err) { -// v7, err := FindOnedatabase.Vulnerability("test7", Fielddatabase.VulnerabilityAll) -// if assert.Nil(t, err) && assert.Len(t, v7.FixedInNodes, 2) { -// assert.Contains(t, v7.FixedInNodes, pkg0.Node) -// assert.NotContains(t, v7.FixedInNodes, pkg1.Node) -// assert.Contains(t, v7.FixedInNodes, pkg1b.Node) -// } -// } -// } - // func TestInsertVulnerabilityNotifications(t *testing.T) { // Open(&config.DatabaseConfig{Type: "memstore"}) // defer Close()