func (v Vulnerability) DatabaseModel() (database.Vulnerability, error) { severity := types.Priority(v.Severity) if !severity.IsValid() { return database.Vulnerability{}, errors.New("Invalid severity") } var dbFeatures []database.FeatureVersion for _, feature := range v.FixedIn { dbFeature, err := feature.DatabaseModel() if err != nil { return database.Vulnerability{}, err } dbFeatures = append(dbFeatures, dbFeature) } return database.Vulnerability{ Name: v.Name, Namespace: database.Namespace{Name: v.Namespace}, Description: v.Description, Link: v.Link, Severity: severity, Metadata: v.Metadata, FixedIn: dbFeatures, }, nil }
// GETLayersVulnerabilities returns the complete list of vulnerabilities that // a layer has if it exists. func GETLayersVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params) { // Get minumum priority parameter. minimumPriority := types.Priority(r.URL.Query().Get("minimumPriority")) if minimumPriority == "" { minimumPriority = "High" // Set default priority to High } else if !minimumPriority.IsValid() { httputils.WriteHTTPError(w, 0, cerrors.NewBadRequestError("invalid priority")) return } // Find layer layer, err := database.FindOneLayerByID(p.ByName("id"), []string{database.FieldLayerParent, database.FieldLayerPackages}) if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Find layer's packages. packagesNodes, err := layer.AllPackages() if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Find vulnerabilities. vulnerabilities, err := getVulnerabilitiesFromLayerPackagesNodes(packagesNodes, minimumPriority, []string{database.FieldVulnerabilityID, database.FieldVulnerabilityLink, database.FieldVulnerabilityPriority, database.FieldVulnerabilityDescription, database.FieldVulnerabilityCausedByPackage}) if err != nil { httputils.WriteHTTPError(w, 0, err) return } httputils.WriteHTTP(w, http.StatusOK, struct{ Vulnerabilities []*database.Vulnerability }{Vulnerabilities: vulnerabilities}) }
func ClairServiceInit() error { // Load database setting if setting.ClairDBPath != "" { clairConf.DBPath = setting.ClairDBPath } else { clairConf.DBPath = DefaultClairDBPath } clairConf.KeepDB = setting.ClairKeepDB clairConf.LogLevel = setting.ClairLogLevel clairConf.Duration = setting.ClairUpdateDuration clairConf.VulnPriority = setting.ClairVulnPriority // Set database if err := database.Open("bolt", clairConf.DBPath); err != nil { logrus.Debug(err) return err } // Set logLevel of clair lib logLevel, err := capnslog.ParseLevel(strings.ToUpper(clairConf.LogLevel)) if err != nil { logLevel, _ = capnslog.ParseLevel(strings.ToUpper(DefaultClairLogLevel)) } capnslog.SetGlobalLogLevel(logLevel) capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, false)) // Set minumum priority parameter. if types.Priority(clairConf.VulnPriority).IsValid() { logrus.Debugf("Vuln priority is invalid :%v.", clairConf.VulnPriority) clairConf.VulnPriority = DefaultClairVulnPriority } // Set 'duration' and Update the CVE database if clairConf.Duration == "" { logrus.Debugf("No duration set, so only update at the beginning.") go updater.Update() clairStopper = nil } else { st := utils.NewStopper() st.Begin() d, err := time.ParseDuration(clairConf.Duration) if err != nil { logrus.Warnf("Wrong duration format, use the default duration: %v.", DefaultClairUpdateDuration) clairConf.Duration = DefaultClairUpdateDuration d, err = time.ParseDuration(clairConf.Duration) if err != nil { logrus.Debugf("Cannot pare du %v", err) } } go updater.Run(d, st) clairStopper = st st.Begin() } return nil }
func intMain() int { // Parse command-line arguments. flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [options] image-id\n\nOptions:\n", os.Args[0]) flag.PrintDefaults() } flag.Parse() if len(flag.Args()) != 1 { flag.Usage() return 1 } imageName := flag.Args()[0] minSeverity := types.Priority(*flagMinimumSeverity) if !minSeverity.IsValid() { flag.Usage() return 1 } if *flagColorMode == "never" { color.NoColor = true } else if *flagColorMode == "always" { color.NoColor = false } // Create a temporary folder. tmpPath, err := ioutil.TempDir("", "analyze-local-image-") if err != nil { log.Fatalf("Could not create temporary folder: %s", err) } defer os.RemoveAll(tmpPath) // Intercept SIGINT / SIGKILl signals. interrupt := make(chan os.Signal) signal.Notify(interrupt, os.Interrupt, os.Kill) // Analyze the image. analyzeCh := make(chan error, 1) go func() { analyzeCh <- AnalyzeLocalImage(imageName, minSeverity, *flagEndpoint, *flagMyAddress, tmpPath) }() select { case <-interrupt: return 130 case err := <-analyzeCh: if err != nil { log.Print(err) return 1 } } return 0 }
// POSTBatchLayersVulnerabilities returns the complete list of vulnerabilities // that the provided layers have, if they all exist. func POSTBatchLayersVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params) { // Parse body var parameters POSTBatchLayersVulnerabilitiesParameters if s, err := httputils.ParseHTTPBody(r, ¶meters); err != nil { httputils.WriteHTTPError(w, s, err) return } if len(parameters.LayersIDs) == 0 { httputils.WriteHTTPError(w, http.StatusBadRequest, errors.New("at least one LayerID query parameter must be provided")) return } // Get minumum priority parameter. minimumPriority := types.Priority(r.URL.Query().Get("minimumPriority")) if minimumPriority == "" { minimumPriority = "High" // Set default priority to High } else if !minimumPriority.IsValid() { httputils.WriteHTTPError(w, 0, cerrors.NewBadRequestError("invalid priority")) return } response := make(map[string]interface{}) // For each LayerID parameter for _, layerID := range parameters.LayersIDs { // Find layer layer, err := database.FindOneLayerByID(layerID, []string{database.FieldLayerParent, database.FieldLayerPackages}) if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Find layer's packages. packagesNodes, err := layer.AllPackages() if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Find vulnerabilities. vulnerabilities, err := getVulnerabilitiesFromLayerPackagesNodes(packagesNodes, minimumPriority, []string{database.FieldVulnerabilityID, database.FieldVulnerabilityLink, database.FieldVulnerabilityPriority, database.FieldVulnerabilityDescription, database.FieldVulnerabilityCausedByPackage}) if err != nil { httputils.WriteHTTPError(w, 0, err) return } response[layerID] = struct{ Vulnerabilities []*database.Vulnerability }{Vulnerabilities: vulnerabilities} } httputils.WriteHTTP(w, http.StatusOK, response) }
func GetVulns(ID string) ([]*database.Vulnerability, error) { layer, err := database.FindOneLayerByID(ID, []string{database.FieldLayerParent, database.FieldLayerPackages}) if err != nil { logrus.Debugf("Cannot get layer: %v", err) return nil, err } packagesNodes, err := layer.AllPackages() if err != nil { logrus.Debugf("Cannot get packages: %v", err) return nil, err } return getVulnerabilitiesFromLayerPackagesNodes(packagesNodes, types.Priority(clairConf.VulnPriority), []string{database.FieldVulnerabilityID, database.FieldVulnerabilityLink, database.FieldVulnerabilityPriority, database.FieldVulnerabilityDescription}) }
// GETLayersVulnerabilitiesDiff returns the list of vulnerabilities that a layer // adds and removes if it exists. func GETLayersVulnerabilitiesDiff(w http.ResponseWriter, r *http.Request, p httprouter.Params) { // Get minumum priority parameter. minimumPriority := types.Priority(r.URL.Query().Get("minimumPriority")) if minimumPriority == "" { minimumPriority = "High" // Set default priority to High } else if !minimumPriority.IsValid() { httputils.WriteHTTPError(w, 0, cerrors.NewBadRequestError("invalid priority")) return } // Find layer. layer, err := database.FindOneLayerByID(p.ByName("id"), []string{database.FieldLayerPackages}) if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Selected fields for vulnerabilities. selectedFields := []string{database.FieldVulnerabilityID, database.FieldVulnerabilityLink, database.FieldVulnerabilityPriority, database.FieldVulnerabilityDescription, database.FieldVulnerabilityCausedByPackage} // Find vulnerabilities for installed packages. addedVulnerabilities, err := getVulnerabilitiesFromLayerPackagesNodes(layer.InstalledPackagesNodes, minimumPriority, selectedFields) if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Find vulnerabilities for removed packages. removedVulnerabilities, err := getVulnerabilitiesFromLayerPackagesNodes(layer.RemovedPackagesNodes, minimumPriority, selectedFields) if err != nil { httputils.WriteHTTPError(w, 0, err) return } // Remove vulnerabilities which appears both in added and removed lists (eg. case of updated packages but still vulnerable). for ia, a := range addedVulnerabilities { for ir, r := range removedVulnerabilities { if a.ID == r.ID { addedVulnerabilities = append(addedVulnerabilities[:ia], addedVulnerabilities[ia+1:]...) removedVulnerabilities = append(removedVulnerabilities[:ir], removedVulnerabilities[ir+1:]...) } } } httputils.WriteHTTP(w, http.StatusOK, struct{ Adds, Removes []*database.Vulnerability }{Adds: addedVulnerabilities, Removes: removedVulnerabilities}) }
// toVulnerabilities converts a path leading to one or multiple vulnerabilities to Vulnerability structs, selecting the specified fields func toVulnerabilities(path *path.Path, selectedFields []string) ([]*Vulnerability, error) { var vulnerabilities []*Vulnerability saveFields(path, selectedFields, []string{FieldVulnerabilityFixedIn, FieldVulnerabilityCausedByPackage}) it, _ := path.BuildIterator().Optimize() defer it.Close() for cayley.RawNext(it) { tags := make(map[string]graph.Value) it.TagResults(tags) vulnerability := Vulnerability{Node: store.NameOf(it.Result())} for _, selectedField := range selectedFields { switch selectedField { case FieldVulnerabilityID: vulnerability.ID = store.NameOf(tags[FieldVulnerabilityID]) case FieldVulnerabilityLink: vulnerability.Link = store.NameOf(tags[FieldVulnerabilityLink]) case FieldVulnerabilityPriority: vulnerability.Priority = types.Priority(store.NameOf(tags[FieldVulnerabilityPriority])) case FieldVulnerabilityDescription: vulnerability.Description = store.NameOf(tags[FieldVulnerabilityDescription]) case FieldVulnerabilityFixedIn: var err error vulnerability.FixedInNodes, err = toValues(cayley.StartPath(store, vulnerability.Node).Out(FieldVulnerabilityFixedIn)) if err != nil { log.Errorf("could not get fixedIn on vulnerability %s: %s.", vulnerability.Node, err.Error()) return []*Vulnerability{}, err } case FieldVulnerabilityCausedByPackage: vulnerability.CausedByPackage = store.NameOf(tags[FieldVulnerabilityCausedByPackage]) default: panic("unknown selectedField") } } vulnerabilities = append(vulnerabilities, &vulnerability) } if it.Err() != nil { log.Errorf("failed query in toVulnerabilities: %s", it.Err()) return []*Vulnerability{}, ErrBackendException } return vulnerabilities, nil }
func ClairGetVulns(ID string, ParentID string, Path string) ([]*database.Vulnerability, error) { // Process data. logrus.Debugf("Start to get vulnerabilities: %v %v %v", ID, ParentID, Path) if err := worker.Process(ID, ParentID, Path); err != nil { logrus.Debugf("End find err process: %v", err) return nil, err } // Find layer layer, err := database.FindOneLayerByID(ID, []string{database.FieldLayerParent, database.FieldLayerPackages}) if err != nil { logrus.Debugf("Cannot get layer: %v", err) return nil, err } // Find layer's packages. packagesNodes, err := layer.AllPackages() if err != nil { logrus.Debugf("Cannot get packages: %v", err) return nil, err } // Find vulnerabilities. return getVulnerabilitiesFromLayerPackagesNodes(packagesNodes, types.Priority(clairConf.VulnPriority), []string{database.FieldVulnerabilityID, database.FieldVulnerabilityLink, database.FieldVulnerabilityPriority, database.FieldVulnerabilityDescription}) }
func TestInsertVulnerability(t *testing.T) { datastore, err := openDatabaseForTest("InsertVulnerability", false) if err != nil { t.Error(err) return } defer datastore.Close() // Create some data. n1 := database.Namespace{Name: "TestInsertVulnerabilityNamespace1"} n2 := database.Namespace{Name: "TestInsertVulnerabilityNamespace2"} f1 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n1, }, Version: types.NewVersionUnsafe("1.0"), } f2 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion1", Namespace: n2, }, Version: types.NewVersionUnsafe("1.0"), } f3 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, Version: types.MaxVersion, } f4 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion2", }, Version: types.NewVersionUnsafe("1.4"), } f5 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion3", }, Version: types.NewVersionUnsafe("1.5"), } f6 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion4", }, Version: types.NewVersionUnsafe("0.1"), } f7 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, Version: types.MaxVersion, } f8 := database.FeatureVersion{ Feature: database.Feature{ Name: "TestInsertVulnerabilityFeatureVersion5", }, Version: types.MinVersion, } // Insert invalid vulnerabilities. for _, vulnerability := range []database.Vulnerability{ { Name: "", Namespace: n1, FixedIn: []database.FeatureVersion{f1}, Severity: types.Unknown, }, { Name: "TestInsertVulnerability0", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, Severity: types.Unknown, }, { Name: "TestInsertVulnerability0-", Namespace: database.Namespace{}, FixedIn: []database.FeatureVersion{f1}, }, { Name: "TestInsertVulnerability0", Namespace: n1, FixedIn: []database.FeatureVersion{f1}, Severity: types.Priority(""), }, { Name: "TestInsertVulnerability0", Namespace: n1, FixedIn: []database.FeatureVersion{f2}, Severity: types.Unknown, }, } { err := datastore.InsertVulnerabilities([]database.Vulnerability{vulnerability}, true) assert.Error(t, err) } // Insert a simple vulnerability and find it. v1meta := make(map[string]interface{}) v1meta["TestInsertVulnerabilityMetadata1"] = "TestInsertVulnerabilityMetadataValue1" v1meta["TestInsertVulnerabilityMetadata2"] = struct { Test string }{ Test: "TestInsertVulnerabilityMetadataValue1", } v1 := database.Vulnerability{ Name: "TestInsertVulnerability1", Namespace: n1, FixedIn: []database.FeatureVersion{f1, f3, f6, f7}, Severity: types.Low, Description: "TestInsertVulnerabilityDescription1", Link: "TestInsertVulnerabilityLink1", Metadata: v1meta, } err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) if assert.Nil(t, err) { v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) if assert.Nil(t, err) { equalsVuln(t, &v1, &v1f) } } // Update vulnerability. v1.Description = "TestInsertVulnerabilityLink2" v1.Link = "TestInsertVulnerabilityLink2" v1.Severity = types.High // Update f3 in f4, add fixed in f5, add fixed in f6 which already exists, removes fixed in f7 by // adding f8 which is f7 but with MinVersion. v1.FixedIn = []database.FeatureVersion{f4, f5, f6, f8} err = datastore.InsertVulnerabilities([]database.Vulnerability{v1}, true) if assert.Nil(t, err) { v1f, err := datastore.FindVulnerability(n1.Name, v1.Name) if assert.Nil(t, err) { // We already had f1 before the update. // Add it to the struct for comparison. v1.FixedIn = append(v1.FixedIn, f1) // Removes f8 from the struct for comparison as it was just here to cancel f7. for i := 0; i < len(v1.FixedIn); i++ { if v1.FixedIn[i].Feature.Name == f8.Feature.Name { v1.FixedIn = append(v1.FixedIn[:i], v1.FixedIn[i+1:]...) } } equalsVuln(t, &v1, &v1f) } } }
func AnalyzeLocalImage(imageName string, minSeverity types.Priority, endpoint, myAddress, tmpPath string) error { // Save image. log.Printf("Saving %s to local disk (this may take some time)", imageName) err := save(imageName, tmpPath) if err != nil { return fmt.Errorf("Could not save image: %s", err) } // Retrieve history. log.Println("Retrieving image history") layerIDs, err := historyFromManifest(tmpPath) if err != nil { layerIDs, err = historyFromCommand(imageName) } if err != nil || len(layerIDs) == 0 { return fmt.Errorf("Could not get image's history: %s", err) } // Setup a simple HTTP server if Clair is not local. if !strings.Contains(endpoint, "127.0.0.1") && !strings.Contains(endpoint, "localhost") { allowedHost := strings.TrimPrefix(endpoint, "http://") portIndex := strings.Index(allowedHost, ":") if portIndex >= 0 { allowedHost = allowedHost[:portIndex] } log.Printf("Setting up HTTP server (allowing: %s)\n", allowedHost) ch := make(chan error) go listenHTTP(tmpPath, allowedHost, ch) select { case err := <-ch: return fmt.Errorf("An error occured when starting HTTP server: %s", err) case <-time.After(100 * time.Millisecond): break } tmpPath = "http://" + myAddress + ":" + strconv.Itoa(httpPort) } // Analyze layers. log.Printf("Analyzing %d layers... \n", len(layerIDs)) for i := 0; i < len(layerIDs); i++ { log.Printf("Analyzing %s\n", layerIDs[i]) if i > 0 { err = analyzeLayer(endpoint, tmpPath+"/"+layerIDs[i]+"/layer.tar", layerIDs[i], layerIDs[i-1]) } else { err = analyzeLayer(endpoint, tmpPath+"/"+layerIDs[i]+"/layer.tar", layerIDs[i], "") } if err != nil { return fmt.Errorf("Could not analyze layer: %s", err) } } // Get vulnerabilities. log.Println("Retrieving image's vulnerabilities") layer, err := getLayer(endpoint, layerIDs[len(layerIDs)-1]) if err != nil { return fmt.Errorf("Could not get layer information: %s", err) } // Print report. fmt.Printf("Clair report for image %s (%s)\n", imageName, time.Now().UTC()) if len(layer.Features) == 0 { fmt.Printf("%s No features have been detected in the image. This usually means that the image isn't supported by Clair.\n", color.YellowString("NOTE:")) return nil } isSafe := true hasVisibleVulnerabilities := false var vulnerabilities = make([]vulnerabilityInfo, 0) for _, feature := range layer.Features { if len(feature.Vulnerabilities) > 0 { for _, vulnerability := range feature.Vulnerabilities { severity := types.Priority(vulnerability.Severity) isSafe = false if minSeverity.Compare(severity) > 0 { continue } hasVisibleVulnerabilities = true vulnerabilities = append(vulnerabilities, vulnerabilityInfo{vulnerability, feature, severity}) } } } // Sort vulnerabilitiy by severity. priority := func(v1, v2 vulnerabilityInfo) bool { return v1.severity.Compare(v2.severity) >= 0 } By(priority).Sort(vulnerabilities) for _, vulnerabilityInfo := range vulnerabilities { vulnerability := vulnerabilityInfo.vulnerability feature := vulnerabilityInfo.feature severity := vulnerabilityInfo.severity fmt.Printf("%s (%s)\n", vulnerability.Name, coloredSeverity(severity)) if vulnerability.Description != "" { fmt.Printf("%s\n\n", text.Indent(text.Wrap(vulnerability.Description, 80), "\t")) } fmt.Printf("\tPackage: %s @ %s\n", feature.Name, feature.Version) if vulnerability.FixedBy != "" { fmt.Printf("\tFixed version: %s\n", vulnerability.FixedBy) } if vulnerability.Link != "" { fmt.Printf("\tLink: %s\n", vulnerability.Link) } fmt.Printf("\tLayer: %s\n", feature.AddedBy) fmt.Println("") } if isSafe { fmt.Printf("%s No vulnerabilities were detected in your image\n", color.GreenString("Success!")) } else if !hasVisibleVulnerabilities { fmt.Printf("%s No vulnerabilities matching the minimum severity level were detected in your image\n", color.YellowString("NOTE:")) } return nil }