func layerEqual(expected, actual *Layer) bool { eq := true eq = eq && expected.Node == actual.Node eq = eq && expected.ID == actual.ID eq = eq && expected.ParentNode == actual.ParentNode eq = eq && expected.OS == actual.OS eq = eq && expected.EngineVersion == actual.EngineVersion eq = eq && len(utils.CompareStringLists(actual.SuccessorsNodes, expected.SuccessorsNodes)) == 0 && len(utils.CompareStringLists(expected.SuccessorsNodes, actual.SuccessorsNodes)) == 0 eq = eq && len(utils.CompareStringLists(actual.RemovedPackagesNodes, expected.RemovedPackagesNodes)) == 0 && len(utils.CompareStringLists(expected.RemovedPackagesNodes, actual.RemovedPackagesNodes)) == 0 eq = eq && len(utils.CompareStringLists(actual.InstalledPackagesNodes, expected.InstalledPackagesNodes)) == 0 && len(utils.CompareStringLists(expected.InstalledPackagesNodes, actual.InstalledPackagesNodes)) == 0 return eq }
// AllPackages computes the full list of packages that l has and return them as // nodes. // It requires that FieldLayerParent, FieldLayerContentInstalledPackages, // FieldLayerContentRemovedPackages fields has been selected on l func (l *Layer) AllPackages() ([]string, error) { var allPackages []string parent, err := l.Parent([]string{FieldLayerParent, FieldLayerPackages}) if err != nil { return []string{}, err } if parent != nil { allPackages, err = parent.AllPackages() if err != nil { return []string{}, err } } return append(utils.CompareStringLists(allPackages, l.RemovedPackagesNodes), l.InstalledPackagesNodes...), nil }
// detectAndInsertInstalledAndRemovedPackages finds the installed and removed // package nodes and inserts the installed packages into the database. func detectAndInsertInstalledAndRemovedPackages(detectedOS string, packageList []*database.Package, parent *database.Layer) (installedNodes, removedNodes []string, err error) { // Get the parent layer's packages. parentPackageNodes, err := parent.AllPackages() if err != nil { return nil, nil, err } parentPackages, err := database.FindAllPackagesByNodes(parentPackageNodes, []string{database.FieldPackageName, database.FieldPackageVersion}) if err != nil { return nil, nil, err } // Map detected packages (name:version) string to packages. packagesNVMapToPackage := make(map[string]*database.Package) for _, p := range packageList { packagesNVMapToPackage[p.Name+":"+p.Version.String()] = p } // Map parent's packages (name:version) string to nodes. parentPackagesNVMapToNodes := make(map[string]string) for _, p := range parentPackages { parentPackagesNVMapToNodes[p.Name+":"+p.Version.String()] = p.Node } // Build a list of the parent layer's packages' node values. var parentPackagesNV []string for _, p := range parentPackages { parentPackagesNV = append(parentPackagesNV, p.Name+":"+p.Version.String()) } // Build a list of the layer packages' node values. var layerPackagesNV []string for _, p := range packageList { layerPackagesNV = append(layerPackagesNV, p.Name+":"+p.Version.String()) } // Calculate the installed and removed packages. removedPackagesNV := utils.CompareStringLists(parentPackagesNV, layerPackagesNV) installedPackagesNV := utils.CompareStringLists(layerPackagesNV, parentPackagesNV) // Build a list of all the installed packages. var installedPackages []*database.Package for _, nv := range installedPackagesNV { p, _ := packagesNVMapToPackage[nv] p.OS = detectedOS installedPackages = append(installedPackages, p) } // Insert that list into the database. err = database.InsertPackages(installedPackages) if err != nil { return nil, nil, err } // Build the list of installed package nodes. for _, p := range installedPackages { if p.Node != "" { installedNodes = append(installedNodes, p.Node) } } // Build the list of removed package nodes. for _, nv := range removedPackagesNV { node, _ := parentPackagesNVMapToNodes[nv] removedNodes = append(removedNodes, node) } return }
// InsertVulnerabilities inserts or updates several vulnerabilities in the database in one transaction // It ensures that a vulnerability can't be fixed by two packages belonging the same Branch. // 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) 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 { // Is the vulnerability already existing ? 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 } } // Don't allow inserting/updating a vulnerability which is fixed in two packages of the same branch if len(vulnerability.FixedInNodes) > 0 { fixedInPackages, err := FindAllPackagesByNodes(vulnerability.FixedInNodes, []string{FieldPackageOS, FieldPackageName}) if err != nil { return []Notification{}, err } fixedInBranches := make(map[string]struct{}) for _, fixedInPackage := range fixedInPackages { branch := fixedInPackage.Branch() if _, branchExists := fixedInBranches[branch]; branchExists { log.Warningf("could not insert vulnerability %s because it is fixed in two packages of the same branch", vulnerability.ID) return []Notification{}, cerrors.NewBadRequestError("could not insert a vulnerability which is fixed in two packages of the same branch") } fixedInBranches[branch] = struct{}{} } } // 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() cachedVulnerabilities[vulnerability.ID] = vulnerability t.AddQuad(cayley.Quad(vulnerability.Node, FieldIs, FieldVulnerabilityIsValue, "")) t.AddQuad(cayley.Quad(vulnerability.Node, FieldVulnerabilityID, vulnerability.ID, "")) t.AddQuad(cayley.Quad(vulnerability.Node, FieldVulnerabilityLink, vulnerability.Link, "")) t.AddQuad(cayley.Quad(vulnerability.Node, FieldVulnerabilityPriority, string(vulnerability.Priority), "")) t.AddQuad(cayley.Quad(vulnerability.Node, FieldVulnerabilityDescription, vulnerability.Description, "")) for _, p := range vulnerability.FixedInNodes { t.AddQuad(cayley.Quad(vulnerability.Node, FieldVulnerabilityFixedIn, p, "")) } // Add a notification newVulnerabilityNotifications[vulnerability.ID] = &NewVulnerabilityNotification{VulnerabilityID: vulnerability.ID} } else { // The vulnerability already exists, update it if vulnerability.Link != "" && existingVulnerability.Link != vulnerability.Link { t.RemoveQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityLink, existingVulnerability.Link, "")) t.AddQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityLink, vulnerability.Link, "")) existingVulnerability.Link = vulnerability.Link } 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 vulnerabilityPriorityIncreasedNotifications[vulnerability.ID] = &VulnerabilityPriorityIncreasedNotification{OldPriority: existingPriorityNotification.OldPriority, NewPriority: vulnerability.Priority, VulnerabilityID: existingVulnerability.ID} } else { // No previous notification, just add a new one vulnerabilityPriorityIncreasedNotifications[vulnerability.ID] = &VulnerabilityPriorityIncreasedNotification{OldPriority: existingVulnerability.Priority, NewPriority: vulnerability.Priority, VulnerabilityID: existingVulnerability.ID} } } } t.RemoveQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityPriority, string(existingVulnerability.Priority), "")) t.AddQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityPriority, string(vulnerability.Priority), "")) existingVulnerability.Priority = vulnerability.Priority } if vulnerability.Description != "" && existingVulnerability.Description != vulnerability.Description { t.RemoveQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityDescription, existingVulnerability.Description, "")) t.AddQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityDescription, vulnerability.Description, "")) existingVulnerability.Description = vulnerability.Description } if len(vulnerability.FixedInNodes) > 0 && len(utils.CompareStringLists(vulnerability.FixedInNodes, existingVulnerability.FixedInNodes)) != 0 { var removedNodes []string var addedNodes []string existingVulnerabilityFixedInPackages, err := FindAllPackagesByNodes(existingVulnerability.FixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion}) if err != nil { return []Notification{}, err } vulnerabilityFixedInPackages, err := FindAllPackagesByNodes(vulnerability.FixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion}) if err != nil { return []Notification{}, err } for _, p := range vulnerabilityFixedInPackages { // Any already existing link ? fixedInLinkAlreadyExists := false for _, ep := range existingVulnerabilityFixedInPackages { if *p == *ep { // This exact link already exists, we won't insert it again fixedInLinkAlreadyExists = true } else if p.Branch() == ep.Branch() { // A link to this package branch already exist and is not the same version, we will delete it t.RemoveQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityFixedIn, ep.Node, "")) 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) } } if fixedInLinkAlreadyExists == false { t.AddQuad(cayley.Quad(existingVulnerability.Node, FieldVulnerabilityFixedIn, p.Node, "")) existingVulnerability.FixedInNodes = append(existingVulnerability.FixedInNodes, p.Node) addedNodes = append(addedNodes, p.Node) } } // Add notification about the FixedIn modification if the vulnerability is not new if len(removedNodes) > 0 || len(addedNodes) > 0 { 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 vulnerabilityPackageChangedNotifications[vulnerability.ID] = &VulnerabilityPackageChangedNotification{VulnerabilityID: vulnerability.ID, AddedFixedInNodes: addedNodes, RemovedFixedInNodes: removedNodes} } } } } } } // Apply transaction if err = store.ApplyTransaction(t); err != nil { log.Errorf("failed transaction (InsertVulnerabilities): %s", err) return []Notification{}, ErrTransaction } // Group all notifications var allNotifications []Notification for _, notification := range newVulnerabilityNotifications { allNotifications = append(allNotifications, notification) } for _, notification := range vulnerabilityPriorityIncreasedNotifications { allNotifications = append(allNotifications, notification) } for _, notification := range vulnerabilityPackageChangedNotifications { allNotifications = append(allNotifications, notification) } return allNotifications, nil }
func (n *VulnerabilityPackageChangedNotification) GetContent() (interface{}, error) { // Returns the removed and added packages as well as the layers that // introduced the vulnerability in the past but don't anymore because of the // removed packages and the layers that now introduce the vulnerability // because of the added packages // Find vulnerability. vulnerability, err := FindOneVulnerability(n.VulnerabilityID, []string{FieldVulnerabilityID, FieldVulnerabilityLink, FieldVulnerabilityPriority, FieldVulnerabilityDescription, FieldVulnerabilityFixedIn}) if err != nil { return []byte{}, err } abstractVulnerability, err := vulnerability.ToAbstractVulnerability() if err != nil { return []byte{}, err } // First part of the answer : added/removed packages addedPackages, err := FindAllPackagesByNodes(n.AddedFixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion, FieldPackagePreviousVersion}) if err != nil { return []byte{}, err } removedPackages, err := FindAllPackagesByNodes(n.RemovedFixedInNodes, []string{FieldPackageOS, FieldPackageName, FieldPackageVersion, FieldPackagePreviousVersion}) if err != nil { return []byte{}, err } // Second part of the answer var addedPackagesPreviousVersions []string for _, pkg := range addedPackages { previousVersions, err := pkg.PreviousVersions([]string{}) if err != nil { return []*Layer{}, err } for _, version := range previousVersions { addedPackagesPreviousVersions = append(addedPackagesPreviousVersions, version.Node) } } var removedPackagesPreviousVersions []string for _, pkg := range removedPackages { previousVersions, err := pkg.PreviousVersions([]string{}) if err != nil { return []*Layer{}, err } for _, version := range previousVersions { removedPackagesPreviousVersions = append(removedPackagesPreviousVersions, version.Node) } } newIntroducingLayers, err := FindAllLayersByAddedPackageNodes(addedPackagesPreviousVersions, []string{FieldLayerID}) if err != nil { return []byte{}, err } formerIntroducingLayers, err := FindAllLayersByAddedPackageNodes(removedPackagesPreviousVersions, []string{FieldLayerID}) if err != nil { return []byte{}, err } newIntroducingLayersIDs := []string{} // empty slice, not null for _, l := range newIntroducingLayers { newIntroducingLayersIDs = append(newIntroducingLayersIDs, l.ID) } formerIntroducingLayersIDs := []string{} // empty slice, not null for _, l := range formerIntroducingLayers { formerIntroducingLayersIDs = append(formerIntroducingLayersIDs, l.ID) } // Remove layers which appears both in new and former lists (eg. case of updated packages but still vulnerable) filteredNewIntroducingLayersIDs := utils.CompareStringLists(newIntroducingLayersIDs, formerIntroducingLayersIDs) filteredFormerIntroducingLayersIDs := utils.CompareStringLists(formerIntroducingLayersIDs, newIntroducingLayersIDs) return struct { Vulnerability *AbstractVulnerability AddedAffectedPackages, RemovedAffectedPackages []*AbstractPackage NewIntroducingLayersIDs, FormerIntroducingLayerIDs []string }{ Vulnerability: abstractVulnerability, AddedAffectedPackages: PackagesToAbstractPackages(addedPackages), RemovedAffectedPackages: PackagesToAbstractPackages(removedPackages), NewIntroducingLayersIDs: filteredNewIntroducingLayersIDs, FormerIntroducingLayerIDs: filteredFormerIntroducingLayersIDs, }, nil }