2015-11-13 19:11:28 +00:00
// Copyright 2015 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 database
import (
"github.com/coreos/clair/utils"
cerrors "github.com/coreos/clair/utils/errors"
"github.com/coreos/clair/utils/types"
"github.com/google/cayley"
"github.com/google/cayley/graph"
"github.com/google/cayley/graph/path"
)
const (
FieldVulnerabilityID = "id"
FieldVulnerabilityLink = "link"
FieldVulnerabilityPriority = "priority"
FieldVulnerabilityDescription = "description"
FieldVulnerabilityFixedIn = "fixedIn"
2015-12-01 21:57:16 +00:00
// FieldVulnerabilityCausedByPackage only makes sense with FindAllVulnerabilitiesByFixedIn.
FieldVulnerabilityCausedByPackage = "causedByPackage"
2015-11-20 20:03:20 +00:00
// This field is not selectable and is for internal use only.
fieldVulnerabilityIsValue = "vulnerability"
2015-11-13 19:11:28 +00:00
)
var FieldVulnerabilityAll = [ ] string { FieldVulnerabilityID , FieldVulnerabilityLink , FieldVulnerabilityPriority , FieldVulnerabilityDescription , FieldVulnerabilityFixedIn }
// Vulnerability represents a vulnerability that is fixed in some Packages
type Vulnerability struct {
Node string ` json:"-" `
ID string
Link string
Priority types . Priority
Description string ` json:",omitempty" `
FixedInNodes [ ] string ` json:"-" `
2015-12-01 21:57:16 +00:00
CausedByPackage string ` json:",omitempty" `
2015-11-13 19:11:28 +00:00
}
// GetNode returns an unique identifier for the graph node
// Requires the key field: ID
func ( v * Vulnerability ) GetNode ( ) string {
2015-11-20 20:03:20 +00:00
return fieldVulnerabilityIsValue + ":" + utils . Hash ( v . ID )
2015-11-13 19:11:28 +00:00
}
// ToAbstractVulnerability converts a Vulnerability into an
// AbstractVulnerability.
func ( v * Vulnerability ) ToAbstractVulnerability ( ) ( * AbstractVulnerability , error ) {
// Find FixedIn packages.
fixedInPackages , err := FindAllPackagesByNodes ( v . FixedInNodes , [ ] string { FieldPackageOS , FieldPackageName , FieldPackageVersion } )
if err != nil {
return nil , err
}
return & AbstractVulnerability {
ID : v . ID ,
Link : v . Link ,
Priority : v . Priority ,
Description : v . Description ,
AffectedPackages : PackagesToAbstractPackages ( fixedInPackages ) ,
} , nil
}
// AbstractVulnerability represents a Vulnerability as it is defined in the database
// package but exposes directly a list of AbstractPackage instead of
// nodes to packages.
type AbstractVulnerability struct {
ID string
Link string
Priority types . Priority
Description string
AffectedPackages [ ] * AbstractPackage
}
// ToVulnerability converts an abstractVulnerability into
// a Vulnerability
func ( av * AbstractVulnerability ) ToVulnerability ( fixedInNodes [ ] string ) * Vulnerability {
return & Vulnerability {
ID : av . ID ,
Link : av . Link ,
Priority : av . Priority ,
Description : av . Description ,
FixedInNodes : fixedInNodes ,
}
}
// InsertVulnerabilities inserts or updates several vulnerabilities in the database in one transaction
// During an update, if the vulnerability was previously fixed by a version in a branch and a new package of that branch is specified, the previous one is deleted
// Otherwise, it simply adds the defined packages, there is currently no way to delete affected packages.
//
// ID, Link, Priority and FixedInNodes fields have to be specified. Description is optionnal.
func InsertVulnerabilities ( vulnerabilities [ ] * Vulnerability ) ( [ ] Notification , error ) {
if len ( vulnerabilities ) == 0 {
return [ ] Notification { } , nil
}
// Create required data structure
var err error
t := cayley . NewTransaction ( )
cachedVulnerabilities := make ( map [ string ] * Vulnerability )
2015-11-21 02:47:48 +00:00
var notifications [ ] Notification
2015-11-13 19:11:28 +00:00
newVulnerabilityNotifications := make ( map [ string ] * NewVulnerabilityNotification )
vulnerabilityPriorityIncreasedNotifications := make ( map [ string ] * VulnerabilityPriorityIncreasedNotification )
vulnerabilityPackageChangedNotifications := make ( map [ string ] * VulnerabilityPackageChangedNotification )
// Iterate over all the vulnerabilities we need to insert/update
for _ , vulnerability := range vulnerabilities {
2015-11-21 02:47:48 +00:00
// Check if the vulnerability already exists
2015-11-13 19:11:28 +00:00
existingVulnerability , _ := cachedVulnerabilities [ vulnerability . ID ]
if existingVulnerability == nil {
existingVulnerability , err = FindOneVulnerability ( vulnerability . ID , FieldVulnerabilityAll )
if err != nil && err != cerrors . ErrNotFound {
return [ ] Notification { } , err
}
if existingVulnerability != nil {
cachedVulnerabilities [ vulnerability . ID ] = existingVulnerability
}
}
// Insert/Update vulnerability
if existingVulnerability == nil {
// The vulnerability does not exist, create it
// Verify parameters
if vulnerability . ID == "" || vulnerability . Link == "" || vulnerability . Priority == "" {
log . Warningf ( "could not insert an incomplete vulnerability [ID: %s, Link: %s, Priority: %s]" , vulnerability . ID , vulnerability . Link , vulnerability . Priority )
return [ ] Notification { } , cerrors . NewBadRequestError ( "Could not insert an incomplete vulnerability" )
}
if ! vulnerability . Priority . IsValid ( ) {
log . Warningf ( "could not insert a vulnerability which has an invalid priority [ID: %s, Link: %s, Priority: %s]. Valid priorities are: %v." , vulnerability . ID , vulnerability . Link , vulnerability . Priority , types . Priorities )
return [ ] Notification { } , cerrors . NewBadRequestError ( "Could not insert a vulnerability which has an invalid priority" )
}
if len ( vulnerability . FixedInNodes ) == 0 {
log . Warningf ( "could not insert a vulnerability which doesn't affect any package [ID: %s]." , vulnerability . ID )
return [ ] Notification { } , cerrors . NewBadRequestError ( "could not insert a vulnerability which doesn't affect any package" )
}
// Insert it
vulnerability . Node = vulnerability . GetNode ( )
2015-12-05 07:42:37 +00:00
t . AddQuad ( cayley . Triple ( vulnerability . Node , fieldIs , fieldVulnerabilityIsValue ) )
t . AddQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityID , vulnerability . ID ) )
t . AddQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityLink , vulnerability . Link ) )
t . AddQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityPriority , string ( vulnerability . Priority ) ) )
t . AddQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityDescription , vulnerability . Description ) )
2015-11-13 19:11:28 +00:00
for _ , p := range vulnerability . FixedInNodes {
2015-12-05 07:42:37 +00:00
t . AddQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityFixedIn , p ) )
2015-11-13 19:11:28 +00:00
}
// Add a notification
2015-11-21 02:47:48 +00:00
notification := & NewVulnerabilityNotification { VulnerabilityID : vulnerability . ID }
notifications = append ( notifications , notification )
newVulnerabilityNotifications [ vulnerability . ID ] = notification
cachedVulnerabilities [ vulnerability . ID ] = vulnerability
2015-11-13 19:11:28 +00:00
} else {
// The vulnerability already exists, update it
if vulnerability . Link != "" && existingVulnerability . Link != vulnerability . Link {
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityLink , existingVulnerability . Link ) )
t . AddQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityLink , vulnerability . Link ) )
2015-11-13 19:11:28 +00:00
existingVulnerability . Link = vulnerability . Link
}
2015-11-21 02:47:48 +00:00
2015-11-13 19:11:28 +00:00
if vulnerability . Priority != "" && vulnerability . Priority != types . Unknown && existingVulnerability . Priority != vulnerability . Priority {
if ! vulnerability . Priority . IsValid ( ) {
log . Warningf ( "could not update a vulnerability which has an invalid priority [ID: %s, Link: %s, Priority: %s]. Valid priorities are: %v." , vulnerability . ID , vulnerability . Link , vulnerability . Priority , types . Priorities )
return [ ] Notification { } , cerrors . NewBadRequestError ( "Could not update a vulnerability which has an invalid priority" )
}
// Add a notification about the priority change if the new priority is higher and the vulnerability is not new
if vulnerability . Priority . Compare ( existingVulnerability . Priority ) > 0 {
if _ , newVulnerabilityNotificationExists := newVulnerabilityNotifications [ vulnerability . ID ] ; ! newVulnerabilityNotificationExists {
// Any priorityChangeNotification already ?
if existingPriorityNotification , _ := vulnerabilityPriorityIncreasedNotifications [ vulnerability . ID ] ; existingPriorityNotification != nil {
// There is a priority change notification, replace it but keep the old priority field
2015-11-21 02:47:48 +00:00
existingPriorityNotification . NewPriority = vulnerability . Priority
2015-11-13 19:11:28 +00:00
} else {
// No previous notification, just add a new one
2015-11-21 02:47:48 +00:00
notification := & VulnerabilityPriorityIncreasedNotification { OldPriority : existingVulnerability . Priority , NewPriority : vulnerability . Priority , VulnerabilityID : existingVulnerability . ID }
notifications = append ( notifications , notification )
vulnerabilityPriorityIncreasedNotifications [ vulnerability . ID ] = notification
2015-11-13 19:11:28 +00:00
}
}
}
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityPriority , string ( existingVulnerability . Priority ) ) )
t . AddQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityPriority , string ( vulnerability . Priority ) ) )
2015-11-13 19:11:28 +00:00
existingVulnerability . Priority = vulnerability . Priority
}
2015-11-21 02:47:48 +00:00
2015-11-13 19:11:28 +00:00
if vulnerability . Description != "" && existingVulnerability . Description != vulnerability . Description {
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityDescription , existingVulnerability . Description ) )
t . AddQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityDescription , vulnerability . Description ) )
2015-11-13 19:11:28 +00:00
existingVulnerability . Description = vulnerability . Description
}
2015-11-21 02:47:48 +00:00
newFixedInNodes := utils . CompareStringLists ( vulnerability . FixedInNodes , existingVulnerability . FixedInNodes )
if len ( newFixedInNodes ) > 0 {
2015-11-13 19:11:28 +00:00
var removedNodes [ ] string
var addedNodes [ ] string
existingVulnerabilityFixedInPackages , err := FindAllPackagesByNodes ( existingVulnerability . FixedInNodes , [ ] string { FieldPackageOS , FieldPackageName , FieldPackageVersion } )
if err != nil {
return [ ] Notification { } , err
}
2015-11-21 02:47:48 +00:00
newFixedInPackages , err := FindAllPackagesByNodes ( newFixedInNodes , [ ] string { FieldPackageOS , FieldPackageName , FieldPackageVersion } )
2015-11-13 19:11:28 +00:00
if err != nil {
return [ ] Notification { } , err
}
2015-11-21 02:47:48 +00:00
for _ , p := range newFixedInPackages {
2015-11-13 19:11:28 +00:00
for _ , ep := range existingVulnerabilityFixedInPackages {
2015-11-21 02:47:48 +00:00
if p . Branch ( ) == ep . Branch ( ) {
2015-11-13 19:11:28 +00:00
// A link to this package branch already exist and is not the same version, we will delete it
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityFixedIn , ep . Node ) )
2015-11-13 19:11:28 +00:00
var index int
for i , n := range existingVulnerability . FixedInNodes {
if n == ep . Node {
index = i
break
}
}
existingVulnerability . FixedInNodes = append ( existingVulnerability . FixedInNodes [ index : ] , existingVulnerability . FixedInNodes [ index + 1 : ] ... )
removedNodes = append ( removedNodes , ep . Node )
}
}
2015-12-05 07:42:37 +00:00
t . AddQuad ( cayley . Triple ( existingVulnerability . Node , FieldVulnerabilityFixedIn , p . Node ) )
2015-11-21 02:47:48 +00:00
existingVulnerability . FixedInNodes = append ( existingVulnerability . FixedInNodes , p . Node )
addedNodes = append ( addedNodes , p . Node )
2015-11-13 19:11:28 +00:00
}
// Add notification about the FixedIn modification if the vulnerability is not new
2015-11-21 02:47:48 +00:00
if _ , newVulnerabilityNotificationExists := newVulnerabilityNotifications [ vulnerability . ID ] ; ! newVulnerabilityNotificationExists {
// Any VulnerabilityPackageChangedNotification already ?
if existingPackageNotification , _ := vulnerabilityPackageChangedNotifications [ vulnerability . ID ] ; existingPackageNotification != nil {
// There is a priority change notification, add the packages modifications to it
existingPackageNotification . AddedFixedInNodes = append ( existingPackageNotification . AddedFixedInNodes , addedNodes ... )
existingPackageNotification . RemovedFixedInNodes = append ( existingPackageNotification . RemovedFixedInNodes , removedNodes ... )
} else {
// No previous notification, just add a new one
notification := & VulnerabilityPackageChangedNotification { VulnerabilityID : vulnerability . ID , AddedFixedInNodes : addedNodes , RemovedFixedInNodes : removedNodes }
notifications = append ( notifications , notification )
vulnerabilityPackageChangedNotifications [ vulnerability . ID ] = notification
2015-11-13 19:11:28 +00:00
}
}
}
}
}
// Apply transaction
if err = store . ApplyTransaction ( t ) ; err != nil {
log . Errorf ( "failed transaction (InsertVulnerabilities): %s" , err )
return [ ] Notification { } , ErrTransaction
}
2015-11-21 02:47:48 +00:00
return notifications , nil
2015-11-13 19:11:28 +00:00
}
// DeleteVulnerability deletes the vulnerability having the given ID
func DeleteVulnerability ( id string ) error {
vulnerability , err := FindOneVulnerability ( id , FieldVulnerabilityAll )
if err != nil {
return err
}
t := cayley . NewTransaction ( )
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityID , vulnerability . ID ) )
t . RemoveQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityLink , vulnerability . Link ) )
t . RemoveQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityPriority , string ( vulnerability . Priority ) ) )
t . RemoveQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityDescription , vulnerability . Description ) )
2015-11-13 19:11:28 +00:00
for _ , p := range vulnerability . FixedInNodes {
2015-12-05 07:42:37 +00:00
t . RemoveQuad ( cayley . Triple ( vulnerability . Node , FieldVulnerabilityFixedIn , p ) )
2015-11-13 19:11:28 +00:00
}
if err := store . ApplyTransaction ( t ) ; err != nil {
log . Errorf ( "failed transaction (DeleteVulnerability): %s" , err )
return ErrTransaction
}
return nil
}
// FindOneVulnerability finds and returns a single vulnerability having the given ID selecting the specified fields
func FindOneVulnerability ( id string , selectedFields [ ] string ) ( * Vulnerability , error ) {
t := & Vulnerability { ID : id }
2015-11-20 20:03:20 +00:00
v , err := toVulnerabilities ( cayley . StartPath ( store , t . GetNode ( ) ) . Has ( fieldIs , fieldVulnerabilityIsValue ) , selectedFields )
2015-11-13 19:11:28 +00:00
if err != nil {
return nil , err
}
if len ( v ) == 1 {
return v [ 0 ] , nil
}
if len ( v ) > 1 {
log . Errorf ( "found multiple vulnerabilities with identical ID [ID: %s]" , id )
return nil , ErrInconsistent
}
return nil , cerrors . ErrNotFound
}
// FindAllVulnerabilitiesByFixedIn finds and returns all vulnerabilities that are fixed in the given packages (speficied by their nodes), selecting the specified fields
func FindAllVulnerabilitiesByFixedIn ( nodes [ ] string , selectedFields [ ] string ) ( [ ] * Vulnerability , error ) {
if len ( nodes ) == 0 {
log . Warning ( "Could not FindAllVulnerabilitiesByFixedIn with an empty nodes array." )
return [ ] * Vulnerability { } , nil
}
2015-12-01 21:57:16 +00:00
// Construct path, potentially saving FieldVulnerabilityCausedByPackage
path := cayley . StartPath ( store , nodes ... )
if utils . Contains ( FieldVulnerabilityCausedByPackage , selectedFields ) {
path = path . Save ( FieldPackageName , FieldVulnerabilityCausedByPackage )
}
path = path . In ( FieldVulnerabilityFixedIn )
return toVulnerabilities ( path , selectedFields )
2015-11-13 19:11:28 +00:00
}
// 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
2015-12-01 21:57:16 +00:00
saveFields ( path , selectedFields , [ ] string { FieldVulnerabilityFixedIn , FieldVulnerabilityCausedByPackage } )
2015-11-13 19:11:28 +00:00
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
}
2015-12-01 21:57:16 +00:00
case FieldVulnerabilityCausedByPackage :
vulnerability . CausedByPackage = store . NameOf ( tags [ FieldVulnerabilityCausedByPackage ] )
2015-11-13 19:11:28 +00:00
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
}