Merge pull request #172 from future-architect/ubuntu_bakusoku
High speed scan on Ubuntu/Debian
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,6 +3,8 @@ vuls
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
.gitmodules
 | 
			
		||||
coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -11,7 +11,7 @@
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands
 | 
			
		||||
PKGS = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands ./cache
 | 
			
		||||
 | 
			
		||||
all: test
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								README.ja.md
									
									
									
									
									
								
							@@ -301,22 +301,24 @@ $ vuls tui
 | 
			
		||||
# Performance Considerations
 | 
			
		||||
 | 
			
		||||
- Ubuntu, Debian  
 | 
			
		||||
アップデート対象のパッケージが沢山ある場合は、毎回apt-get changelogするので遅いし、スキャン対象サーバのリソースを消費する。
 | 
			
		||||
`apt-get changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。
 | 
			
		||||
アップデート対象のパッケージが沢山ある場合、チェンジログの取得に時間がかかるので、初回のスキャンは遅い。  
 | 
			
		||||
ただ、2回目以降はキャッシュしたchangelogを使うので速くなる。  
 | 
			
		||||
 | 
			
		||||
- CentOS  
 | 
			
		||||
アップデート対象すべてのchangelogを一度で取得しパースする。スキャンスピードは高速、サーバリソース消費量は小さい。
 | 
			
		||||
アップデート対象すべてのchangelogを一度で取得しパースする。スキャンスピードは速い、サーバリソース消費量は小さい。
 | 
			
		||||
 | 
			
		||||
- Amazon, RHEL and FreeBSD  
 | 
			
		||||
高速にスキャンし、スキャン対象サーバのリソース消費量は小さい。
 | 
			
		||||
 | 
			
		||||
| Distribution|         Scan Speed | Resource Usage On Target Server |
 | 
			
		||||
|:------------|:-------------------|:-------------|
 | 
			
		||||
| Ubuntu      |               Slow | Heavy            |
 | 
			
		||||
| Debian      |               Slow | Heavy            |
 | 
			
		||||
| CentOS      |               Fast | Light            |
 | 
			
		||||
| Amazon      |               Fast | Light            |
 | 
			
		||||
| RHEL        |               Fast | Light            |
 | 
			
		||||
| FreeBSD     |               Fast | Light            |
 | 
			
		||||
| Distribution|         Scan Speed | 
 | 
			
		||||
|:------------|:-------------------|
 | 
			
		||||
| Ubuntu      | 初回は遅い / 2回目以降速い |
 | 
			
		||||
| Debian      | 初回は遅い / 2回目以降速い |
 | 
			
		||||
| CentOS      |               速い |
 | 
			
		||||
| Amazon      |               速い | 
 | 
			
		||||
| RHEL        |               速い | 
 | 
			
		||||
| FreeBSD     |               速い |
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
@@ -340,7 +342,7 @@ web/app server in the same configuration under the load balancer
 | 
			
		||||
|:------------|-------------------:|
 | 
			
		||||
| Ubuntu      |          12, 14, 16|
 | 
			
		||||
| Debian      |                7, 8|
 | 
			
		||||
| RHEL        |          4, 5, 6, 7|
 | 
			
		||||
| RHEL        |                6, 7|
 | 
			
		||||
| CentOS      |             5, 6, 7|
 | 
			
		||||
| Amazon Linux|                 All|
 | 
			
		||||
| FreeBSD     |                  10|
 | 
			
		||||
@@ -595,7 +597,6 @@ prepare
 | 
			
		||||
# Usage: Scan
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
$ vuls scan -help
 | 
			
		||||
scan:
 | 
			
		||||
        scan
 | 
			
		||||
@@ -604,6 +605,7 @@ scan:
 | 
			
		||||
                [-results-dir=/path/to/results]
 | 
			
		||||
                [-cve-dictionary-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
                [-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
                [-cache-dbpath=/path/to/cache.db]
 | 
			
		||||
                [-cvss-over=7]
 | 
			
		||||
                [-ignore-unscored-cves]
 | 
			
		||||
                [-ssh-external]
 | 
			
		||||
@@ -626,7 +628,6 @@ scan:
 | 
			
		||||
                [SERVER]...
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  -ask-key-password
 | 
			
		||||
        Ask ssh privatekey password before scanning
 | 
			
		||||
  -aws-profile string
 | 
			
		||||
@@ -641,6 +642,8 @@ scan:
 | 
			
		||||
        Azure storage container name
 | 
			
		||||
  -azure-key string
 | 
			
		||||
        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
 | 
			
		||||
  -cache-dbpath string
 | 
			
		||||
        /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -cve-dictionary-dbpath string
 | 
			
		||||
@@ -649,8 +652,6 @@ scan:
 | 
			
		||||
        http://CVE.Dictionary (default "http://127.0.0.1:1323")
 | 
			
		||||
  -cvss-over float
 | 
			
		||||
        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
 | 
			
		||||
  -results-dir string
 | 
			
		||||
        /path/to/results (default "$PWD/results")
 | 
			
		||||
  -debug
 | 
			
		||||
        debug mode
 | 
			
		||||
  -debug-sql
 | 
			
		||||
@@ -671,6 +672,8 @@ scan:
 | 
			
		||||
        Send report via Slack
 | 
			
		||||
  -report-text
 | 
			
		||||
        Write report to text files ($PWD/results/current)
 | 
			
		||||
  -results-dir string
 | 
			
		||||
        /path/to/results (default "$PWD/results")
 | 
			
		||||
  -ssh-external
 | 
			
		||||
        Use external ssh command. Default: Use the Go native implementation
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								README.md
									
									
									
									
									
								
							@@ -297,25 +297,27 @@ see https://github.com/future-architect/vuls/tree/master/setup/docker
 | 
			
		||||
----
 | 
			
		||||
# Performance Considerations
 | 
			
		||||
 | 
			
		||||
- on Ubuntu and Debian  
 | 
			
		||||
- On Ubuntu and Debian  
 | 
			
		||||
Vuls issues `apt-get changelog` for each upgradable packages and parse the changelog.  
 | 
			
		||||
`apt-get changelog` is slow and resource usage is heavy when there are many updatable packages on target server.  
 | 
			
		||||
`apt-get changelog` is slow and resource usage is heavy when there are many updatable packages on target server.   
 | 
			
		||||
Vuls stores these changelogs to KVS([boltdb](https://github.com/boltdb/bolt)).  
 | 
			
		||||
From the second time on, the scan speed is fast by using the local cache.
 | 
			
		||||
 | 
			
		||||
- on CentOS  
 | 
			
		||||
- On CentOS  
 | 
			
		||||
Vuls issues `yum update --changelog` to get changelogs of upgradable packages at once and parse the changelog.  
 | 
			
		||||
Scan speed is fast and resource usage is light.  
 | 
			
		||||
 | 
			
		||||
- On Amazon, RHEL and FreeBSD  
 | 
			
		||||
High speed scan and resource usage is light because Vuls can get CVE IDs by using package manager(no need to parse a changelog).
 | 
			
		||||
 | 
			
		||||
| Distribution|         Scan Speed | Resource Usage On Target Server |
 | 
			
		||||
| Distribution|         Scan Speed | 
 | 
			
		||||
|:------------|:-------------------|:-------------|
 | 
			
		||||
| Ubuntu      |               Slow | Heavy            |
 | 
			
		||||
| Debian      |               Slow | Heavy            |
 | 
			
		||||
| CentOS      |               Fast | Light            |
 | 
			
		||||
| Amazon      |               Fast | Light            |
 | 
			
		||||
| RHEL        |               Fast | Light            |
 | 
			
		||||
| FreeBSD     |               Fast | Light            |
 | 
			
		||||
| Ubuntu      |  First time: Slow / From the second time: Fast |
 | 
			
		||||
| Debian      |  First time: Slow / From the second time: Fast |
 | 
			
		||||
| CentOS      |               Fast |
 | 
			
		||||
| Amazon      |               Fast |
 | 
			
		||||
| RHEL        |               Fast |
 | 
			
		||||
| FreeBSD     |               Fast |
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
@@ -339,7 +341,7 @@ web/app server in the same configuration under the load balancer
 | 
			
		||||
|:------------|-------------------:|
 | 
			
		||||
| Ubuntu      |          12, 14, 16|
 | 
			
		||||
| Debian      |                7, 8|
 | 
			
		||||
| RHEL        |          4, 5, 6, 7|
 | 
			
		||||
| RHEL        |                6, 7|
 | 
			
		||||
| CentOS      |             5, 6, 7|
 | 
			
		||||
| Amazon Linux|                 All|
 | 
			
		||||
| FreeBSD     |                  10|
 | 
			
		||||
@@ -603,6 +605,7 @@ scan:
 | 
			
		||||
                [-results-dir=/path/to/results]
 | 
			
		||||
                [-cve-dictionary-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
                [-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
                [-cache-dbpath=/path/to/cache.db]
 | 
			
		||||
                [-cvss-over=7]
 | 
			
		||||
                [-ignore-unscored-cves]
 | 
			
		||||
                [-ssh-external]
 | 
			
		||||
@@ -639,6 +642,8 @@ scan:
 | 
			
		||||
        Azure storage container name
 | 
			
		||||
  -azure-key string
 | 
			
		||||
        Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
 | 
			
		||||
  -cache-dbpath string
 | 
			
		||||
        /path/to/cache.db (local cache of changelog for Ubuntu/Debian) (default "$PWD/cache.db")
 | 
			
		||||
  -config string
 | 
			
		||||
        /path/to/toml (default "$PWD/config.toml")
 | 
			
		||||
  -cve-dictionary-dbpath string
 | 
			
		||||
@@ -647,8 +652,6 @@ scan:
 | 
			
		||||
        http://CVE.Dictionary (default "http://127.0.0.1:1323")
 | 
			
		||||
  -cvss-over float
 | 
			
		||||
        -cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))
 | 
			
		||||
  -results-dir string
 | 
			
		||||
        /path/to/results (default "$PWD/results")
 | 
			
		||||
  -debug
 | 
			
		||||
        debug mode
 | 
			
		||||
  -debug-sql
 | 
			
		||||
@@ -669,6 +672,8 @@ scan:
 | 
			
		||||
        Send report via Slack
 | 
			
		||||
  -report-text
 | 
			
		||||
        Write report to text files ($PWD/results/current)
 | 
			
		||||
  -results-dir string
 | 
			
		||||
        /path/to/results (default "$PWD/results")
 | 
			
		||||
  -ssh-external
 | 
			
		||||
        Use external ssh command. Default: Use the Go native implementation
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										173
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,173 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bolt holds a pointer of bolt.DB
 | 
			
		||||
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
 | 
			
		||||
type Bolt struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Log  *logrus.Entry
 | 
			
		||||
	db   *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
 | 
			
		||||
func SetupBolt(path string, l *logrus.Entry) error {
 | 
			
		||||
	l.Infof("Open boltDB: %s", path)
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b := Bolt{
 | 
			
		||||
		Path: path,
 | 
			
		||||
		Log:  l,
 | 
			
		||||
		db:   db,
 | 
			
		||||
	}
 | 
			
		||||
	if err = b.createBucketIfNotExists(metabucket); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DB = b
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Close a db.
 | 
			
		||||
func (b Bolt) Close() error {
 | 
			
		||||
	if b.db == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  CreateBucketIfNotExists creates a buket that is specified by arg.
 | 
			
		||||
func (b *Bolt) createBucketIfNotExists(name string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		_, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to create bucket: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMeta gets a Meta Information os the servername to boltdb.
 | 
			
		||||
func (b Bolt) GetMeta(serverName string) (meta Meta, found bool, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(serverName))
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			found = false
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if e := json.Unmarshal(v, &meta); e != nil {
 | 
			
		||||
			return e
 | 
			
		||||
		}
 | 
			
		||||
		found = true
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
 | 
			
		||||
func (b Bolt) EnsureBuckets(meta Meta) error {
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to marshal to JSON: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		b.Log.Debugf("Put to meta: %s", meta.Name)
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if err := bkt.Put([]byte(meta.Name), jsonBytes); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// re-create a bucket (bucket name: servername)
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		if bkt != nil {
 | 
			
		||||
			b.Log.Debugf("Delete bucket: %s", meta.Name)
 | 
			
		||||
			if err := tx.DeleteBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			b.Log.Debugf("Bucket deleted: %s", meta.Name)
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Create bucket: %s", meta.Name)
 | 
			
		||||
		if _, err := tx.CreateBucket([]byte(meta.Name)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		b.Log.Debugf("Bucket created: %s", meta.Name)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PrettyPrint is for debuging
 | 
			
		||||
func (b Bolt) PrettyPrint(meta Meta) error {
 | 
			
		||||
	return b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		v := bkt.Get([]byte(meta.Name))
 | 
			
		||||
		b.Log.Debugf("key:%s, value:%s", meta.Name, v)
 | 
			
		||||
 | 
			
		||||
		bkt = tx.Bucket([]byte(meta.Name))
 | 
			
		||||
		c := bkt.Cursor()
 | 
			
		||||
		for k, v := c.First(); k != nil; k, v = c.Next() {
 | 
			
		||||
			b.Log.Debugf("key:%s, len: %d, %s...",
 | 
			
		||||
				k, len(v), util.Truncate(string(v), 30))
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetChangelog get the changelgo of specified packName from the Bucket
 | 
			
		||||
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		v := bkt.Get([]byte(packName))
 | 
			
		||||
		if v == nil {
 | 
			
		||||
			changelog = ""
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		changelog = string(v)
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PutChangelog put the changelgo of specified packName into the Bucket
 | 
			
		||||
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			return fmt.Errorf("Faild to get Bucket: %s", servername)
 | 
			
		||||
		}
 | 
			
		||||
		if err := bkt.Put([]byte(packName), []byte(changelog)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										134
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
const servername = "server1"
 | 
			
		||||
 | 
			
		||||
var meta = Meta{
 | 
			
		||||
	Name: servername,
 | 
			
		||||
	Distro: config.Distro{
 | 
			
		||||
		Family:  "ubuntu",
 | 
			
		||||
		Release: "16.04",
 | 
			
		||||
	},
 | 
			
		||||
	Packs: []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "apt",
 | 
			
		||||
			Version: "1",
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupBolt(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	err := SetupBolt(path, log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// check if meta bucket exists
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	m, found, err := DB.GetMeta(servername)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to get meta: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		t.Errorf("Not Found in meta")
 | 
			
		||||
	}
 | 
			
		||||
	if !reflect.DeepEqual(meta, m) {
 | 
			
		||||
		t.Errorf("expected %v, actual %v", meta, m)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.Close(); err != nil {
 | 
			
		||||
		t.Errorf("Failed to close bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPutGetChangelog(t *testing.T) {
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if actual, err := DB.GetChangelog(servername, "apt"); err != nil {
 | 
			
		||||
		t.Errorf("Failed to get changelog: %s", err)
 | 
			
		||||
	} else {
 | 
			
		||||
		if actual != clog {
 | 
			
		||||
			t.Errorf("changelog is not same. e: %s, a: %s", clog, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package cache
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DB has a cache instance
 | 
			
		||||
var DB Cache
 | 
			
		||||
 | 
			
		||||
const metabucket = "changelog-meta"
 | 
			
		||||
 | 
			
		||||
// Cache is a interface of cache
 | 
			
		||||
type Cache interface {
 | 
			
		||||
	Close() error
 | 
			
		||||
	GetMeta(string) (Meta, bool, error)
 | 
			
		||||
	EnsureBuckets(Meta) error
 | 
			
		||||
	PrettyPrint(Meta) error
 | 
			
		||||
	GetChangelog(string, string) (string, error)
 | 
			
		||||
	PutChangelog(string, string, string) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Meta holds a server name, distro information of the scanned server and
 | 
			
		||||
// package information that was collected at the last scan.
 | 
			
		||||
type Meta struct {
 | 
			
		||||
	Name   string
 | 
			
		||||
	Distro config.Distro
 | 
			
		||||
	Packs  []models.PackageInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindPack search a PackageInfo
 | 
			
		||||
func (m Meta) FindPack(name string) (pack models.PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range m.Packs {
 | 
			
		||||
		if name == p.Name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return pack, false
 | 
			
		||||
}
 | 
			
		||||
@@ -47,6 +47,7 @@ type ScanCmd struct {
 | 
			
		||||
	jsonBaseDir      string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
	cacheDBPath      string
 | 
			
		||||
 | 
			
		||||
	cvssScoreOver      float64
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
@@ -89,6 +90,7 @@ func (*ScanCmd) Usage() string {
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-cve-dictionary-dbpath=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cve-dictionary-url=http://127.0.0.1:1323]
 | 
			
		||||
		[-cache-dbpath=/path/to/cache.db]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ssh-external]
 | 
			
		||||
@@ -140,6 +142,13 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		defaultURL,
 | 
			
		||||
		"http://CVE.Dictionary")
 | 
			
		||||
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cacheDBPath,
 | 
			
		||||
		"cache-dbpath",
 | 
			
		||||
		defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
		"cvss-over",
 | 
			
		||||
@@ -341,6 +350,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
	c.Conf.JSONBaseDir = p.jsonBaseDir
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDictionaryURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.CacheDBPath = p.cacheDBPath
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.SSHExternal = p.sshExternal
 | 
			
		||||
 
 | 
			
		||||
@@ -49,6 +49,7 @@ type Config struct {
 | 
			
		||||
	HTTPProxy   string `valid:"url"`
 | 
			
		||||
	JSONBaseDir string
 | 
			
		||||
	CveDBPath   string
 | 
			
		||||
	CacheDBPath string
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
@@ -69,14 +70,21 @@ func (c Config) Validate() bool {
 | 
			
		||||
	if len(c.JSONBaseDir) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.JSONBaseDir); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. jsonBaseDir: %s", c.JSONBaseDir))
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.JSONBaseDir))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CveDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. dbpath: %s", c.CveDBPath))
 | 
			
		||||
				"SQLite3 DB(Cve Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CacheDBPath) != 0 {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -230,7 +238,17 @@ type ServerInfo struct {
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
	Container       Container
 | 
			
		||||
	Family          string
 | 
			
		||||
	Distro          Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Distro has distribution info
 | 
			
		||||
type Distro struct {
 | 
			
		||||
	Family  string
 | 
			
		||||
	Release string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l Distro) String() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsContainer returns whether this ServerInfo is about container
 | 
			
		||||
 
 | 
			
		||||
@@ -186,41 +186,6 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  func (api cvedictClient) httpGet(key, url string, query map[string]string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
 | 
			
		||||
//      var body string
 | 
			
		||||
//      var errs []error
 | 
			
		||||
//      var resp *http.Response
 | 
			
		||||
//      f := func() (err error) {
 | 
			
		||||
//          req := gorequest.New().SetDebug(true).Proxy(api.httpProxy).Get(url)
 | 
			
		||||
//          for key := range query {
 | 
			
		||||
//              req = req.Query(fmt.Sprintf("%s=%s", key, query[key])).Set("Content-Type", "application/x-www-form-urlencoded")
 | 
			
		||||
//          }
 | 
			
		||||
//          pp.Println(req)
 | 
			
		||||
//          resp, body, errs = req.End()
 | 
			
		||||
//          if 0 < len(errs) || resp.StatusCode != 200 {
 | 
			
		||||
//              errChan <- fmt.Errorf("HTTP error. errs: %v, url: %s", errs, url)
 | 
			
		||||
//          }
 | 
			
		||||
//          return nil
 | 
			
		||||
//      }
 | 
			
		||||
//      notify := func(err error, t time.Duration) {
 | 
			
		||||
//          log.Warnf("Failed to get. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
//      if err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
//      }
 | 
			
		||||
//      //  resChan <- body
 | 
			
		||||
//      cveDetail := cve.CveDetail{}
 | 
			
		||||
//      if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
//          errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
//      }
 | 
			
		||||
//      resChan <- response{
 | 
			
		||||
//          key,
 | 
			
		||||
//          cveDetail,
 | 
			
		||||
//      }
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CpeName    string
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
 
 | 
			
		||||
@@ -222,14 +222,14 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="365.54366048177087" width="251.74999999999977" x="641.4999999999995" y="285.984130859375"/>
 | 
			
		||||
              <y:Geometry height="382.650146484375" width="320.0" x="575.0" y="285.984130859375"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="251.74999999999977" x="0.0" y="0.0">Vuls</y:NodeLabel>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="320.0" x="0.0" y="0.0">Vuls</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="14" bottomF="13.710652669270871" left="25" leftF="25.499999999999773" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
 | 
			
		||||
              <y:BorderInsets bottom="29" bottomF="28.63427734375" left="0" leftF="0.0" right="0" rightF="0.0" top="31" topF="30.57621256510413"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
@@ -248,7 +248,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        <node id="n4::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="798.2499999999993" y="566.4311319986981"/>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="800.0" y="575.0"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.595703125" x="18.7021484375" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
@@ -296,6 +296,24 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n4::n3">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="590.0" y="575.0"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="63.203125" x="8.3984375" y="8.8671875">Web View
 | 
			
		||||
(Vulsrepo)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
@@ -319,30 +337,6 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="70.0" width="60.5" x="699.9999999999993" y="453.02174886067706"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="48.4140625" x="6.04296875" y="25.93359375">SQLite3<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:SVGNode>
 | 
			
		||||
          <y:Geometry height="37.0" width="109.57881927490234" x="889.737640380859" y="748.5119222005209"/>
 | 
			
		||||
@@ -356,7 +350,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        </y:SVGNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
 | 
			
		||||
          <y:Geometry height="24.0" width="35.0" x="811.8264548778523" y="681.5277913411459"/>
 | 
			
		||||
@@ -379,20 +373,20 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9" yfiles.foldertype="group">
 | 
			
		||||
    <node id="n8" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="293.63460286458337" width="251.74999999999977" x="645.7499999999998" y="-51.181884765625114"/>
 | 
			
		||||
              <y:Geometry height="293.63460286458337" width="322.9999999999998" x="574.4999999999998" y="-51.181884765625114"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="251.74999999999977" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="322.9999999999998" x="0.0" y="0.0">go-cve-dictionary</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="25" bottomF="24.639889388761645" left="12" leftF="12.249999999999773" right="2" rightF="2.4999999999995453" top="50" topF="49.515869140625114"/>
 | 
			
		||||
              <y:BorderInsets bottom="27" bottomF="27.139889388761617" left="37" leftF="37.249999999999886" right="2" rightF="2.4999999999995453" top="50" topF="49.515869140625114"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
@@ -407,12 +401,12 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n9:">
 | 
			
		||||
        <node id="n9::n0">
 | 
			
		||||
      <graph edgedefault="directed" id="n8:">
 | 
			
		||||
        <node id="n8::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="70.0" width="60.5" x="809.75" y="132.8128287101966"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:Geometry height="70.0" width="60.5" x="779.75" y="130.31282871019664"/>
 | 
			
		||||
              <y:Fill color="#99CCFF" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
                <y:LabelModel>
 | 
			
		||||
@@ -432,10 +426,10 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n9::n1">
 | 
			
		||||
        <node id="n8::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="101.0" x="672.9999999999995" y="148.4841308593749"/>
 | 
			
		||||
              <y:Geometry height="50.0" width="101.0" x="626.7499999999997" y="142.23413085937491"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="73.943359375" x="13.5283203125" y="15.93359375">HTTP server<y:LabelModel>
 | 
			
		||||
@@ -449,7 +443,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n9::n2">
 | 
			
		||||
        <node id="n8::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="800.0" y="35.0"/>
 | 
			
		||||
@@ -468,7 +462,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10" yfiles.foldertype="group">
 | 
			
		||||
    <node id="n9" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
@@ -496,8 +490,8 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n10:">
 | 
			
		||||
        <node id="n10::n0">
 | 
			
		||||
      <graph edgedefault="directed" id="n9:">
 | 
			
		||||
        <node id="n9::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="433.22635904947913"/>
 | 
			
		||||
@@ -514,7 +508,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n10::n1">
 | 
			
		||||
        <node id="n9::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="333.3980916341144"/>
 | 
			
		||||
@@ -534,7 +528,7 @@ Container<y:LabelModel>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11" yfiles.foldertype="group">
 | 
			
		||||
    <node id="n10" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
@@ -562,8 +556,8 @@ Container<y:LabelModel>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n11:">
 | 
			
		||||
        <node id="n11::n0">
 | 
			
		||||
      <graph edgedefault="directed" id="n10:">
 | 
			
		||||
        <node id="n10::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="553.0387573242189"/>
 | 
			
		||||
@@ -580,7 +574,7 @@ Container<y:LabelModel>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n11::n1">
 | 
			
		||||
        <node id="n10::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1032.6223485469814" y="566.4311319986981"/>
 | 
			
		||||
@@ -599,7 +593,89 @@ Container<y:LabelModel>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n9::n2" target="n0">
 | 
			
		||||
    <node id="n11" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="108.94242662355293" width="124.0" x="649.0" y="434.05757337644707"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="124.0" x="0.0" y="0.0">results dir</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 7</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n11:">
 | 
			
		||||
        <node id="n11::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="664.0" y="470.72358900144707"/>
 | 
			
		||||
              <y:Fill color="#99CCFF" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n11::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="671.0" y="480.2981186685961"/>
 | 
			
		||||
              <y:Fill color="#99CCFF" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n11::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="682.0" y="492.0"/>
 | 
			
		||||
              <y:Fill color="#99CCFF" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n8::n2" target="n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -636,17 +712,17 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n4::n2" target="n9::n1">
 | 
			
		||||
    <edge id="e2" source="n4::n2" target="n8::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="34.626953125" x="-70.78547462732604" y="-104.38485166353485">HTTP<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="241.78515625" x="-130.8343747008911" y="-104.32744691064147">HTTP or --cve-dictoianry-dbpath option<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.6418953379495804" segment="-1"/>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.6111993328569665" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -678,7 +754,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="31.802734375" x="-23.436084747315135" y="96.06095129235564">send<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="31.802734375" x="-24.439868927002635" y="91.6240450195761">send<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -696,7 +772,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.201171875" x="39.557346848955035" y="107.80743708610783">Generate<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="56.201171875" x="41.04640068840672" y="114.35365985463022">Generate<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -714,11 +790,11 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="109.7265625" x="-125.3179906251471" y="-50.63345557215132">Detail Information<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="140.76953125" x="-148.83947500014722" y="-53.63345557215143">View Detail Information<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="58.40488430810431" distanceToCenter="true" position="left" ratio="0.3174763616620919" segment="-1"/>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="65.44983715439666" distanceToCenter="true" position="left" ratio="0.3605218238342404" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -726,7 +802,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n4::n2" target="n11::n0">
 | 
			
		||||
    <edge id="e6" source="n4::n2" target="n10::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="87.9098606499872" sy="24.54915453361525" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -744,7 +820,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n4::n2" target="n10::n0">
 | 
			
		||||
    <edge id="e7" source="n4::n2" target="n9::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -762,7 +838,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n10::e0" source="n10::n0" target="n10::n1">
 | 
			
		||||
    <edge id="n9::e0" source="n9::n0" target="n9::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -780,7 +856,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n10::n1" target="n3">
 | 
			
		||||
    <edge id="e8" source="n9::n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -790,7 +866,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n11::n0" target="n3">
 | 
			
		||||
    <edge id="e9" source="n10::n0" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -800,13 +876,13 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n9::e0" source="n9::n2" target="n9::n0">
 | 
			
		||||
    <edge id="n8::e0" source="n8::n2" target="n8::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.10546875" x="-48.552734375" y="14.840008105098306">Insert<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.10546875" x="-56.200249246840485" y="13.59000810509832">Insert<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -818,44 +894,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n4::n2" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="70.275390625" x="-75.4169220517914" y="5.2924810128525905">Insert
 | 
			
		||||
Scan Result<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n6" target="n4::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.9375" x="-48.96875000000057" y="19.334788004557254">Select<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.5" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n4::n0" target="n7">
 | 
			
		||||
    <edge id="e10" source="n4::n0" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -865,7 +904,7 @@ Scan Result<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n7" target="n5">
 | 
			
		||||
    <edge id="e11" source="n6" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -883,18 +922,18 @@ Scan Result<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n4::n2" target="n9::n0">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="e12" source="n4::n2" target="n8::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="191.283203125" x="39.258457373248575" y="-102.36487019599565">--cve-dictoianry-dbpath option<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="4.0" x="116.345248769779" y="-97.77053302962645">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="99.7428628309468" distanceToCenter="true" position="right" ratio="0.749331808189429" segment="-1"/>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="99.7428628309468" distanceToCenter="true" position="right" ratio="0.7403938917830506" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
@@ -902,14 +941,13 @@ Scan Result<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n9::e1" source="n9::n1" target="n9::n0">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
    <edge id="n8::e1" source="n8::n1" target="n8::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.9375" x="0.8643523609480326" y="20.003628886299452">Select<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="37.9375" x="7.031249999999773" y="20.56044056577005">Select<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -921,6 +959,48 @@ Scan Result<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n4::n2" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n11" target="n4::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e15" source="n5" target="n4::n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e16" source="n11" target="n4::n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources>
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB  | 
@@ -72,7 +72,7 @@ FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Get upgradable packages
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -222,7 +222,7 @@ Reporting<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="271.369140625" x="-1.6845703124999432" y="11.8671875">Get all changelogs by using package manager
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" textColor="#000000" visible="true" width="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -397,7 +397,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n1" target="n12">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -416,7 +415,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -427,7 +425,6 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n13" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										24
									
								
								scan/base.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								scan/base.go
									
									
									
									
									
								
							@@ -32,9 +32,8 @@ import (
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
	ServerInfo config.ServerInfo
 | 
			
		||||
	Distro     config.Distro
 | 
			
		||||
 | 
			
		||||
	Family   string
 | 
			
		||||
	Release  string
 | 
			
		||||
	Platform models.Platform
 | 
			
		||||
	osPackages
 | 
			
		||||
 | 
			
		||||
@@ -54,13 +53,20 @@ func (l base) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setDistributionInfo(fam, rel string) {
 | 
			
		||||
	l.Family = fam
 | 
			
		||||
	l.Release = rel
 | 
			
		||||
func (l *base) setDistro(fam, rel string) {
 | 
			
		||||
	d := config.Distro{
 | 
			
		||||
		Family:  fam,
 | 
			
		||||
		Release: rel,
 | 
			
		||||
	}
 | 
			
		||||
	l.Distro = d
 | 
			
		||||
 | 
			
		||||
	s := l.getServerInfo()
 | 
			
		||||
	s.Distro = d
 | 
			
		||||
	l.setServerInfo(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getDistributionInfo() string {
 | 
			
		||||
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
			
		||||
func (l base) getDistro() config.Distro {
 | 
			
		||||
	return l.Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) setPlatform(p models.Platform) {
 | 
			
		||||
@@ -250,8 +256,8 @@ func (l *base) convertToModel() (models.ScanResult, error) {
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
		Family:      l.Family,
 | 
			
		||||
		Release:     l.Release,
 | 
			
		||||
		Family:      l.Distro.Family,
 | 
			
		||||
		Release:     l.Distro.Release,
 | 
			
		||||
		Container:   container,
 | 
			
		||||
		Platform:    l.Platform,
 | 
			
		||||
		KnownCves:   scoredCves,
 | 
			
		||||
 
 | 
			
		||||
@@ -70,8 +70,9 @@ func TestIsAwsInstanceID(t *testing.T) {
 | 
			
		||||
		{"no data", false},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := isAwsInstanceID(tt.in)
 | 
			
		||||
		actual := r.isAwsInstanceID(tt.in)
 | 
			
		||||
		if tt.expected != actual {
 | 
			
		||||
			t.Errorf("expected %t, actual %t, str: %s", tt.expected, actual, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										227
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						
									
										227
									
								
								scan/debian.go
									
									
									
									
									
								
							@@ -24,6 +24,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
@@ -39,6 +40,7 @@ type debian struct {
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -46,7 +48,6 @@ func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
			
		||||
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err error) {
 | 
			
		||||
	deb = newDebian(c)
 | 
			
		||||
	deb.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
			
		||||
		if r.Error != nil {
 | 
			
		||||
@@ -69,12 +70,12 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
			
		||||
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -90,10 +91,10 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		if len(result) == 0 {
 | 
			
		||||
			Log.Warnf(
 | 
			
		||||
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
 | 
			
		||||
			deb.setDistributionInfo("debian/ubuntu", "unknown")
 | 
			
		||||
			deb.setDistro("debian/ubuntu", "unknown")
 | 
			
		||||
		} else {
 | 
			
		||||
			distro := strings.ToLower(trim(result[1]))
 | 
			
		||||
			deb.setDistributionInfo(distro, trim(result[2]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
		}
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
@@ -101,7 +102,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := sshExec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistributionInfo("debian", trim(r.Stdout))
 | 
			
		||||
		deb.setDistro("debian", trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -133,7 +134,7 @@ func (o *debian) install() error {
 | 
			
		||||
		return fmt.Errorf(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
	if o.Distro.Family == "debian" {
 | 
			
		||||
		// install aptitude
 | 
			
		||||
		cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
 | 
			
		||||
		if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
@@ -208,7 +209,7 @@ func (o *debian) parseScannedPackagesLine(line string) (name, version string, er
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	if o.Family == "debian" {
 | 
			
		||||
	if o.Distro.Family == "debian" {
 | 
			
		||||
		if r := o.ssh("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("aptitude is not installed: %s", r)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
@@ -218,9 +219,8 @@ func (o *debian) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//TODO return whether already expired.
 | 
			
		||||
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
 | 
			
		||||
	//  cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.ssh(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
@@ -241,12 +241,21 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	current := cache.Meta{
 | 
			
		||||
		Name:   o.getServerInfo().ServerName,
 | 
			
		||||
		Distro: o.getServerInfo().Distro,
 | 
			
		||||
		Packs:  unsecurePacks,
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("Ensure changelog cache: %s", current.Name)
 | 
			
		||||
	if err := o.ensureChangelogCache(current); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect CVE information of upgradable packages
 | 
			
		||||
	cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -256,63 +265,61 @@ func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInf
 | 
			
		||||
	return cvePacksInfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	resChan := make(chan models.PackageInfo, len(packs))
 | 
			
		||||
	errChan := make(chan error, len(packs))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range packs {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
func (o *debian) ensureChangelogCache(current cache.Meta) error {
 | 
			
		||||
	// Search from cache
 | 
			
		||||
	old, found, err := cache.DB.GetMeta(current.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to get meta. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	if !found {
 | 
			
		||||
		o.log.Debugf("Not found in meta: %s", current.Name)
 | 
			
		||||
		err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(5 * 60 * time.Second)
 | 
			
		||||
	concurrency := 5
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range packs {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					cmd := fmt.Sprintf("LANG=en_US.UTF-8 apt-cache policy %s", p.Name)
 | 
			
		||||
					r := o.ssh(cmd, sudo)
 | 
			
		||||
					if !r.isSuccess() {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to SSH: %s.", r)
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
					ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						errChan <- fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
					}
 | 
			
		||||
					p.NewVersion = ver.Candidate
 | 
			
		||||
					resChan <- p
 | 
			
		||||
				}(pack)
 | 
			
		||||
	} else {
 | 
			
		||||
		if current.Distro.Family != old.Distro.Family ||
 | 
			
		||||
			current.Distro.Release != old.Distro.Release {
 | 
			
		||||
			o.log.Debugf("Need to refesh meta: %s", current.Name)
 | 
			
		||||
			err = cache.DB.EnsureBuckets(current)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to ensure buckets. err: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			o.log.Debugf("Reuse meta: %s", current.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	result := []models.PackageInfo{}
 | 
			
		||||
	for i := 0; i < len(packs); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pack := <-resChan:
 | 
			
		||||
			result = append(result, pack)
 | 
			
		||||
			o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
 | 
			
		||||
				i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, fmt.Errorf("Timeout fillCandidateVersion")
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		cache.DB.PrettyPrint(current)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []models.PackageInfo, err error) {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, p := range before {
 | 
			
		||||
		names = append(names, p.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd := fmt.Sprintf("LANG=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
 | 
			
		||||
	r := o.ssh(cmd, sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s.", r)
 | 
			
		||||
	}
 | 
			
		||||
	packChangelog := o.splitAptCachePolicy(r.Stdout)
 | 
			
		||||
	for k, v := range packChangelog {
 | 
			
		||||
		ver, err := o.parseAptCachePolicy(v, k)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		p, found := before.FindByName(k)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Not found: %s", k)
 | 
			
		||||
		}
 | 
			
		||||
		p.NewVersion = ver.Candidate
 | 
			
		||||
		filled = append(filled, p)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return nil, fmt.Errorf("%v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
@@ -369,9 +376,11 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
	meta := cache.Meta{
 | 
			
		||||
		Name:   o.getServerInfo().ServerName,
 | 
			
		||||
		Distro: o.getServerInfo().Distro,
 | 
			
		||||
		Packs:  unsecurePacks,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type strarray []string
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
@@ -398,6 +407,18 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
					changelog := o.getChangelogCache(meta, p)
 | 
			
		||||
					if 0 < len(changelog) {
 | 
			
		||||
						cveIDs := o.getCveIDFromChangelog(changelog, p.Name, p.Version)
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							strarray
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// if the changelog is not in cache or failed to get from local cache,
 | 
			
		||||
					// get the changelog of the package via internet.
 | 
			
		||||
					if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
@@ -411,6 +432,8 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { CVE ID: [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[string][]models.PackageInfo)
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(unsecurePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
@@ -429,7 +452,6 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
			return nil, fmt.Errorf("Timeout scanPackageCveIDs")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(errs) {
 | 
			
		||||
		return nil, fmt.Errorf("%v", errs)
 | 
			
		||||
	}
 | 
			
		||||
@@ -438,7 +460,6 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
	for k := range cvePackages {
 | 
			
		||||
		cveIDs = append(cveIDs, k)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
 | 
			
		||||
 | 
			
		||||
	o.log.Info("Fetching CVE details...")
 | 
			
		||||
@@ -459,9 +480,32 @@ func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePac
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getChangelogCache(meta cache.Meta, pack models.PackageInfo) string {
 | 
			
		||||
	cachedPack, found := meta.FindPack(pack.Name)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if cachedPack.NewVersion != pack.NewVersion {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	changelog, err := cache.DB.GetChangelog(meta.Name, pack.Name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Warnf("Failed to get chnagelog. bucket: %s, key:%s, err: %s",
 | 
			
		||||
			meta.Name, pack.Name, err)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	if len(changelog) == 0 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Cache hit: %s, len: %d, %s...",
 | 
			
		||||
		meta.Name, len(changelog), util.Truncate(changelog, 30))
 | 
			
		||||
	return changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
@@ -476,36 +520,38 @@ func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]string, error) {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
	err := cache.DB.PutChangelog(o.getServerInfo().ServerName, pack.Name, r.Stdout)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to put changelog into cache")
 | 
			
		||||
	}
 | 
			
		||||
	// No error will be returned. Only logging.
 | 
			
		||||
	return o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
 | 
			
		||||
	return o.getCveIDFromChangelog(r.Stdout, pack.Name, pack.Version), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getCveIDParsingChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) (cveIDs []string, err error) {
 | 
			
		||||
func (o *debian) getCveIDFromChangelog(changelog string,
 | 
			
		||||
	packName string, versionOrLater string) []string {
 | 
			
		||||
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	if cveIDs, err := o.parseChangelog(changelog, packName, versionOrLater); err == nil {
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ver := strings.Split(versionOrLater, "ubuntu")[0]
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
	if cveIDs, err := o.parseChangelog(changelog, packName, ver); err == nil {
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(versionOrLater, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		ver = splittedByColon[1]
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs, err = o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	cveIDs, err := o.parseChangelog(changelog, packName, ver)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return
 | 
			
		||||
		return cveIDs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only logging the error.
 | 
			
		||||
	o.log.Error(err)
 | 
			
		||||
	return []string{}, nil
 | 
			
		||||
	return []string{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
@@ -538,6 +584,29 @@ func (o *debian) parseChangelog(changelog string,
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
 | 
			
		||||
	//  re := regexp.MustCompile(`(?m:^[^ \t]+:$)`)
 | 
			
		||||
	re := regexp.MustCompile(`(?m:^[^ \t]+:\r\n)`)
 | 
			
		||||
	ii := re.FindAllStringIndex(stdout, -1)
 | 
			
		||||
	ri := []int{}
 | 
			
		||||
	for i := len(ii) - 1; 0 <= i; i-- {
 | 
			
		||||
		ri = append(ri, ii[i][0])
 | 
			
		||||
	}
 | 
			
		||||
	splitted := []string{}
 | 
			
		||||
	lasti := len(stdout)
 | 
			
		||||
	for _, i := range ri {
 | 
			
		||||
		splitted = append(splitted, stdout[i:lasti])
 | 
			
		||||
		lasti = i
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packChangelog := map[string]string{}
 | 
			
		||||
	for _, r := range splitted {
 | 
			
		||||
		packName := r[:strings.Index(r, ":")]
 | 
			
		||||
		packChangelog[packName] = r
 | 
			
		||||
	}
 | 
			
		||||
	return packChangelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type packCandidateVer struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Installed string
 | 
			
		||||
 
 | 
			
		||||
@@ -18,10 +18,14 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -183,7 +187,7 @@ util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		actual := d.getCveIDFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(actual) != len(tt.expected) {
 | 
			
		||||
			t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
@@ -195,13 +199,6 @@ util-linux (2.26.2-6) unstable; urgency=medium`,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Errorf("Returning error is unexpected")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetUpdatablePackNames(t *testing.T) {
 | 
			
		||||
@@ -520,6 +517,95 @@ Calculating upgrade... Done
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
	const servername = "server1"
 | 
			
		||||
	pack := models.PackageInfo{
 | 
			
		||||
		Name:       "apt",
 | 
			
		||||
		Version:    "1.0.0",
 | 
			
		||||
		NewVersion: "1.0.1",
 | 
			
		||||
	}
 | 
			
		||||
	var meta = cache.Meta{
 | 
			
		||||
		Name: servername,
 | 
			
		||||
		Distro: config.Distro{
 | 
			
		||||
			Family:  "ubuntu",
 | 
			
		||||
			Release: "16.04",
 | 
			
		||||
		},
 | 
			
		||||
		Packs: []models.PackageInfo{pack},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	if err := cache.SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.Remove(path)
 | 
			
		||||
 | 
			
		||||
	if err := cache.DB.EnsureBuckets(meta); err != nil {
 | 
			
		||||
		t.Errorf("Failed to ensure buckets: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	actual := d.getChangelogCache(meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("Failed to get empty stirng from cache:")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	if err := cache.DB.PutChangelog(servername, "apt", clog); err != nil {
 | 
			
		||||
		t.Errorf("Failed to put changelog: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual = d.getChangelogCache(meta, pack)
 | 
			
		||||
	if actual != clog {
 | 
			
		||||
		t.Errorf("Failed to get changelog from cache: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// increment a version of the pack
 | 
			
		||||
	pack.NewVersion = "1.0.2"
 | 
			
		||||
	actual = d.getChangelogCache(meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// change a name of the pack
 | 
			
		||||
	pack.Name = "bash"
 | 
			
		||||
	actual = d.getChangelogCache(meta, pack)
 | 
			
		||||
	if actual != "" {
 | 
			
		||||
		t.Errorf("The changelog is not invalidated: %s", actual)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSplitAptCachePolicy(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		stdout   string
 | 
			
		||||
		expected map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		// This function parse apt-cache policy by using Regexp multi-line mode.
 | 
			
		||||
		// So, test data includes "\r\n"
 | 
			
		||||
		{
 | 
			
		||||
			"apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\napt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\nbase-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
			map[string]string{
 | 
			
		||||
				"apt": "apt:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"apt-utils": "apt-utils:\r\n  Installed: 1.2.6\r\n  Candidate: 1.2.12~ubuntu16.04.1\r\n  Version table:\r\n     1.2.12~ubuntu16.04.1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     1.2.10ubuntu1 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 1.2.6 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
 | 
			
		||||
				"base-files": "base-files:\r\n  Installed: 9.4ubuntu3\r\n  Candidate: 9.4ubuntu4.2\r\n  Version table:\r\n     9.4ubuntu4.2 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial-updates/main amd64 Packages\r\n     9.4ubuntu4 500\r\n        500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages\r\n *** 9.4ubuntu3 100\r\n        100 /var/lib/dpkg/status\r\n",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := d.splitAptCachePolicy(tt.stdout)
 | 
			
		||||
		if !reflect.DeepEqual(tt.expected, actual) {
 | 
			
		||||
			e := pp.Sprintf("%v", tt.expected)
 | 
			
		||||
			a := pp.Sprintf("%v", actual)
 | 
			
		||||
			t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseAptCachePolicy(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,20 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -19,18 +36,18 @@ type bsd struct {
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/freebsd.rb
 | 
			
		||||
func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	bsd = newBsd(c)
 | 
			
		||||
	c.Family = "FreeBSD"
 | 
			
		||||
	if r := sshExec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "FreeBSD") == true {
 | 
			
		||||
			if b := sshExec(c, "uname -r", noSudo); b.isSuccess() {
 | 
			
		||||
				bsd.setDistributionInfo("FreeBSD", strings.TrimSpace(b.Stdout))
 | 
			
		||||
				bsd.setServerInfo(c)
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro("FreeBSD", rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,16 +42,16 @@ type redhat struct {
 | 
			
		||||
func newRedhat(c config.ServerInfo) *redhat {
 | 
			
		||||
	r := &redhat{}
 | 
			
		||||
	r.log = util.NewCustomLogger(c)
 | 
			
		||||
	r.setServerInfo(c)
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
 | 
			
		||||
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
	red = newRedhat(c)
 | 
			
		||||
	red.setServerInfo(c)
 | 
			
		||||
 | 
			
		||||
	if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
 | 
			
		||||
		red.setDistributionInfo("fedora", "unknown")
 | 
			
		||||
		red.setDistro("fedora", "unknown")
 | 
			
		||||
		Log.Warn("Fedora not tested yet: %s", r)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
@@ -72,9 +72,9 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
			release := result[2]
 | 
			
		||||
			switch strings.ToLower(result[1]) {
 | 
			
		||||
			case "centos", "centos linux":
 | 
			
		||||
				red.setDistributionInfo("centos", release)
 | 
			
		||||
				red.setDistro("centos", release)
 | 
			
		||||
			default:
 | 
			
		||||
				red.setDistributionInfo("rhel", release)
 | 
			
		||||
				red.setDistro("rhel", release)
 | 
			
		||||
			}
 | 
			
		||||
			return true, red
 | 
			
		||||
		}
 | 
			
		||||
@@ -90,7 +90,7 @@ func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
 | 
			
		||||
				release = fields[4]
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		red.setDistributionInfo(family, release)
 | 
			
		||||
		red.setDistro(family, release)
 | 
			
		||||
		return true, red
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -113,7 +113,7 @@ func (o *redhat) checkIfSudoNoPasswd() error {
 | 
			
		||||
// CentOS 7 ... yum-plugin-changelog
 | 
			
		||||
// RHEL, Amazon ... no additinal packages needed
 | 
			
		||||
func (o *redhat) install() error {
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "rhel", "amazon":
 | 
			
		||||
		o.log.Infof("Nothing to do")
 | 
			
		||||
		return nil
 | 
			
		||||
@@ -123,14 +123,13 @@ func (o *redhat) install() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) installYumChangelog() error {
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		if 0 < len(o.Distro.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
				o.Family, o.Release)
 | 
			
		||||
				"Not implemented yet: %s", o.Distro)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var packName = ""
 | 
			
		||||
@@ -157,12 +156,12 @@ func (o *redhat) installYumChangelog() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) checkRequiredPackagesInstalled() error {
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		var majorVersion int
 | 
			
		||||
		if 0 < len(o.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
		if 0 < len(o.Distro.Release) {
 | 
			
		||||
			majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
		} else {
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
 | 
			
		||||
			msg := fmt.Sprintf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
@@ -240,7 +239,7 @@ func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, erro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
 | 
			
		||||
	if o.Family != "centos" {
 | 
			
		||||
	if o.Distro.Family != "centos" {
 | 
			
		||||
		// Amazon, RHEL has yum updateinfo as default
 | 
			
		||||
		// yum updateinfo can collenct vendor advisory information.
 | 
			
		||||
		return o.scanUnsecurePackagesUsingYumPluginSecurity()
 | 
			
		||||
@@ -460,12 +459,10 @@ func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo
 | 
			
		||||
 | 
			
		||||
func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) {
 | 
			
		||||
	var majorVersion int
 | 
			
		||||
	if 0 < len(o.Release) && o.Family == "centos" {
 | 
			
		||||
		majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
 | 
			
		||||
	if 0 < len(o.Distro.Release) && o.Distro.Family == "centos" {
 | 
			
		||||
		majorVersion, _ = strconv.Atoi(strings.Split(o.Distro.Release, ".")[0])
 | 
			
		||||
	} else {
 | 
			
		||||
		return nil, fmt.Errorf(
 | 
			
		||||
			"Not implemented yet. family: %s, release: %s",
 | 
			
		||||
			o.Family, o.Release)
 | 
			
		||||
		return nil, fmt.Errorf("Not implemented yet: %s", o.getDistro())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	orglines := strings.Split(allChangelog, "\n")
 | 
			
		||||
@@ -569,7 +566,7 @@ type distroAdvisoryCveIDs struct {
 | 
			
		||||
// Scaning unsecure packages using yum-plugin-security.
 | 
			
		||||
// Amazon, RHEL
 | 
			
		||||
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
 | 
			
		||||
	if o.Family == "centos" {
 | 
			
		||||
	if o.Distro.Family == "centos" {
 | 
			
		||||
		// CentOS has no security channel.
 | 
			
		||||
		// So use yum check-update && parse changelog
 | 
			
		||||
		return CvePacksList{}, fmt.Errorf(
 | 
			
		||||
@@ -717,7 +714,7 @@ func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveID
 | 
			
		||||
 | 
			
		||||
		switch sectionState {
 | 
			
		||||
		case Header:
 | 
			
		||||
			switch o.Family {
 | 
			
		||||
			switch o.Distro.Family {
 | 
			
		||||
			case "centos":
 | 
			
		||||
				// CentOS has no security channel.
 | 
			
		||||
				// So use yum check-update && parse changelog
 | 
			
		||||
@@ -964,7 +961,7 @@ func (o *redhat) clone() osTypeInterface {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *redhat) sudo() bool {
 | 
			
		||||
	switch o.Family {
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		return false
 | 
			
		||||
	default:
 | 
			
		||||
 
 | 
			
		||||
@@ -451,7 +451,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-09-04")
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "redhat"
 | 
			
		||||
	r.Distro = config.Distro{Family: "redhat"}
 | 
			
		||||
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
@@ -511,7 +511,7 @@ Description : The Berkeley Internet Name Domain (BIND) is an implementation of
 | 
			
		||||
func TestParseYumUpdateinfoAmazon(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amazon"
 | 
			
		||||
	r.Distro = config.Distro{Family: "redhat"}
 | 
			
		||||
 | 
			
		||||
	issued, _ := time.Parse("2006-01-02", "2015-12-15")
 | 
			
		||||
	updated, _ := time.Parse("2006-01-02", "2015-12-16")
 | 
			
		||||
@@ -601,7 +601,7 @@ Description : Package updates are available for Amazon Linux AMI that fix the
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLines(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "centos"
 | 
			
		||||
	r.Distro = config.Distro{Family: "centos"}
 | 
			
		||||
	stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
 | 
			
		||||
Loading mirror speeds from cached hostfile
 | 
			
		||||
 * base: mirror.fairway.ne.jp
 | 
			
		||||
@@ -709,7 +709,7 @@ bind-utils.x86_64                       30:9.3.6-25.P1.el5_11.8          updates
 | 
			
		||||
 | 
			
		||||
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "amazon"
 | 
			
		||||
	r.Distro = config.Distro{Family: "amazon"}
 | 
			
		||||
	stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
 | 
			
		||||
34 package(s) needed for security, out of 71 available
 | 
			
		||||
 | 
			
		||||
@@ -1110,8 +1110,10 @@ func TestGetChangelogCVELines(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r := newRedhat(config.ServerInfo{})
 | 
			
		||||
	r.Family = "centos"
 | 
			
		||||
	r.Release = "6.7"
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "6.7",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos6 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos6)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -1194,7 +1196,10 @@ func TestGetChangelogCVELines(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Release = "5.6"
 | 
			
		||||
	r.Distro = config.Distro{
 | 
			
		||||
		Family:  "centos",
 | 
			
		||||
		Release: "5.6",
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range testsCentos5 {
 | 
			
		||||
		rpm2changelog, err := r.parseAllChangelog(stdoutCentos5)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,20 @@
 | 
			
		||||
/* Vuls - Vulnerability Scanner
 | 
			
		||||
Copyright (C) 2016  Future Architect, Inc. Japan.
 | 
			
		||||
 | 
			
		||||
This program is free software: you can redistribute it and/or modify
 | 
			
		||||
it under the terms of the GNU General Public License as published by
 | 
			
		||||
the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
(at your option) any later version.
 | 
			
		||||
 | 
			
		||||
This program is distributed in the hope that it will be useful,
 | 
			
		||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
You should have received a copy of the GNU General Public License
 | 
			
		||||
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -5,6 +22,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/cache"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
@@ -20,8 +38,9 @@ type osTypeInterface interface {
 | 
			
		||||
	setServerInfo(config.ServerInfo)
 | 
			
		||||
	getServerInfo() config.ServerInfo
 | 
			
		||||
 | 
			
		||||
	setDistributionInfo(string, string)
 | 
			
		||||
	getDistributionInfo() string
 | 
			
		||||
	setDistro(string, string)
 | 
			
		||||
	getDistro() config.Distro
 | 
			
		||||
	//  getFamily() string
 | 
			
		||||
 | 
			
		||||
	checkIfSudoNoPasswd() error
 | 
			
		||||
	detectPlatform() error
 | 
			
		||||
@@ -188,7 +207,7 @@ func detectServerOSes() (sshAbleOses []osTypeInterface) {
 | 
			
		||||
				Log.Infof("(%d/%d) Detected: %s: %s",
 | 
			
		||||
					i+1, len(config.Conf.Servers),
 | 
			
		||||
					res.getServerInfo().ServerName,
 | 
			
		||||
					res.getDistributionInfo())
 | 
			
		||||
					res.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting servers"
 | 
			
		||||
@@ -248,7 +267,7 @@ func detectContainerOSes() (actives []osTypeInterface) {
 | 
			
		||||
				}
 | 
			
		||||
				oses = append(oses, res...)
 | 
			
		||||
				Log.Infof("Detected: %s@%s: %s",
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistributionInfo())
 | 
			
		||||
					sinfo.Container.Name, sinfo.ServerName, osi.getDistro())
 | 
			
		||||
			}
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			msg := "Timed out while detecting containers"
 | 
			
		||||
@@ -417,6 +436,13 @@ func Scan() []error {
 | 
			
		||||
		return errs
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := setupCangelogCache(); err != nil {
 | 
			
		||||
		return []error{err}
 | 
			
		||||
	}
 | 
			
		||||
	if cache.DB != nil {
 | 
			
		||||
		defer cache.DB.Close()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Log.Info("Scanning vulnerable OS packages...")
 | 
			
		||||
	if errs := scanPackages(); errs != nil {
 | 
			
		||||
		return errs
 | 
			
		||||
@@ -429,6 +455,23 @@ func Scan() []error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setupCangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case "ubuntu", "debian":
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
		if err := cache.SetupBolt(config.Conf.CacheDBPath, Log); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkRequiredPackagesInstalled() []error {
 | 
			
		||||
	timeoutSec := 30 * 60
 | 
			
		||||
	return parallelSSHExec(func(o osTypeInterface) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -302,7 +302,7 @@ func decolateCmd(c conf.ServerInfo, cmd string, sudo bool) string {
 | 
			
		||||
		cmd = strings.Replace(cmd, "|", "| sudo ", -1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Family != "FreeBSD" {
 | 
			
		||||
	if c.Distro.Family != "FreeBSD" {
 | 
			
		||||
		// set pipefail option. Bash only
 | 
			
		||||
		// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
 | 
			
		||||
		cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								util/util.go
									
									
									
									
									
								
							@@ -124,3 +124,14 @@ func PrependProxyEnv(cmd string) string {
 | 
			
		||||
//      }
 | 
			
		||||
//      return time.Unix(i, 0), nil
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Truncate truncates string to the length
 | 
			
		||||
func Truncate(str string, length int) string {
 | 
			
		||||
	if length < 0 {
 | 
			
		||||
		return str
 | 
			
		||||
	}
 | 
			
		||||
	if length <= len(str) {
 | 
			
		||||
		return str[:length]
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -131,3 +131,43 @@ func TestPrependHTTPProxyEnv(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestTruncate(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in     string
 | 
			
		||||
		length int
 | 
			
		||||
		out    string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcde",
 | 
			
		||||
			length: 3,
 | 
			
		||||
			out:    "abc",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 5,
 | 
			
		||||
			out:    "abcde",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 10,
 | 
			
		||||
			out:    "abcdefg",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: 0,
 | 
			
		||||
			out:    "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in:     "abcdefg",
 | 
			
		||||
			length: -1,
 | 
			
		||||
			out:    "abcdefg",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := Truncate(tt.in, tt.length)
 | 
			
		||||
		if actual != tt.out {
 | 
			
		||||
			t.Errorf("\nexpected: %s\n  actual: %s", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user