bffa6499b7
created table layer_namespace to store the many to many unique mapping of layers and namespaces changed v1 api to provide a list of namespaces for each layer changed namespace detector to use all registered detectors to detect namespaces updated tests for multiple namespaces Fixes #150
438 lines
14 KiB
Go
438 lines
14 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 (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/coreos/clair/database"
|
|
"github.com/coreos/clair/ext/versionfmt/dpkg"
|
|
"github.com/coreos/clair/pkg/commonerr"
|
|
)
|
|
|
|
func TestFindLayer(t *testing.T) {
|
|
datastore, err := openDatabaseForTest("FindLayer", true)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer datastore.Close()
|
|
|
|
// Layer-0: no parent, no namespace, no feature, no vulnerability
|
|
layer, err := datastore.FindLayer("layer-0", false, false)
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
assert.Equal(t, "layer-0", layer.Name)
|
|
assert.Len(t, layer.Namespaces, 0)
|
|
assert.Nil(t, layer.Parent)
|
|
assert.Equal(t, 1, layer.EngineVersion)
|
|
assert.Len(t, layer.Features, 0)
|
|
}
|
|
|
|
layer, err = datastore.FindLayer("layer-0", true, false)
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
assert.Len(t, layer.Features, 0)
|
|
}
|
|
|
|
// Layer-1: one parent, adds two features, one vulnerability
|
|
layer, err = datastore.FindLayer("layer-1", false, false)
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) {
|
|
assert.Equal(t, layer.Name, "layer-1")
|
|
assertExpectedNamespaceName(t, &layer, []string{"debian:7"})
|
|
if assert.NotNil(t, layer.Parent) {
|
|
assert.Equal(t, "layer-0", layer.Parent.Name)
|
|
}
|
|
assert.Equal(t, 1, layer.EngineVersion)
|
|
assert.Len(t, layer.Features, 0)
|
|
}
|
|
|
|
layer, err = datastore.FindLayer("layer-1", true, false)
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
|
|
for _, featureVersion := range layer.Features {
|
|
assert.Equal(t, "debian:7", featureVersion.Feature.Namespace.Name)
|
|
|
|
switch featureVersion.Feature.Name {
|
|
case "wechat":
|
|
assert.Equal(t, "0.5", featureVersion.Version)
|
|
case "openssl":
|
|
assert.Equal(t, "1.0", featureVersion.Version)
|
|
default:
|
|
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
layer, err = datastore.FindLayer("layer-1", true, true)
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
|
|
for _, featureVersion := range layer.Features {
|
|
assert.Equal(t, "debian:7", featureVersion.Feature.Namespace.Name)
|
|
|
|
switch featureVersion.Feature.Name {
|
|
case "wechat":
|
|
assert.Equal(t, "0.5", featureVersion.Version)
|
|
case "openssl":
|
|
assert.Equal(t, "1.0", featureVersion.Version)
|
|
|
|
if assert.Len(t, featureVersion.AffectedBy, 1) {
|
|
assert.Equal(t, "debian:7", featureVersion.AffectedBy[0].Namespace.Name)
|
|
assert.Equal(t, "CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Name)
|
|
assert.Equal(t, database.HighSeverity, featureVersion.AffectedBy[0].Severity)
|
|
assert.Equal(t, "A vulnerability affecting OpenSSL < 2.0 on Debian 7.0", featureVersion.AffectedBy[0].Description)
|
|
assert.Equal(t, "http://google.com/#q=CVE-OPENSSL-1-DEB7", featureVersion.AffectedBy[0].Link)
|
|
assert.Equal(t, "2.0", featureVersion.AffectedBy[0].FixedBy)
|
|
}
|
|
default:
|
|
t.Errorf("unexpected package %s for layer-1", featureVersion.Feature.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Testing Multiple namespaces layer-3b has debian:7 and debian:8 namespaces
|
|
layer, err = datastore.FindLayer("layer-3b", true, true)
|
|
|
|
if assert.Nil(t, err) && assert.NotNil(t, layer) && assert.Len(t, layer.Features, 2) {
|
|
assert.Equal(t, "layer-3b", layer.Name)
|
|
// validate the namespace
|
|
assertExpectedNamespaceName(t, &layer, []string{"debian:7", "debian:8"})
|
|
for _, featureVersion := range layer.Features {
|
|
switch featureVersion.Feature.Namespace.Name {
|
|
case "debian:7":
|
|
assert.Equal(t, "wechat", featureVersion.Feature.Name)
|
|
assert.Equal(t, "0.5", featureVersion.Version)
|
|
case "debian:8":
|
|
assert.Equal(t, "openssl", featureVersion.Feature.Name)
|
|
assert.Equal(t, "1.0", featureVersion.Version)
|
|
default:
|
|
t.Errorf("unexpected package %s for layer-3b", featureVersion.Feature.Name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInsertLayer(t *testing.T) {
|
|
datastore, err := openDatabaseForTest("InsertLayer", false)
|
|
if err != nil {
|
|
t.Error(err)
|
|
return
|
|
}
|
|
defer datastore.Close()
|
|
|
|
// Insert invalid layer.
|
|
testInsertLayerInvalid(t, datastore)
|
|
|
|
// Insert a layer tree.
|
|
testInsertLayerTree(t, datastore)
|
|
|
|
// Update layer.
|
|
testInsertLayerUpdate(t, datastore)
|
|
|
|
// Delete layer.
|
|
testInsertLayerDelete(t, datastore)
|
|
}
|
|
|
|
func testInsertLayerInvalid(t *testing.T, datastore database.Datastore) {
|
|
invalidLayers := []database.Layer{
|
|
{},
|
|
{Name: "layer0", Parent: &database.Layer{}},
|
|
{Name: "layer0", Parent: &database.Layer{Name: "UnknownLayer"}},
|
|
}
|
|
|
|
for _, invalidLayer := range invalidLayers {
|
|
err := datastore.InsertLayer(invalidLayer)
|
|
assert.Error(t, err)
|
|
}
|
|
}
|
|
|
|
func testInsertLayerTree(t *testing.T, datastore database.Datastore) {
|
|
f1 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace2",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature1",
|
|
},
|
|
Version: "1.0",
|
|
}
|
|
f2 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace2",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature2",
|
|
},
|
|
Version: "0.34",
|
|
}
|
|
f3 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace2",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature3",
|
|
},
|
|
Version: "0.56",
|
|
}
|
|
f4 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace3",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature2",
|
|
},
|
|
Version: "0.34",
|
|
}
|
|
f5 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace3",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature3",
|
|
},
|
|
Version: "0.56",
|
|
}
|
|
f6 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace3",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature4",
|
|
},
|
|
Version: "0.666",
|
|
}
|
|
|
|
layers := []database.Layer{
|
|
{
|
|
Name: "TestInsertLayer1",
|
|
},
|
|
{
|
|
Name: "TestInsertLayer2",
|
|
Parent: &database.Layer{Name: "TestInsertLayer1"},
|
|
Namespaces: []database.Namespace{database.Namespace{
|
|
Name: "TestInsertLayerNamespace1",
|
|
VersionFormat: dpkg.ParserName,
|
|
}},
|
|
},
|
|
// This layer changes the namespace and adds Features.
|
|
{
|
|
Name: "TestInsertLayer3",
|
|
Parent: &database.Layer{Name: "TestInsertLayer2"},
|
|
Namespaces: []database.Namespace{database.Namespace{
|
|
Name: "TestInsertLayerNamespace2",
|
|
VersionFormat: dpkg.ParserName,
|
|
}},
|
|
Features: []database.FeatureVersion{f1, f2, f3},
|
|
},
|
|
// This layer covers the case where the last layer doesn't provide any new Feature.
|
|
{
|
|
Name: "TestInsertLayer4a",
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
Features: []database.FeatureVersion{f1, f2, f3},
|
|
},
|
|
// This layer covers the case where the last layer provides Features.
|
|
// It also modifies the Namespace ("upgrade") but keeps some Features not upgraded, their
|
|
// Namespaces should then remain unchanged.
|
|
{
|
|
Name: "TestInsertLayer4b",
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
Namespaces: []database.Namespace{database.Namespace{
|
|
Name: "TestInsertLayerNamespace3",
|
|
VersionFormat: dpkg.ParserName,
|
|
}},
|
|
Features: []database.FeatureVersion{
|
|
// Deletes TestInsertLayerFeature1.
|
|
// Keep TestInsertLayerFeature2 (old Namespace should be kept):
|
|
f4,
|
|
// Upgrades TestInsertLayerFeature3 (with new Namespace):
|
|
f5,
|
|
// Adds TestInsertLayerFeature4:
|
|
f6,
|
|
},
|
|
},
|
|
}
|
|
|
|
var err error
|
|
retrievedLayers := make(map[string]database.Layer)
|
|
for _, layer := range layers {
|
|
if layer.Parent != nil {
|
|
// Retrieve from database its parent and assign.
|
|
parent := retrievedLayers[layer.Parent.Name]
|
|
layer.Parent = &parent
|
|
}
|
|
|
|
err = datastore.InsertLayer(layer)
|
|
assert.Nil(t, err)
|
|
|
|
retrievedLayers[layer.Name], err = datastore.FindLayer(layer.Name, true, false)
|
|
assert.Nil(t, err)
|
|
}
|
|
|
|
// layer inherits all namespaces from its ancestries
|
|
l4a := retrievedLayers["TestInsertLayer4a"]
|
|
assertExpectedNamespaceName(t, &l4a, []string{"TestInsertLayerNamespace2", "TestInsertLayerNamespace1"})
|
|
assert.Len(t, l4a.Features, 3)
|
|
for _, featureVersion := range l4a.Features {
|
|
if cmpFV(featureVersion, f1) && cmpFV(featureVersion, f2) && cmpFV(featureVersion, f3) {
|
|
assert.Error(t, fmt.Errorf("TestInsertLayer4a contains an unexpected package: %#v. Should contain %#v and %#v and %#v.", featureVersion, f1, f2, f3))
|
|
}
|
|
}
|
|
|
|
l4b := retrievedLayers["TestInsertLayer4b"]
|
|
assertExpectedNamespaceName(t, &l4b, []string{"TestInsertLayerNamespace1", "TestInsertLayerNamespace2", "TestInsertLayerNamespace3"})
|
|
assert.Len(t, l4b.Features, 3)
|
|
for _, featureVersion := range l4b.Features {
|
|
if cmpFV(featureVersion, f2) && cmpFV(featureVersion, f5) && cmpFV(featureVersion, f6) {
|
|
assert.Error(t, fmt.Errorf("TestInsertLayer4a contains an unexpected package: %#v. Should contain %#v and %#v and %#v.", featureVersion, f2, f4, f6))
|
|
}
|
|
}
|
|
}
|
|
|
|
func testInsertLayerUpdate(t *testing.T, datastore database.Datastore) {
|
|
f7 := database.FeatureVersion{
|
|
Feature: database.Feature{
|
|
Namespace: database.Namespace{
|
|
Name: "TestInsertLayerNamespace3",
|
|
VersionFormat: dpkg.ParserName,
|
|
},
|
|
Name: "TestInsertLayerFeature7",
|
|
},
|
|
Version: "0.01",
|
|
}
|
|
|
|
l3, _ := datastore.FindLayer("TestInsertLayer3", true, false)
|
|
l3u := database.Layer{
|
|
Name: l3.Name,
|
|
Parent: l3.Parent,
|
|
Namespaces: []database.Namespace{database.Namespace{
|
|
Name: "TestInsertLayerNamespaceUpdated1",
|
|
VersionFormat: dpkg.ParserName,
|
|
}},
|
|
Features: []database.FeatureVersion{f7},
|
|
}
|
|
|
|
l4u := database.Layer{
|
|
Name: "TestInsertLayer4",
|
|
Parent: &database.Layer{Name: "TestInsertLayer3"},
|
|
Features: []database.FeatureVersion{f7},
|
|
EngineVersion: 2,
|
|
}
|
|
|
|
// Try to re-insert without increasing the EngineVersion.
|
|
err := datastore.InsertLayer(l3u)
|
|
assert.Nil(t, err)
|
|
|
|
l3uf, err := datastore.FindLayer(l3u.Name, true, false)
|
|
if assert.Nil(t, err) {
|
|
assertSameNamespaceName(t, &l3, &l3uf)
|
|
assert.Equal(t, l3.EngineVersion, l3uf.EngineVersion)
|
|
assert.Len(t, l3uf.Features, len(l3.Features))
|
|
}
|
|
|
|
// Update layer l3.
|
|
// Verify that the Namespace, EngineVersion and FeatureVersions got updated.
|
|
l3u.EngineVersion = 2
|
|
err = datastore.InsertLayer(l3u)
|
|
assert.Nil(t, err)
|
|
|
|
l3uf, err = datastore.FindLayer(l3u.Name, true, false)
|
|
if assert.Nil(t, err) {
|
|
assertSameNamespaceName(t, &l3u, &l3uf)
|
|
assert.Equal(t, l3u.EngineVersion, l3uf.EngineVersion)
|
|
if assert.Len(t, l3uf.Features, 1) {
|
|
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l3uf.Features[0])
|
|
}
|
|
}
|
|
|
|
// Update layer l4.
|
|
// Verify that the Namespace got updated from its new Parent's, and also verify the
|
|
// EnginVersion and FeatureVersions.
|
|
l4u.Parent = &l3uf
|
|
err = datastore.InsertLayer(l4u)
|
|
assert.Nil(t, err)
|
|
|
|
l4uf, err := datastore.FindLayer(l3u.Name, true, false)
|
|
if assert.Nil(t, err) {
|
|
assertSameNamespaceName(t, &l3u, &l4uf)
|
|
assert.Equal(t, l4u.EngineVersion, l4uf.EngineVersion)
|
|
if assert.Len(t, l4uf.Features, 1) {
|
|
assert.True(t, cmpFV(l3uf.Features[0], f7), "Updated layer should have %#v but actually have %#v", f7, l4uf.Features[0])
|
|
}
|
|
}
|
|
}
|
|
|
|
func assertSameNamespaceName(t *testing.T, layer1 *database.Layer, layer2 *database.Layer) {
|
|
assert.Len(t, compareStringLists(extractNamespaceName(layer1), extractNamespaceName(layer2)), 0)
|
|
}
|
|
|
|
func assertExpectedNamespaceName(t *testing.T, layer *database.Layer, expectedNames []string) {
|
|
assert.Len(t, compareStringLists(extractNamespaceName(layer), expectedNames), 0)
|
|
}
|
|
|
|
func extractNamespaceName(layer *database.Layer) []string {
|
|
slist := make([]string, 0, len(layer.Namespaces))
|
|
for _, ns := range layer.Namespaces {
|
|
slist = append(slist, ns.Name)
|
|
}
|
|
return slist
|
|
}
|
|
|
|
func testInsertLayerDelete(t *testing.T, datastore database.Datastore) {
|
|
err := datastore.DeleteLayer("TestInsertLayerX")
|
|
assert.Equal(t, commonerr.ErrNotFound, err)
|
|
|
|
// ensure layer_namespace table is cleaned up once a layer is removed
|
|
layer3, err := datastore.FindLayer("TestInsertLayer3", false, false)
|
|
layer4a, err := datastore.FindLayer("TestInsertLayer4a", false, false)
|
|
layer4b, err := datastore.FindLayer("TestInsertLayer4b", false, false)
|
|
|
|
err = datastore.DeleteLayer("TestInsertLayer3")
|
|
assert.Nil(t, err)
|
|
|
|
_, err = datastore.FindLayer("TestInsertLayer3", false, false)
|
|
assert.Equal(t, commonerr.ErrNotFound, err)
|
|
assertNotInLayerNamespace(t, layer3.ID, datastore)
|
|
_, err = datastore.FindLayer("TestInsertLayer4a", false, false)
|
|
assert.Equal(t, commonerr.ErrNotFound, err)
|
|
assertNotInLayerNamespace(t, layer4a.ID, datastore)
|
|
_, err = datastore.FindLayer("TestInsertLayer4b", true, false)
|
|
assert.Equal(t, commonerr.ErrNotFound, err)
|
|
assertNotInLayerNamespace(t, layer4b.ID, datastore)
|
|
}
|
|
|
|
func assertNotInLayerNamespace(t *testing.T, layerID int, datastore database.Datastore) {
|
|
pg, ok := datastore.(*pgSQL)
|
|
if !assert.True(t, ok) {
|
|
return
|
|
}
|
|
tx, err := pg.Begin()
|
|
if !assert.Nil(t, err) {
|
|
return
|
|
}
|
|
rows, err := tx.Query(searchLayerNamespace, layerID)
|
|
assert.False(t, rows.Next())
|
|
}
|
|
|
|
func cmpFV(a, b database.FeatureVersion) bool {
|
|
return a.Feature.Name == b.Feature.Name &&
|
|
a.Feature.Namespace.Name == b.Feature.Namespace.Name &&
|
|
a.Version == b.Version
|
|
}
|