contrib: adapt analyze-local-images for new API
This commit is contained in:
parent
ec8cf9fb26
commit
001c0a73d3
@ -22,7 +22,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -35,19 +34,20 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
postLayerURI = "/v1/layers"
|
postLayerURI = "/v1/layers"
|
||||||
getLayerFeaturesURI = "/v1/layers/%s?features=true"
|
getLayerFeaturesURI = "/v1/layers/%s?vulnerabilities"
|
||||||
httpPort = 9279
|
httpPort = 9279
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// Parse command-line arguments.
|
||||||
endpoint := flag.String("endpoint", "http://127.0.0.1:6060", "Address to Clair API")
|
endpoint := flag.String("endpoint", "http://127.0.0.1:6060", "Address to Clair API")
|
||||||
myAddress := flag.String("my-address", "127.0.0.1", "Address from the point of view of Clair")
|
myAddress := flag.String("my-address", "127.0.0.1", "Address from the point of view of Clair")
|
||||||
minimumPriority := flag.String("minimum-priority", "Low", "Minimum vulnerability vulnerability to show")
|
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [options] image-id\n\nOptions:\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, "Usage: %s [options] image-id\n\nOptions:\n", os.Args[0])
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if len(flag.Args()) != 1 {
|
if len(flag.Args()) != 1 {
|
||||||
@ -56,46 +56,41 @@ func main() {
|
|||||||
}
|
}
|
||||||
imageName := flag.Args()[0]
|
imageName := flag.Args()[0]
|
||||||
|
|
||||||
// Save image
|
// Save image.
|
||||||
fmt.Printf("Saving %s\n", imageName)
|
fmt.Printf("Saving %s\n", imageName)
|
||||||
path, err := save(imageName)
|
path, err := save(imageName)
|
||||||
defer os.RemoveAll(path)
|
defer os.RemoveAll(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("- Could not save image: %s\n", err)
|
fmt.Printf("- Could not save image: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve history
|
// Retrieve history.
|
||||||
fmt.Println("Getting image's history")
|
fmt.Println("Getting image's history")
|
||||||
layerIDs, err := historyFromManifest(path)
|
layerIDs, err := historyFromManifest(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
layerIDs, err = historyFromCommand(imageName)
|
layerIDs, err = historyFromCommand(imageName)
|
||||||
}
|
}
|
||||||
if err != nil || len(layerIDs) == 0 {
|
if err != nil || len(layerIDs) == 0 {
|
||||||
log.Fatalf("- Could not get image's history: %s\n", err)
|
fmt.Printf("- Could not get image's history: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup a simple HTTP server if Clair is not local
|
// Setup a simple HTTP server if Clair is not local.
|
||||||
if !strings.Contains(*endpoint, "127.0.0.1") && !strings.Contains(*endpoint, "localhost") {
|
if !strings.Contains(*endpoint, "127.0.0.1") && !strings.Contains(*endpoint, "localhost") {
|
||||||
go func(path string) {
|
allowedHost := strings.TrimPrefix(*endpoint, "http://")
|
||||||
allowedHost := strings.TrimPrefix(*endpoint, "http://")
|
portIndex := strings.Index(allowedHost, ":")
|
||||||
portIndex := strings.Index(allowedHost, ":")
|
if portIndex >= 0 {
|
||||||
if portIndex >= 0 {
|
allowedHost = allowedHost[:portIndex]
|
||||||
allowedHost = allowedHost[:portIndex]
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Setting up HTTP server (allowing: %s)\n", allowedHost)
|
go listenHTTP(path, allowedHost)
|
||||||
|
|
||||||
err := http.ListenAndServe(":"+strconv.Itoa(httpPort), restrictedFileServer(path, allowedHost))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("- An error occurs with the HTTP Server: %s\n", err)
|
|
||||||
}
|
|
||||||
}(path)
|
|
||||||
|
|
||||||
path = "http://" + *myAddress + ":" + strconv.Itoa(httpPort)
|
path = "http://" + *myAddress + ":" + strconv.Itoa(httpPort)
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Analyze layers
|
// Analyze layers.
|
||||||
fmt.Printf("Analyzing %d layers\n", len(layerIDs))
|
fmt.Printf("Analyzing %d layers\n", len(layerIDs))
|
||||||
for i := 0; i < len(layerIDs); i++ {
|
for i := 0; i < len(layerIDs); i++ {
|
||||||
fmt.Printf("- Analyzing %s\n", layerIDs[i])
|
fmt.Printf("- Analyzing %s\n", layerIDs[i])
|
||||||
@ -107,32 +102,61 @@ func main() {
|
|||||||
err = analyzeLayer(*endpoint, path+"/"+layerIDs[i]+"/layer.tar", layerIDs[i], "")
|
err = analyzeLayer(*endpoint, path+"/"+layerIDs[i]+"/layer.tar", layerIDs[i], "")
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("- Could not analyze layer: %s\n", err)
|
fmt.Printf("- Could not analyze layer: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get vulnerabilities
|
// Get vulnerabilities.
|
||||||
fmt.Println("Getting image's features")
|
fmt.Println("Getting image's vulnerabilities")
|
||||||
features, err := getFeatures(*endpoint, layerIDs[len(layerIDs)-1], *minimumPriority)
|
layer, err := getLayer(*endpoint, layerIDs[len(layerIDs)-1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("- Could not get features: %s\n", err)
|
fmt.Printf("- Could not get layer information: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print report.
|
||||||
|
fmt.Printf("\n# Clair report for image %s (%s)\n", imageName, time.Now().UTC())
|
||||||
|
|
||||||
|
if len(layer.Features) == 0 {
|
||||||
|
fmt.Println("No feature has been detected on the image.")
|
||||||
|
fmt.Println("This usually means that the image isn't supported by Clair.")
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
isSafe := true
|
isSafe := true
|
||||||
for _, feature := range features {
|
for _, feature := range layer.Features {
|
||||||
if len(feature.Vulnerabilities) != 0 {
|
fmt.Printf("## Feature: %s %s (%s)\n", feature.Name, feature.Version, feature.Namespace)
|
||||||
|
|
||||||
|
if len(feature.Vulnerabilities) > 0 {
|
||||||
isSafe = false
|
isSafe = false
|
||||||
fmt.Printf("- # Feature: %s %s %s\n", feature.Name, feature.Namespace, feature.Version)
|
|
||||||
|
fmt.Printf(" - Added by: %s\n", feature.AddedBy)
|
||||||
|
|
||||||
for _, vulnerability := range feature.Vulnerabilities {
|
for _, vulnerability := range feature.Vulnerabilities {
|
||||||
fmt.Printf("\t - Name: %s\n", vulnerability.Name)
|
fmt.Printf("### (%s) %s\n", vulnerability.Severity, vulnerability.Name)
|
||||||
fmt.Printf("\t - Priority: %s\n", vulnerability.Severity)
|
|
||||||
fmt.Printf("\t - Link: %s\n", vulnerability.Link)
|
if vulnerability.Description != "" {
|
||||||
fmt.Printf("\t - Description: %s\n", vulnerability.Description)
|
fmt.Printf(" - Link: %s\n", vulnerability.Link)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vulnerability.Link != "" {
|
||||||
|
fmt.Printf(" - Description: %s\n", vulnerability.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
if vulnerability.FixedBy != "" {
|
||||||
|
fmt.Printf(" - Fixed version: %s\n", vulnerability.FixedBy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vulnerability.Metadata) > 0 {
|
||||||
|
fmt.Printf(" - Metadata: %+v\n", vulnerability.Metadata)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isSafe {
|
if isSafe {
|
||||||
fmt.Println("Bravo, your image looks SAFE !")
|
fmt.Println("\nBravo, your image looks SAFE !")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,8 +252,37 @@ func historyFromCommand(imageName string) ([]string, error) {
|
|||||||
return layers, nil
|
return layers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func listenHTTP(path, allowedHost string) {
|
||||||
|
fmt.Printf("Setting up HTTP server (allowing: %s)\n", allowedHost)
|
||||||
|
|
||||||
|
restrictedFileServer := func(path, allowedHost string) http.Handler {
|
||||||
|
fc := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Host == allowedHost {
|
||||||
|
http.FileServer(http.Dir(path)).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(403)
|
||||||
|
}
|
||||||
|
return http.HandlerFunc(fc)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := http.ListenAndServe(":"+strconv.Itoa(httpPort), restrictedFileServer(path, allowedHost))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("- An error occurs with the HTTP server: %s\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func analyzeLayer(endpoint, path, layerName, parentLayerName string) error {
|
func analyzeLayer(endpoint, path, layerName, parentLayerName string) error {
|
||||||
payload := v1.LayerEnvelope{Layer: &v1.Layer{Name: layerName, Path: path, ParentName: parentLayerName, Format: "Docker"}}
|
payload := v1.LayerEnvelope{
|
||||||
|
Layer: &v1.Layer{
|
||||||
|
Name: layerName,
|
||||||
|
Path: path,
|
||||||
|
ParentName: parentLayerName,
|
||||||
|
Format: "Docker",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
jsonPayload, err := json.Marshal(payload)
|
jsonPayload, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -250,41 +303,31 @@ func analyzeLayer(endpoint, path, layerName, parentLayerName string) error {
|
|||||||
|
|
||||||
if response.StatusCode != 201 {
|
if response.StatusCode != 201 {
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
return fmt.Errorf("Got response %d with message %s", response.StatusCode, string(body))
|
return fmt.Errorf("- Got response %d with message %s", response.StatusCode, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFeatures(endpoint, layerID, minimumPriority string) ([]v1.Feature, error) {
|
func getLayer(endpoint, layerID string) (v1.Layer, error) {
|
||||||
response, err := http.Get(endpoint + fmt.Sprintf(getLayerFeaturesURI, layerID))
|
response, err := http.Get(endpoint + fmt.Sprintf(getLayerFeaturesURI, layerID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []v1.Feature{}, err
|
return v1.Layer{}, err
|
||||||
}
|
}
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
if response.StatusCode != 200 {
|
if response.StatusCode != 200 {
|
||||||
body, _ := ioutil.ReadAll(response.Body)
|
body, _ := ioutil.ReadAll(response.Body)
|
||||||
return []v1.Feature{}, fmt.Errorf("Got response %d with message %s", response.StatusCode, string(body))
|
err := fmt.Errorf("- Got response %d with message %s", response.StatusCode, string(body))
|
||||||
|
return v1.Layer{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponse v1.LayerEnvelope
|
var apiResponse v1.LayerEnvelope
|
||||||
if err = json.NewDecoder(response.Body).Decode(&apiResponse); err != nil {
|
if err = json.NewDecoder(response.Body).Decode(&apiResponse); err != nil {
|
||||||
return []v1.Feature{}, err
|
return v1.Layer{}, err
|
||||||
} else if apiResponse.Error != nil {
|
} else if apiResponse.Error != nil {
|
||||||
return []v1.Feature{}, errors.New(apiResponse.Error.Message)
|
return v1.Layer{}, errors.New(apiResponse.Error.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apiResponse.Layer.Features, nil
|
return *apiResponse.Layer, nil
|
||||||
}
|
|
||||||
|
|
||||||
func restrictedFileServer(path, allowedHost string) http.Handler {
|
|
||||||
fc := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Host == allowedHost {
|
|
||||||
http.FileServer(http.Dir(path)).ServeHTTP(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(403)
|
|
||||||
}
|
|
||||||
return http.HandlerFunc(fc)
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user