diff --git a/README.md b/README.md index 5289285..63b21ae 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ need to be booted with `-insecure-tls` for this to work. * `IGNORE_UNFIXED` - Do not count vulnerabilities without a fix towards the threshold +* `IGNORE_UNDERLYING` - Do not count vulnerabilities if they don't appear in the topmost layer. + Usage: CLAIR_ADDR=localhost CLAIR_OUTPUT=High CLAIR_THRESHOLD=10 DOCKER_USER=docker DOCKER_PASSWORD=secret klar postgres:9.5.1 diff --git a/clair/clair.go b/clair/clair.go index 837a536..bc794d4 100644 --- a/clair/clair.go +++ b/clair/clair.go @@ -105,9 +105,14 @@ func filterEmptyLayers(fsLayers []docker.FsLayer) (filteredLayers []docker.FsLay return } +func lastLayer(fsLayers []docker.FsLayer) (lastLayer []docker.FsLayer) { + lastLayer = append(lastLayer, fsLayers[len(fsLayers)-1]) + return +} + // Analyse sent each layer from Docker image to Clair and returns // a list of found vulnerabilities -func (c *Clair) Analyse(image *docker.Image) ([]*Vulnerability, error) { +func (c *Clair) Analyse(image *docker.Image, ignoreUnderlying bool) ([]*Vulnerability, error) { // Filter the empty layers in image image.FsLayers = filterEmptyLayers(image.FsLayers) layerLength := len(image.FsLayers) @@ -120,6 +125,10 @@ func (c *Clair) Analyse(image *docker.Image) ([]*Vulnerability, error) { if err := c.api.Push(image); err != nil { return nil, fmt.Errorf("push image %s/%s:%s to Clair failed: %s\n", image.Registry, image.Name, image.Tag, err.Error()) } + if ignoreUnderlying { + //we only analyse the last layer + image.FsLayers = lastLayer(image.FsLayers) + } vs, err := c.api.Analyze(image) if err != nil { return nil, fmt.Errorf("analyse image %s/%s:%s failed: %s\n", image.Registry, image.Name, image.Tag, err.Error()) diff --git a/klar.go b/klar.go index 32ec928..36edbfe 100644 --- a/klar.go +++ b/klar.go @@ -45,6 +45,7 @@ const ( optionRegistryInsecure = "REGISTRY_INSECURE" optionWhiteListFile = "WHITELIST_FILE" optionIgnoreUnfixed = "IGNORE_UNFIXED" + optionIgnoreUnderlying = "IGNORE_UNDERLYING" ) var priorities = []string{"Unknown", "Negligible", "Low", "Medium", "High", "Critical", "Defcon1"} @@ -119,15 +120,16 @@ type jsonOutput struct { } type config struct { - ClairAddr string - ClairOutput string - Threshold int - JSONOutput bool - FormatStyle string - ClairTimeout time.Duration - DockerConfig docker.Config - WhiteListFile string - IgnoreUnfixed bool + ClairAddr string + ClairOutput string + Threshold int + JSONOutput bool + FormatStyle string + ClairTimeout time.Duration + DockerConfig docker.Config + WhiteListFile string + IgnoreUnfixed bool + IgnoreUnderlying bool } func newConfig(args []string) (*config, error) { @@ -161,14 +163,15 @@ func newConfig(args []string) (*config, error) { } return &config{ - ClairAddr: clairAddr, - ClairOutput: clairOutput, - Threshold: parseIntOption(optionClairThreshold), - JSONOutput: formatStyle == "json", - FormatStyle: formatStyle, - IgnoreUnfixed: parseBoolOption(optionIgnoreUnfixed), - ClairTimeout: time.Duration(clairTimeout) * time.Minute, - WhiteListFile: os.Getenv(optionWhiteListFile), + ClairAddr: clairAddr, + ClairOutput: clairOutput, + Threshold: parseIntOption(optionClairThreshold), + JSONOutput: formatStyle == "json", + FormatStyle: formatStyle, + IgnoreUnfixed: parseBoolOption(optionIgnoreUnfixed), + IgnoreUnderlying: parseBoolOption(optionIgnoreUnderlying), + ClairTimeout: time.Duration(clairTimeout) * time.Minute, + WhiteListFile: os.Getenv(optionWhiteListFile), DockerConfig: docker.Config{ ImageName: args[1], User: os.Getenv(optionDockerUser), diff --git a/main.go b/main.go index a69832e..81f9a60 100644 --- a/main.go +++ b/main.go @@ -71,7 +71,7 @@ func main() { var vs []*clair.Vulnerability for _, ver := range []int{1, 3} { c := clair.NewClair(conf.ClairAddr, ver, conf.ClairTimeout) - vs, err = c.Analyse(image) + vs, err = c.Analyse(image, conf.IgnoreUnderlying) if err != nil { fmt.Fprintf(os.Stderr, "Failed to analyze using API v%d: %s\n", ver, err) } else {