Skip to content

Commit

Permalink
Louis/affected manifests (quay#178)
Browse files Browse the repository at this point in the history
* bug fixes

this commit preliminarily fixes several bugs.

it is not an error to reach layer fetch state and have no layers to
fetch.

pretty_name value on a vuln object was in the wrong order when scanned
from the database

ubuntu 16.04 has the wrong value in it's release struct

* add python repo scanner

in order to support index assisted manifest content lookup a
repository record must exist in the db to index.
this commit adds a repository scanner which indexes a repository
when any pip repository is found in a layer.

* data model update

this commit adds the manifest_index table to the indexer data model.
this table allows the contents of a manifest to be searched via an
index.

* omnimatcher package

this commit adds a package which uses all known matchers to understand
if a particular IndexRecord is vulnerable to a particular manifest.

* persistence

this commit adds methods to the indexer's store interface for persiting
to and retreiving from the manifest index.
a refactor of the store into better grouped interfaces is performed as
well.

* indexer plumbing

in this commit a new state is added to the Indexer's state machine
dubbed IndexManifest.
In this state a coalesced index report is persisted to the
manifest_index table.
Refactoring all the state definitinos to their own file is performed as
well.

Signed-off-by: ldelossa <ldelossa@redhat.com>

* tests

this commit adds tests to both the indexer state machine and the new
affected manifest persistence methods

Signed-off-by: ldelossa <ldelossa@redhat.com>

* pr fixes and various fixes

this commit handles some PR review fixes, finds and fixes an absence of
error handling in registerscanners method and adds a max group size
for concurrent handling of affected manifests requests

* further pr fixes
  • Loading branch information
Louis DeLosSantos authored May 29, 2020
1 parent c7de532 commit c4a279b
Show file tree
Hide file tree
Showing 48 changed files with 1,388 additions and 228 deletions.
9 changes: 1 addition & 8 deletions alpine/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,7 @@ func (u *Updater) parse(ctx context.Context, sdb *SecurityDB) ([]*claircore.Vuln
Package: &claircore.Package{
Name: pkg.Pkg.Name,
},
Dist: &claircore.Distribution{
VersionCodeName: string(u.repo),
VersionID: string(u.release),
Version: string(u.release),
DID: ID,
Name: Name,
PrettyName: ReleaseToPrettyName[u.release],
},
Dist: releaseToDist(u.release),
}
out = append(out, unpackSecFixes(partial, pkg.Pkg.Secfixes)...)
}
Expand Down
81 changes: 9 additions & 72 deletions alpine/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "botan",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2018-12435",
Expand All @@ -39,14 +32,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "botan",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2018-9860",
Expand All @@ -56,14 +42,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "botan",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2018-9127",
Expand All @@ -73,14 +52,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "botan",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2019-9929",
Expand All @@ -90,14 +62,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "cfengine",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2017-6949",
Expand All @@ -107,14 +72,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "chicken",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2017-9334",
Expand All @@ -124,14 +82,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "chicken",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2016-6830",
Expand All @@ -141,14 +92,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "chicken",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
{
Name: "CVE-2016-6831",
Expand All @@ -158,14 +102,7 @@ var V3_10_community_truncated_vulns = []*claircore.Vulnerability{
Package: &claircore.Package{
Name: "chicken",
},
Dist: &claircore.Distribution{
VersionCodeName: "community",
VersionID: "v3.10",
Version: "v3.10",
DID: "alpine",
Name: "Alpine Linux",
PrettyName: "Alpine Linux v3.10",
},
Dist: releaseToDist(V3_10),
},
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/klauspost/compress v1.9.4
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
github.com/lib/pq v1.2.0
github.com/opencontainers/runc v0.1.1 // indirect
github.com/quay/alas v1.0.1
github.com/quay/goval-parser v0.8.1
Expand Down
2 changes: 1 addition & 1 deletion internal/indexer/controller/coalesce.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func coalesce(ctx context.Context, s *Controller) (State, error) {
return Terminal, err
}
s.report = MergeSR(s.report, reports)
return IndexFinished, nil
return IndexManifest, nil
}

// MergeSR merges IndexReports.
Expand Down
44 changes: 44 additions & 0 deletions internal/indexer/controller/coalesce_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package controller

import (
"context"
"testing"

"github.com/quay/claircore/internal/indexer"
)

// Test_Coalesce confirms when no error is encountered
// the the coalesce method will transition to the correct
// state
//
// this test simply provides no Ecosystems to the index
// controller and does no work.
func Test_Coalesce(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()
var tt = []struct {
name string
expectedState State
}{
{
name: "successful index",
expectedState: IndexManifest,
},
}

for _, table := range tt {
t.Run(table.name, func(t *testing.T) {
ctx, done := context.WithCancel(ctx)
defer done()
indexer := New(&indexer.Opts{})

state, err := coalesce(ctx, indexer)
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
if table.expectedState != state {
t.Fatalf("got: %s, wanted: %s", table.expectedState, state)
}
})
}
}
43 changes: 0 additions & 43 deletions internal/indexer/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,6 @@ import (
"github.com/quay/claircore/internal/indexer"
)

