Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions clair.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"io/ioutil"
"net/http"

"github.com/coreos/clair/api/v1"
v1 "github.com/coreos/clair/api/v1"
)

const (
Expand All @@ -28,15 +28,21 @@ type vulnerabilityInfo struct {

// analyzeLayer tells Clair which layers to analyze
func analyzeLayers(layerIds []string, clairURL string, scannerIP string) {
var layerPath string
tmpPath := "http://" + scannerIP + ":" + httpPort

for i := 0; i < len(layerIds); i++ {
if legacy {
layerPath = tmpPath + "/" + layerIds[i] + "/layer.tar"
} else {
layerPath = tmpPath + "/" + layerIds[i]
}
logger.Infof("Analyzing %s", layerIds[i])

if i > 0 {
analyzeLayer(clairURL, tmpPath+"/"+layerIds[i]+"/layer.tar", layerIds[i], layerIds[i-1])
analyzeLayer(clairURL, layerPath, layerIds[i], layerIds[i-1])
} else {
analyzeLayer(clairURL, tmpPath+"/"+layerIds[i]+"/layer.tar", layerIds[i], "")
analyzeLayer(clairURL, layerPath, layerIds[i], "")
}
}
}
Expand Down
79 changes: 74 additions & 5 deletions docker.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package main

import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"strconv"
"strings"

"github.com/docker/docker/client"
Expand All @@ -16,20 +22,56 @@ type manifestJSON struct {
Layers []string
}

type newManifestJSON struct {
Layers []newManifestJSONDigest `json:"layers"`
}

type newManifestJSONDigest struct {
Digest string `json:"digest"`
}

// saveDockerImage saves Docker image to temorary folder
func saveDockerImage(imageName string, tmpPath string) {
docker := createDockerClient()

imageReader, err := docker.ImageSave(context.Background(), []string{imageName})
version, err := docker.ServerVersion(context.Background())
if err != nil {
logger.Fatalf("Could not save Docker image [%s]: %v", imageName, err)
logger.Fatalf("Could not find Docker version: %v", err)
}
majorVersion, err := strconv.Atoi(strings.Split(version.Version, ".")[0])
if err != nil {
logger.Fatalf("Error while parsing Docker version '%s': %v", version.Version, err)
}
if majorVersion < 25 {
legacy = true
imageReader, err := docker.ImageSave(context.Background(), []string{imageName})
if err != nil {
logger.Fatalf("Could not save Docker image [%s]: %v", imageName, err)
}
defer imageReader.Close()

defer imageReader.Close()
if err = untar(imageReader, tmpPath); err != nil {
logger.Fatalf("Could not save Docker image: could not untar [%s]: %v", imageName, err)
}
} else {
updateDockerImage(imageName, tmpPath)
}
}

func updateDockerImage(imageName string, tmpPath string) {
logger.Infof("Converting Docker image '%s' in '%s' to legacy format...", imageName, tmpPath)
cmd := exec.Command(getEnv("SKOPEO_BIN_PATH", "skopeo"), "copy", "--format", "v2s2", fmt.Sprintf("docker-daemon:%s", imageName), fmt.Sprintf("dir:%s", tmpPath))
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb

if err = untar(imageReader, tmpPath); err != nil {
logger.Fatalf("Could not save Docker image: could not untar [%s]: %v", imageName, err)
if errors.Is(cmd.Err, exec.ErrDot) {
cmd.Err = nil
}
if err := cmd.Run(); err != nil {
log.Fatalf("Error running skopeo: %s %s", err, errb.String())
}
updateLegacyManifestFile(tmpPath)
}

func createDockerClient() client.APIClient {
Expand Down Expand Up @@ -75,3 +117,30 @@ func parseAndValidateManifestFile(manifestFile io.Reader) []manifestJSON {
}
return manifest
}

// readManifestFile reads the local manifest.json
func updateLegacyManifestFile(path string) {
manifestFile := path + "/manifest.json"
mf, err := os.Open(manifestFile)
if err != nil {
logger.Fatalf("Could not read Docker image layers: could not open [%s]: %v", manifestFile, err)
}
defer mf.Close()

var manifest newManifestJSON

if err := json.NewDecoder(mf).Decode(&manifest); err != nil {
logger.Fatalf("Could not read Docker image layers: manifest.json is not json: %v", err)
}
mf.Close()
var legacyManifest []manifestJSON = []manifestJSON{
{
Layers: make([]string, len(manifest.Layers)),
},
}
for i, v := range manifest.Layers {
legacyManifest[0].Layers[i] = strings.TrimPrefix(v.Digest, "sha256:")
}
manifestFileContent, _ := json.MarshalIndent(legacyManifest, "", " ")
os.WriteFile(manifestFile, manifestFileContent, 0644)
}
2 changes: 2 additions & 0 deletions scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type vulnerabilitiesWhitelist struct {

const tmpPrefix = "clair-scanner-"

var legacy = false

type scannerConfig struct {
imageName string
whitelist vulnerabilitiesWhitelist
Expand Down
25 changes: 24 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ const (
httpPort = "9279"
)

type StatusRespWr struct {
http.ResponseWriter // We embed http.ResponseWriter
status int
}

func (w *StatusRespWr) WriteHeader(status int) {
w.status = status // Store the status for our own use
w.ResponseWriter.WriteHeader(status)
}

// httpFileServer servers files from a specified folder
// TODO if port can't be opened is not handled
func httpFileServer(path string) *http.Server {
server := &http.Server{Addr: ":" + httpPort}
server := &http.Server{Addr: ":" + httpPort, Handler: logRequest(http.DefaultServeMux)}
http.Handle("/", http.FileServer(http.Dir(path)))
go func() {
server.ListenAndServe()
Expand All @@ -21,3 +31,16 @@ func httpFileServer(path string) *http.Server {
logger.Infof("Server listening on port %s", httpPort)
return server
}

func logRequest(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Debugf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
srw := &StatusRespWr{ResponseWriter: w}
handler.ServeHTTP(srw, r)
if srw.status >= 400 { // 400+ codes are the error codes
logger.Debugf("Error status code: %d when serving path: %s",
srw.status, r.RequestURI)
}

})
}
10 changes: 9 additions & 1 deletion utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func untar(imageReader io.ReadCloser, target string) error {
}

path := filepath.Join(target, header.Name)
if !strings.HasPrefix(path, filepath.Clean(target) + string(os.PathSeparator)) {
if !strings.HasPrefix(path, filepath.Clean(target)+string(os.PathSeparator)) {
return fmt.Errorf("%s: illegal file path", header.Name)
}
info := header.FileInfo()
Expand Down Expand Up @@ -112,3 +112,11 @@ func validateThreshold(threshold string) {
}
logger.Fatalf("Invalid CVE severity threshold %s given", threshold)
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if len(value) == 0 {
return defaultValue
}
return value
}