// Copyright 2019 clair authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package vulnerability import ( "database/sql" "github.com/coreos/clair/database" "github.com/coreos/clair/database/pgsql/feature" "github.com/coreos/clair/database/pgsql/util" "github.com/lib/pq" log "github.com/sirupsen/logrus" ) const ( searchNamespacedFeaturesVulnerabilities = ` SELECT vanf.namespaced_feature_id, v.name, v.description, v.link, v.severity, v.metadata, vaf.fixedin, n.name, n.version_format FROM vulnerability_affected_namespaced_feature AS vanf, Vulnerability AS v, vulnerability_affected_feature AS vaf, namespace AS n WHERE vanf.namespaced_feature_id = ANY($1) AND vaf.id = vanf.added_by AND v.id = vanf.vulnerability_id AND n.id = v.namespace_id AND v.deleted_at IS NULL` lockVulnerabilityAffects = `LOCK vulnerability_affected_namespaced_feature IN SHARE ROW EXCLUSIVE MODE` insertVulnerabilityAffectedNamespacedFeature = ` INSERT INTO vulnerability_affected_namespaced_feature(vulnerability_id, namespaced_feature_id, added_by) VALUES ($1, $2, $3)` ) func queryPersistVulnerabilityAffectedNamespacedFeature(count int) string { return util.QueryPersist(count, "vulnerability_affected_namespaced_feature", "vulnerability_affected_namesp_vulnerability_id_namespaced_f_key", "vulnerability_id", "namespaced_feature_id", "added_by") } // FindAffectedNamespacedFeatures retrieves vulnerabilities associated with the // feature. func FindAffectedNamespacedFeatures(tx *sql.Tx, features []database.NamespacedFeature) ([]database.NullableAffectedNamespacedFeature, error) { if len(features) == 0 { return nil, nil } vulnerableFeatures := make([]database.NullableAffectedNamespacedFeature, len(features)) featureIDs, err := feature.FindNamespacedFeatureIDs(tx, features) if err != nil { return nil, err } for i, id := range featureIDs { if id.Valid { vulnerableFeatures[i].Valid = true vulnerableFeatures[i].NamespacedFeature = features[i] } } rows, err := tx.Query(searchNamespacedFeaturesVulnerabilities, pq.Array(featureIDs)) if err != nil { return nil, util.HandleError("searchNamespacedFeaturesVulnerabilities", err) } defer rows.Close() for rows.Next() { var ( featureID int64 vuln database.VulnerabilityWithFixedIn ) err := rows.Scan(&featureID, &vuln.Name, &vuln.Description, &vuln.Link, &vuln.Severity, &vuln.Metadata, &vuln.FixedInVersion, &vuln.Namespace.Name, &vuln.Namespace.VersionFormat, ) if err != nil { return nil, util.HandleError("searchNamespacedFeaturesVulnerabilities", err) } for i, id := range featureIDs { if id.Valid && id.Int64 == featureID { vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy = append(vulnerableFeatures[i].AffectedNamespacedFeature.AffectedBy, vuln) } } } return vulnerableFeatures, nil } func CacheAffectedNamespacedFeatures(tx *sql.Tx, features []database.NamespacedFeature) error { if len(features) == 0 { return nil } _, err := tx.Exec(lockVulnerabilityAffects) if err != nil { return util.HandleError("lockVulnerabilityAffects", err) } cache, err := SearchAffectingVulnerabilities(tx, features) keys := make([]interface{}, 0, len(cache)*3) for _, c := range cache { keys = append(keys, c.vulnID, c.nsFeatureID, c.vulnAffectingID) } if len(cache) == 0 { return nil } affected, err := tx.Exec(queryPersistVulnerabilityAffectedNamespacedFeature(len(cache)), keys...) if err != nil { return util.HandleError("persistVulnerabilityAffectedNamespacedFeature", err) } if count, err := affected.RowsAffected(); err != nil { log.Debugf("Cached %d features in vulnerability_affected_namespaced_feature", count) } return nil }