// stateFunc implement the logic of our controller and map directly to States.
// returning an error will exit the controller in an error state.
// returning Terminal ends the controller in a non error state.
type stateFunc func(context.Context, *Controller) (State, error)

// States and their explanations.
// each state is implemented by a stateFunc implemented in their own files.
const (
// Terminal is the state which halts the fsm and returns the current s.result to the caller
Terminal State = iota
// CheckManifest determines if the manifest should be scanned.
// if no Terminal is returned and we return the existing IndexReport.
// Transitions: FetchLayers, Terminal
CheckManifest
// FetchLayers retrieves all the layers in a manifest and stacks them the same obtain the file image contents.
// creates the "image" layer
// Transitions: LayerScan
FetchLayers
// ScanLayers scans each image including the image layer and indexes the contents
// Transitions: BuildLayerResult
ScanLayers
// Coalesce runs each provided ecosystem's coalescer and mergs their scan results
// Transitions: ScanFinished
Coalesce
// IndexError state indicates a impassable error has occured.
// returns a ScanResult with the error field
// Transitions: Terminal
IndexError
// IndexFinished state is the terminal state and should return a IndexReport
// to the caller of Scan()
// Transitions: Terminal
IndexFinished
)

// provides a mapping of States to their implemented stateFunc methods
var stateToStateFunc = map[State]stateFunc{
CheckManifest: checkManifest,
FetchLayers: fetchLayers,
ScanLayers: scanLayers,
Coalesce: coalesce,
IndexFinished: indexFinished,
}

// StartState is a global variable which is normally set to the starting state
// of the controller. this global maybe overwriten to aide in testing. for example
// confirming that the controller does the correct thing in terminal states.
Expand Down
3 changes: 0 additions & 3 deletions internal/indexer/controller/fetchlayers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ func fetchLayers(ctx context.Context, s *Controller) (State, error) {
if err != nil {
return Terminal, fmt.Errorf("failed to determine layers to fetch: %w", err)
}
if len(toFetch) == 0 {
return Terminal, fmt.Errorf("reached FetchLayer states but could not determine layers to scan")
}
log.Debug().
Int("count", len(toFetch)).
Msg("fetching layers")
Expand Down
26 changes: 26 additions & 0 deletions internal/indexer/controller/indexmanifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controller

import (
"context"
"fmt"

"github.com/rs/zerolog"
)

func indexManifest(ctx context.Context, c *Controller) (State, error) {
log := zerolog.Ctx(ctx).With().
Str("state", c.getState().String()).
Logger()
ctx = log.WithContext(ctx)
log.Info().Msg("starting to index manifest...")

if c.report == nil {
return Terminal, fmt.Errorf("reached IndexManifest state with a nil report field. cannot continue")
}

err := c.Store.IndexManifest(ctx, c.report)
if err != nil {
return Terminal, fmt.Errorf("indexing manifest contents failed: %v", err)
}
return IndexFinished, nil
}
90 changes: 90 additions & 0 deletions internal/indexer/controller/indexmanifest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package controller

import (
"context"
"fmt"
"testing"

"github.com/golang/mock/gomock"
"github.com/quay/claircore/internal/indexer"
)

func Test_IndexManifest(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()
var tt = []struct {
name string
expectedState State
mock func(t *testing.T) indexer.Store
}{
{
name: "successful index",
expectedState: IndexFinished,
mock: func(t *testing.T) indexer.Store {
ctrl := gomock.NewController(t)
s := indexer.NewMockStore(ctrl)
s.EXPECT().IndexManifest(gomock.Any(), gomock.Any()).Return(nil)
return s
},
},
}

for _, table := range tt {
t.Run(table.name, func(t *testing.T) {
ctx, done := context.WithCancel(ctx)
defer done()
s := table.mock(t)
indexer := New(&indexer.Opts{
Store: s,
})

state, err := indexManifest(ctx, indexer)
if err != nil {
t.Fatalf("did not expect error: %v", err)
}
if table.expectedState != state {
t.Fatalf("got: %v, want: %v", state, table.expectedState)
}
})
}
}

func Test_IndexManifest_Failure(t *testing.T) {
ctx, done := context.WithCancel(context.Background())
defer done()
var tt = []struct {
name string
expectedState State
mock func(t *testing.T) indexer.Store
}{
{
name: "index failure",
expectedState: Terminal,
mock: func(t *testing.T) indexer.Store {
ctrl := gomock.NewController(t)
s := indexer.NewMockStore(ctrl)
s.EXPECT().IndexManifest(gomock.Any(), gomock.Any()).Return(fmt.Errorf("failure"))
return s
},
},
}

for _, table := range tt {
t.Run(table.name, func(t *testing.T) {
ctx, done := context.WithCancel(ctx)
defer done()
s := table.mock(t)
indexer := New(&indexer.Opts{
Store: s,
})

state, err := indexManifest(ctx, indexer)
if err == nil {
t.Fatalf("expected error")
}
if table.expectedState != state {
t.Fatalf("got: %v, want: %v", state, table.expectedState)
}
})
}
}
Loading

0 comments on commit c4a279b

Please sign in to comment.