@@ -1,6 +1,5 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - 1.7
 | 
			
		||||
  - 1.8
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@
 | 
			
		||||
	clean
 | 
			
		||||
 | 
			
		||||
SRCS = $(shell git ls-files '*.go')
 | 
			
		||||
PKGS = ./. ./config ./models ./report ./cveapi ./scan ./util ./commands ./cache
 | 
			
		||||
PKGS = ./. ./cache ./commands ./config ./models ./oval ./report ./scan ./util 
 | 
			
		||||
VERSION := $(shell git describe --tags --abbrev=0)
 | 
			
		||||
REVISION := $(shell git rev-parse --short HEAD)
 | 
			
		||||
LDFLAGS := -X 'main.version=$(VERSION)' \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										149
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -1,16 +1,17 @@
 | 
			
		||||
memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/Azure/azure-storage-go"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "12ccaadb081cdd217702067d28da9a7ff4305239"
 | 
			
		||||
  name = "github.com/Azure/azure-sdk-for-go"
 | 
			
		||||
  packages = ["storage"]
 | 
			
		||||
  revision = "57db66900881e9fd21fd041a9d013514700ecab3"
 | 
			
		||||
  version = "v10.3.0-beta"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/Azure/go-autorest"
 | 
			
		||||
  packages = ["autorest","autorest/azure","autorest/date"]
 | 
			
		||||
  revision = "a2fdd780c9a50455cecd249b00bdc3eb73a78e31"
 | 
			
		||||
  version = "v7.3.1"
 | 
			
		||||
  packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
 | 
			
		||||
  revision = "77a52603f06947221c672f10275abc9bf2c7d557"
 | 
			
		||||
  version = "v8.3.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/BurntSushi/toml"
 | 
			
		||||
@@ -18,40 +19,35 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
  revision = "b26d9c308763d68093482582cea63d69be07a0f0"
 | 
			
		||||
  version = "v0.3.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/Sirupsen/logrus"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "10f801ebc38b33738c9d17d50860f484a0988ff5"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/asaskevich/govalidator"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877"
 | 
			
		||||
  version = "v5"
 | 
			
		||||
  revision = "4918b99a7cb949bb295f3c7bbaf24b577d806e35"
 | 
			
		||||
  version = "v6"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/aws/aws-sdk-go"
 | 
			
		||||
  packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/request","aws/session","aws/signer/v4","private/endpoints","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","private/waiter","service/s3","service/sts"]
 | 
			
		||||
  revision = "5b341290c488aa6bd76b335d819b4a68516ec3ab"
 | 
			
		||||
  packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/s3","service/sts"]
 | 
			
		||||
  revision = "264af29009637e0a9e5d4a276d0969c3ed918ffd"
 | 
			
		||||
  version = "v1.10.29"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/boltdb/bolt"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "583e8937c61f1af6513608ccc75c97b6abdf4ff9"
 | 
			
		||||
  version = "v1.3.0"
 | 
			
		||||
  revision = "2f1ce7a837dcb8da3ec595b1dac9d0632f0f99e8"
 | 
			
		||||
  version = "v1.3.1"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/cenkalti/backoff"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "32cd0c5b3aef12c76ed64aaf678f6c79736be7dc"
 | 
			
		||||
  version = "v1.0.0"
 | 
			
		||||
  revision = "61153c768f31ee5f130071d08fc82b85208528de"
 | 
			
		||||
  version = "v1.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/cheggaaa/pb"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "b6229822fa186496fcbf34111237e7a9693c6971"
 | 
			
		||||
  version = "v1.0.13"
 | 
			
		||||
  revision = "0d6285554e726cc0620cbecc7e6969c945dcc63b"
 | 
			
		||||
  version = "v1.0.17"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/dgrijalva/jwt-go"
 | 
			
		||||
@@ -62,8 +58,14 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-ini/ini"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "e7fea39b01aea8d5671f6858f0532f56e8bff3a5"
 | 
			
		||||
  version = "v1.27.0"
 | 
			
		||||
  revision = "20b96f641a5ea98f2f8619ff4f3e061cff4833bd"
 | 
			
		||||
  version = "v1.28.2"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-redis/redis"
 | 
			
		||||
  packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
 | 
			
		||||
  revision = "19c1c2272e00c1aaa903cf574c746cd449f9cd3c"
 | 
			
		||||
  version = "v6.5.7"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/go-sql-driver/mysql"
 | 
			
		||||
@@ -108,22 +110,34 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
  version = "0.2.2"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/jroimartin/gocui"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "612b0b2987ec1a6af46d7008cef1efd4b3898346"
 | 
			
		||||
  revision = "4e9ce9a8e26f2ef33dfe297dbdfca148733b6b9b"
 | 
			
		||||
  version = "v0.3.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/k0kubun/pp"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "027a6d1765d673d337e687394dbe780dd64e2a1e"
 | 
			
		||||
  version = "v2.3.0"
 | 
			
		||||
  revision = "d1532fc5d94ecdf2da29e24d7b99721f3287de4a"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-deb-version"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "9865fe14d09b1c729188ac810466dde90f897ee3"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "74609b86c936dff800c69ec89fcf4bc52d5f13a4"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
  packages = ["config","db","jvn","log","models","nvd","util"]
 | 
			
		||||
  revision = "d47709be4cc24d2c77a7be9096dcfcf211ba1d57"
 | 
			
		||||
  revision = "c20fa7e1d07f7c700baf12c855f7fcf61525f1b6"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
@@ -131,23 +145,35 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
  revision = "641dc2cc2d3cbf295dad356667b74c69bcbd6f70"
 | 
			
		||||
  version = "v0.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/goval-dictionary"
 | 
			
		||||
  packages = ["config","db","db/rdb","log","models"]
 | 
			
		||||
  revision = "3523cc174e68f285d0572d07c68ffa3a9290799c"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "e7519b8c80ba008a3bfc57ffa31232bf2a77f455"
 | 
			
		||||
  revision = "75edb2e85a38873f0318be05a458446681d1022f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/labstack/gommon"
 | 
			
		||||
  packages = ["color","log"]
 | 
			
		||||
  revision = "779b8a8b9850a97acba6a3fe20feb628c39e17c1"
 | 
			
		||||
  version = "0.2.2"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/lib/pq"
 | 
			
		||||
  packages = [".","hstore","oid"]
 | 
			
		||||
  revision = "2704adc878c21e1329f46f6e56a1c387d788ff94"
 | 
			
		||||
  revision = "e42267488fe361b9dc034be7a6bffef5b195bceb"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-colorable"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "d228849504861217f796da67fae4f6e347643f15"
 | 
			
		||||
  version = "v0.0.7"
 | 
			
		||||
  revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
 | 
			
		||||
  version = "v0.0.9"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/mattn/go-isatty"
 | 
			
		||||
@@ -183,7 +209,7 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/nsf/termbox-go"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "7994c181db7761ca3c67a217068cf31826113f5f"
 | 
			
		||||
  revision = "4ed959e0540971545eddb8c75514973d670cf739"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/parnurzeal/gorequest"
 | 
			
		||||
@@ -200,29 +226,66 @@ memo = "404d058cf6b46d820e153afc5721e1ab2aa2b10ec345d969b9c460097f99add3"
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/rifflock/lfshook"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "2adb3e0c4ddd8778c4adde609d2dfd4fbe6096ea"
 | 
			
		||||
  version = "1.6"
 | 
			
		||||
  revision = "6844c808343cb8fa357d7f141b1b990e05d24e41"
 | 
			
		||||
  version = "1.7"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  name = "github.com/satori/uuid"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "879c5887cd475cd7864858769793b2ceb0d44feb"
 | 
			
		||||
  version = "v1.1.0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/sirupsen/logrus"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "84573d5f03ab3740f524c7842c3a9bf617961d32"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/valyala/bytebufferpool"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/valyala/fasttemplate"
 | 
			
		||||
  packages = ["."]
 | 
			
		||||
  revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/ymomoi/goval-parser"
 | 
			
		||||
  packages = ["oval"]
 | 
			
		||||
  revision = "0a0be1dd9d0855b50be0be5a10ad3085382b6d59"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/crypto"
 | 
			
		||||
  packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
 | 
			
		||||
  revision = "ed779e1bec0180cdfce8135ca6558067b388777b"
 | 
			
		||||
  revision = "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/net"
 | 
			
		||||
  packages = ["context","idna","publicsuffix"]
 | 
			
		||||
  revision = "d1e1b351919c6738fdeb9893d5c998b161464f0c"
 | 
			
		||||
  revision = "1c05540f6879653db88113bc4a2b70aec4bd491f"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/sys"
 | 
			
		||||
  packages = ["unix"]
 | 
			
		||||
  revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a"
 | 
			
		||||
  packages = ["unix","windows"]
 | 
			
		||||
  revision = "07c182904dbd53199946ba614a412c61d3c548f5"
 | 
			
		||||
 | 
			
		||||
[[projects]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "golang.org/x/text"
 | 
			
		||||
  packages = ["internal/gen","internal/triegen","internal/ucd","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
 | 
			
		||||
  revision = "f4b4367115ec2de254587813edaa901bc1c723a8"
 | 
			
		||||
  revision = "e56139fd9c5bc7244c76116c68e500765bb6db6b"
 | 
			
		||||
 | 
			
		||||
[solve-meta]
 | 
			
		||||
  analyzer-name = "dep"
 | 
			
		||||
  analyzer-version = 1
 | 
			
		||||
  inputs-digest = "36d700add80d36c56484ed310b9a7e622b3e308ab22eb42bdfb02fd8f5c90407"
 | 
			
		||||
  solver-name = "gps-cdcl"
 | 
			
		||||
  solver-version = 1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								Gopkg.toml
									
									
									
									
									
								
							
							
						
						@@ -1,28 +1,90 @@
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/Azure/azure-storage-go"
 | 
			
		||||
# Gopkg.toml example
 | 
			
		||||
#
 | 
			
		||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
 | 
			
		||||
# for detailed Gopkg.toml documentation.
 | 
			
		||||
#
 | 
			
		||||
# required = ["github.com/user/thing/cmd/thing"]
 | 
			
		||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
 | 
			
		||||
#
 | 
			
		||||
# [[constraint]]
 | 
			
		||||
#   name = "github.com/user/project"
 | 
			
		||||
#   version = "1.0.0"
 | 
			
		||||
#
 | 
			
		||||
# [[constraint]]
 | 
			
		||||
#   name = "github.com/user/project2"
 | 
			
		||||
#   branch = "dev"
 | 
			
		||||
#   source = "github.com/myfork/project2"
 | 
			
		||||
#
 | 
			
		||||
# [[override]]
 | 
			
		||||
#  name = "github.com/x/y"
 | 
			
		||||
#  version = "2.4.0"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/BurntSushi/toml"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/asaskevich/govalidator"
 | 
			
		||||
  version = "6.0.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/boltdb/bolt"
 | 
			
		||||
  version = "1.3.1"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/cenkalti/backoff"
 | 
			
		||||
  version = "1.0.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/Sirupsen/logrus"
 | 
			
		||||
  name = "github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
  name = "github.com/aws/aws-sdk-go"
 | 
			
		||||
  revision = "5b341290c488aa6bd76b335d819b4a68516ec3ab"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/gosuri/uitable"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/howeyc/gopass"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/jroimartin/gocui"
 | 
			
		||||
  version = "0.3.0"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
  name = "github.com/k0kubun/pp"
 | 
			
		||||
 | 
			
		||||
[[dependencies]]
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-deb-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
  version = "0.1.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/parnurzeal/gorequest"
 | 
			
		||||
  version = "0.2.15"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  name = "github.com/rifflock/lfshook"
 | 
			
		||||
  version = "1.7.0"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
[[constraint]]
 | 
			
		||||
  branch = "master"
 | 
			
		||||
  name = "github.com/kotakanbe/go-cve-dictionary"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										774
									
								
								README.ja.md
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										2
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -22,9 +22,9 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Bolt holds a pointer of bolt.DB
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -22,10 +22,10 @@ import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
@@ -37,8 +37,8 @@ var meta = Meta{
 | 
			
		||||
		Family:  "ubuntu",
 | 
			
		||||
		Release: "16.04",
 | 
			
		||||
	},
 | 
			
		||||
	Packs: []models.PackageInfo{
 | 
			
		||||
		{
 | 
			
		||||
	Packs: models.Packages{
 | 
			
		||||
		"apt": {
 | 
			
		||||
			Name:    "apt",
 | 
			
		||||
			Version: "1",
 | 
			
		||||
		},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								cache/db.go
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -45,16 +45,6 @@ type Cache interface {
 | 
			
		||||
type Meta struct {
 | 
			
		||||
	Name      string
 | 
			
		||||
	Distro    config.Distro
 | 
			
		||||
	Packs     []models.PackageInfo
 | 
			
		||||
	Packs     models.Packages
 | 
			
		||||
	CreatedAt time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +0,0 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/howeyc/gopass"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func getPasswd(prompt string) (string, error) {
 | 
			
		||||
	for {
 | 
			
		||||
		fmt.Print(prompt)
 | 
			
		||||
		pass, err := gopass.GetPasswdMasked()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read password")
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(pass) {
 | 
			
		||||
			return string(pass[:]), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -36,6 +36,7 @@ type ConfigtestCmd struct {
 | 
			
		||||
	logDir         string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
@@ -53,6 +54,7 @@ func (*ConfigtestCmd) Synopsis() string { return "Test configuration" }
 | 
			
		||||
func (*ConfigtestCmd) Usage() string {
 | 
			
		||||
	return `configtest:
 | 
			
		||||
	configtest
 | 
			
		||||
			[-deep]
 | 
			
		||||
			[-config=/path/to/config.toml]
 | 
			
		||||
			[-log-dir=/path/to/log]
 | 
			
		||||
			[-ask-key-password]
 | 
			
		||||
@@ -86,6 +88,8 @@ func (p *ConfigtestCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.deep, "deep", false, "Config test for deep scan mode")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.httpProxy,
 | 
			
		||||
		"http-proxy",
 | 
			
		||||
@@ -133,6 +137,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
@@ -169,7 +174,7 @@ func (p *ConfigtestCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interfa
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking dependendies...")
 | 
			
		||||
	util.Log.Info("Checking dependencies...")
 | 
			
		||||
	scan.CheckDependencies(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking sudo settings...")
 | 
			
		||||
 
 | 
			
		||||
@@ -27,8 +27,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	ps "github.com/kotakanbe/go-pingscanner"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DiscoverCmd is Subcommand of host discovery mode
 | 
			
		||||
@@ -87,9 +87,9 @@ func (p *DiscoverCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Output the tmeplate of config.toml
 | 
			
		||||
// Output the template of config.toml
 | 
			
		||||
func printConfigToml(ips []string) (err error) {
 | 
			
		||||
	const tomlTempale = `
 | 
			
		||||
	const tomlTemplate = `
 | 
			
		||||
[slack]
 | 
			
		||||
hookURL      = "https://hooks.slack.com/services/abc123/defghijklmnopqrstuvwxyz"
 | 
			
		||||
channel      = "#channel-name"
 | 
			
		||||
@@ -99,13 +99,13 @@ authUser     = "username"
 | 
			
		||||
notifyUsers  = ["@username"]
 | 
			
		||||
 | 
			
		||||
[email]
 | 
			
		||||
smtpAddr      = "smtp.gmail.com"
 | 
			
		||||
smtpAddr      = "smtp.example.com"
 | 
			
		||||
smtpPort      = "587"
 | 
			
		||||
user          = "username"
 | 
			
		||||
password      = "password"
 | 
			
		||||
from          = "from@address.com"
 | 
			
		||||
to            = ["to@address.com"]
 | 
			
		||||
cc            = ["cc@address.com"]
 | 
			
		||||
from          = "from@example.com"
 | 
			
		||||
to            = ["to@example.com"]
 | 
			
		||||
cc            = ["cc@example.com"]
 | 
			
		||||
subjectPrefix = "[vuls]"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
@@ -140,7 +140,7 @@ host         = "{{$ip}}"
 | 
			
		||||
#    ["key", "value"],
 | 
			
		||||
#]
 | 
			
		||||
#[servers.{{index $names $i}}.containers]
 | 
			
		||||
#type = "docker" #or "lxd" defualt: docker
 | 
			
		||||
#type = "docker" #or "lxd" default: docker
 | 
			
		||||
#includes = ["${running}"]
 | 
			
		||||
#excludes = ["container_name_a", "4aa37a8b63b9"]
 | 
			
		||||
 | 
			
		||||
@@ -149,7 +149,7 @@ host         = "{{$ip}}"
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
	var tpl *template.Template
 | 
			
		||||
	if tpl, err = template.New("tempalte").Parse(tomlTempale); err != nil {
 | 
			
		||||
	if tpl, err = template.New("template").Parse(tomlTemplate); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -167,7 +167,7 @@ host         = "{{$ip}}"
 | 
			
		||||
	}
 | 
			
		||||
	a.Names = names
 | 
			
		||||
 | 
			
		||||
	fmt.Println("# Create config.toml using below and then ./vuls --config=/path/to/config.toml")
 | 
			
		||||
	fmt.Println("# Create config.toml using below and then ./vuls -config=/path/to/config.toml")
 | 
			
		||||
	if err = tpl.Execute(os.Stdout, a); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -68,9 +69,8 @@ func (p *HistoryCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{
 | 
			
		||||
	c.Conf.DebugSQL = p.debugSQL
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
	dirs, err := report.ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirs {
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
@@ -46,9 +46,13 @@ type ReportCmd struct {
 | 
			
		||||
	ignoreUnscoredCves bool
 | 
			
		||||
	httpProxy          string
 | 
			
		||||
 | 
			
		||||
	cvedbtype string
 | 
			
		||||
	cvedbpath string
 | 
			
		||||
	cvedbURL  string
 | 
			
		||||
	cveDBType string
 | 
			
		||||
	cveDBPath string
 | 
			
		||||
	cveDBURL  string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	toSlack     bool
 | 
			
		||||
	toEMail     bool
 | 
			
		||||
@@ -65,9 +69,10 @@ type ReportCmd struct {
 | 
			
		||||
 | 
			
		||||
	gzip bool
 | 
			
		||||
 | 
			
		||||
	awsProfile  string
 | 
			
		||||
	awsS3Bucket string
 | 
			
		||||
	awsRegion   string
 | 
			
		||||
	awsProfile      string
 | 
			
		||||
	awsS3Bucket     string
 | 
			
		||||
	awsS3ResultsDir string
 | 
			
		||||
	awsRegion       string
 | 
			
		||||
 | 
			
		||||
	azureAccount   string
 | 
			
		||||
	azureKey       string
 | 
			
		||||
@@ -96,6 +101,9 @@ func (*ReportCmd) Usage() string {
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
@@ -114,7 +122,8 @@ func (*ReportCmd) Usage() string {
 | 
			
		||||
		[-aws-profile=default]
 | 
			
		||||
		[-aws-region=us-west-2]
 | 
			
		||||
		[-aws-s3-bucket=bucket_name]
 | 
			
		||||
		[-azure-account=accout]
 | 
			
		||||
		[-aws-s3-results-dir=/bucket/path/to/results]
 | 
			
		||||
		[-azure-account=account]
 | 
			
		||||
		[-azure-key=key]
 | 
			
		||||
		[-azure-container=container]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
@@ -150,23 +159,42 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbtype,
 | 
			
		||||
		&p.cveDBType,
 | 
			
		||||
		"cvedb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching CVE dictionary (sqlite3, mysql or postgres)")
 | 
			
		||||
 | 
			
		||||
	defaultCveDBPath := filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbpath,
 | 
			
		||||
		&p.cveDBPath,
 | 
			
		||||
		"cvedb-path",
 | 
			
		||||
		defaultCveDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get cve detail from cve.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.cvedbURL,
 | 
			
		||||
		&p.cveDBURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or DB connection string")
 | 
			
		||||
		"http://cve-dictionary.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(
 | 
			
		||||
		&p.cvssScoreOver,
 | 
			
		||||
@@ -237,6 +265,7 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&p.awsProfile, "aws-profile", "default", "AWS profile to use")
 | 
			
		||||
	f.StringVar(&p.awsRegion, "aws-region", "us-east-1", "AWS region to use")
 | 
			
		||||
	f.StringVar(&p.awsS3Bucket, "aws-s3-bucket", "", "S3 bucket name")
 | 
			
		||||
	f.StringVar(&p.awsS3ResultsDir, "aws-s3-results-dir", "", "/bucket/path/to/results")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.toAzureBlob,
 | 
			
		||||
		"to-azure-blob",
 | 
			
		||||
@@ -273,15 +302,18 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Lang = p.lang
 | 
			
		||||
	c.Conf.ResultsDir = p.resultsDir
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cvedbURL
 | 
			
		||||
	c.Conf.RefreshCve = p.refreshCve
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.CveDBType = p.cveDBType
 | 
			
		||||
	c.Conf.CveDBPath = p.cveDBPath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDBURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
	c.Conf.CvssScoreOver = p.cvssScoreOver
 | 
			
		||||
	c.Conf.IgnoreUnscoredCves = p.ignoreUnscoredCves
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	c.Conf.FormatXML = p.formatXML
 | 
			
		||||
	c.Conf.FormatJSON = p.formatJSON
 | 
			
		||||
	c.Conf.FormatOneEMail = p.formatOneEMail
 | 
			
		||||
@@ -291,13 +323,14 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
 | 
			
		||||
	c.Conf.GZIP = p.gzip
 | 
			
		||||
	c.Conf.Diff = p.diff
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.diff {
 | 
			
		||||
		dir, err = jsonDir([]string{})
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = jsonDir(f.Args())
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
@@ -327,6 +360,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
		c.Conf.AwsRegion = p.awsRegion
 | 
			
		||||
		c.Conf.AwsProfile = p.awsProfile
 | 
			
		||||
		c.Conf.S3Bucket = p.awsS3Bucket
 | 
			
		||||
		c.Conf.S3ResultsDir = p.awsS3ResultsDir
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %s", c.Conf.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
@@ -347,7 +381,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
 | 
			
		||||
		c.Conf.AzureContainer = p.azureContainer
 | 
			
		||||
		if len(c.Conf.AzureContainer) == 0 {
 | 
			
		||||
			util.Log.Error("Azure storage container name is requied with --azure-container option")
 | 
			
		||||
			util.Log.Error("Azure storage container name is required with -azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
@@ -366,9 +400,9 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
	if ok, err := cveapi.CveClient.CheckHealth(); !ok {
 | 
			
		||||
	if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
		util.Log.Errorf("CVE HTTP server is not running. err: %s", err)
 | 
			
		||||
		util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with --cvedb-path option")
 | 
			
		||||
		util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with -cvedb-path option")
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	if c.Conf.CveDBURL != "" {
 | 
			
		||||
@@ -379,66 +413,25 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var history models.ScanHistory
 | 
			
		||||
	history, err = loadOneScanHistory(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if c.Conf.OvalDBURL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with -ovaldb-path option")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			util.Log.Debugf("need to refresh")
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" && c.Conf.CveDBURL == "" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					util.Log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			filled.Lang = c.Conf.Lang
 | 
			
		||||
			if err := overwriteJSONFile(dir, *filled); err != nil {
 | 
			
		||||
				util.Log.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("no need to refresh")
 | 
			
		||||
			results = append(results, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if p.diff {
 | 
			
		||||
		currentHistory := models.ScanHistory{ScanResults: results}
 | 
			
		||||
		previousHistory, err := loadPreviousScanHistory(currentHistory)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		history, err = diff(currentHistory, previousHistory)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		results = []models.ScanResult{}
 | 
			
		||||
		for _, r := range history.ScanResults {
 | 
			
		||||
			filled, _ := r.FillCveDetail()
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	for _, r := range results {
 | 
			
		||||
		res = append(res, r.FilterByCvssOver())
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ type ScanCmd struct {
 | 
			
		||||
	httpProxy      string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	containersOnly bool
 | 
			
		||||
	deep           bool
 | 
			
		||||
	skipBroken     bool
 | 
			
		||||
	sshNative      bool
 | 
			
		||||
	pipe           bool
 | 
			
		||||
@@ -60,6 +61,7 @@ func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-deep]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
@@ -132,6 +134,12 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.deep,
 | 
			
		||||
		"deep",
 | 
			
		||||
		false,
 | 
			
		||||
		"Deep scan mode. Scan accuracy improves and scanned information becomes richer. Since analysis of changelog, issue commands requiring sudo, but it may be slower and high load on the target server")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
		"pipe",
 | 
			
		||||
@@ -149,7 +157,7 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		&p.scanTimeoutSec,
 | 
			
		||||
		"timeout-scan",
 | 
			
		||||
		120*60,
 | 
			
		||||
		"Number of seconds for scaning vulnerabilities for all servers",
 | 
			
		||||
		"Number of seconds for scanning vulnerabilities for all servers",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -223,6 +231,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
 | 
			
		||||
	c.Conf.SSHNative = p.sshNative
 | 
			
		||||
	c.Conf.HTTPProxy = p.httpProxy
 | 
			
		||||
	c.Conf.ContainersOnly = p.containersOnly
 | 
			
		||||
	c.Conf.Deep = p.deep
 | 
			
		||||
	c.Conf.SkipBroken = p.skipBroken
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
 
 | 
			
		||||
@@ -38,12 +38,17 @@ type TuiCmd struct {
 | 
			
		||||
	configPath string
 | 
			
		||||
	logDir     string
 | 
			
		||||
 | 
			
		||||
	resultsDir       string
 | 
			
		||||
	refreshCve       bool
 | 
			
		||||
	resultsDir string
 | 
			
		||||
	refreshCve bool
 | 
			
		||||
 | 
			
		||||
	cvedbtype        string
 | 
			
		||||
	cvedbpath        string
 | 
			
		||||
	cveDictionaryURL string
 | 
			
		||||
 | 
			
		||||
	ovalDBType string
 | 
			
		||||
	ovalDBPath string
 | 
			
		||||
	ovalDBURL  string
 | 
			
		||||
 | 
			
		||||
	pipe bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -51,7 +56,7 @@ type TuiCmd struct {
 | 
			
		||||
func (*TuiCmd) Name() string { return "tui" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to anayze vulnerabilites" }
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
@@ -61,6 +66,9 @@ func (*TuiCmd) Usage() string {
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres]
 | 
			
		||||
		[-cvedb-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql]
 | 
			
		||||
		[-ovaldb-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
@@ -110,7 +118,26 @@ func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
		&p.cveDictionaryURL,
 | 
			
		||||
		"cvedb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://cve-dictionary.com:8080 or DB connection string")
 | 
			
		||||
		"http://cve-dictionary.example.com:1323 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBType,
 | 
			
		||||
		"ovaldb-type",
 | 
			
		||||
		"sqlite3",
 | 
			
		||||
		"DB type for fetching OVAL dictionary (sqlite3 or mysql)")
 | 
			
		||||
 | 
			
		||||
	defaultOvalDBPath := filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBPath,
 | 
			
		||||
		"ovaldb-path",
 | 
			
		||||
		defaultOvalDBPath,
 | 
			
		||||
		"/path/to/sqlite3 (For get oval detail from oval.sqlite3)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&p.ovalDBURL,
 | 
			
		||||
		"ovaldb-url",
 | 
			
		||||
		"",
 | 
			
		||||
		"http://goval-dictionary.example.com:1324 or mysql connection string")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&p.pipe,
 | 
			
		||||
@@ -139,6 +166,9 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 | 
			
		||||
	c.Conf.CveDBType = p.cvedbtype
 | 
			
		||||
	c.Conf.CveDBPath = p.cvedbpath
 | 
			
		||||
	c.Conf.CveDBURL = p.cveDictionaryURL
 | 
			
		||||
	c.Conf.OvalDBType = p.ovalDBType
 | 
			
		||||
	c.Conf.OvalDBPath = p.ovalDBPath
 | 
			
		||||
	c.Conf.OvalDBURL = p.ovalDBURL
 | 
			
		||||
 | 
			
		||||
	log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
@@ -146,44 +176,22 @@ func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) s
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.Pipe = p.pipe
 | 
			
		||||
	jsonDir, err := jsonDir(f.Args())
 | 
			
		||||
 | 
			
		||||
	dir, err := report.JSONDir(f.Args())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read json dir under results: %s", err)
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	history, err := loadOneScanHistory(jsonDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Errorf("Failed to read from JSON: %s", err)
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	for _, r := range history.ScanResults {
 | 
			
		||||
		if p.refreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if c.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
				if _, err := os.Stat(c.Conf.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
					log.Errorf("SQLite3 DB(CVE-Dictionary) is not exist: %s",
 | 
			
		||||
						c.Conf.CveDBPath)
 | 
			
		||||
					return subcommands.ExitFailure
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			filled, err := fillCveInfoFromCveDB(r)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := overwriteJSONFile(jsonDir, *filled); err != nil {
 | 
			
		||||
				log.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
			results = append(results, *filled)
 | 
			
		||||
		} else {
 | 
			
		||||
			results = append(results, r)
 | 
			
		||||
		}
 | 
			
		||||
	if res, err = report.FillCveInfos(res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	history.ScanResults = results
 | 
			
		||||
	return report.RunTui(history)
 | 
			
		||||
	return report.RunTui(res)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										311
									
								
								commands/util.go
									
									
									
									
									
								
							
							
						
						@@ -18,316 +18,21 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/howeyc/gopass"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// JSONDirs is array of json files path.
 | 
			
		||||
type jsonDirs []string
 | 
			
		||||
 | 
			
		||||
// sort as recent directories are at the head
 | 
			
		||||
func (d jsonDirs) Len() int {
 | 
			
		||||
	return len(d)
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Swap(i, j int) {
 | 
			
		||||
	d[i], d[j] = d[j], d[i]
 | 
			
		||||
}
 | 
			
		||||
func (d jsonDirs) Less(i, j int) bool {
 | 
			
		||||
	return d[j] < d[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getValidJSONDirs return valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func lsValidJSONDirs() (dirs jsonDirs, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(c.Conf.ResultsDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", c.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(c.Conf.ResultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(dirs)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDir returns
 | 
			
		||||
// If there is an arg, check if it is a valid format and return the corresponding path under results.
 | 
			
		||||
// If arg passed via PIPE (such as history subcommand), return that path.
 | 
			
		||||
// Otherwise, returns the path of the latest directory
 | 
			
		||||
func jsonDir(args []string) (string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
 | 
			
		||||
	if 0 < len(args) {
 | 
			
		||||
		if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := filepath.Join(c.Conf.ResultsDir, args[0])
 | 
			
		||||
		for _, d := range dirs {
 | 
			
		||||
			ss := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
			timedir := ss[len(ss)-1]
 | 
			
		||||
			if timedir == args[0] {
 | 
			
		||||
				return path, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", fmt.Errorf("Invalid path: %s", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PIPE
 | 
			
		||||
	if c.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
func getPasswd(prompt string) (string, error) {
 | 
			
		||||
	for {
 | 
			
		||||
		fmt.Print(prompt)
 | 
			
		||||
		pass, err := gopass.GetPasswdMasked()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
			return "", fmt.Errorf("Failed to read password")
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			return filepath.Join(c.Conf.ResultsDir, fields[0]), nil
 | 
			
		||||
		if 0 < len(pass) {
 | 
			
		||||
			return string(pass[:]), nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// returns latest dir when no args or no PIPE
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("No results under %s",
 | 
			
		||||
			c.Conf.ResultsDir)
 | 
			
		||||
	}
 | 
			
		||||
	return dirs[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneServerScanResult read JSON data of one server
 | 
			
		||||
func loadOneServerScanResult(jsonFile string) (result models.ScanResult, err error) {
 | 
			
		||||
	var data []byte
 | 
			
		||||
	if data, err = ioutil.ReadFile(jsonFile); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", jsonFile, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if json.Unmarshal(data, &result) != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to parse %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneScanHistory read JSON data
 | 
			
		||||
func loadOneScanHistory(jsonDir string) (scanHistory models.ScanHistory, err error) {
 | 
			
		||||
	var results []models.ScanResult
 | 
			
		||||
	var files []os.FileInfo
 | 
			
		||||
	if files, err = ioutil.ReadDir(jsonDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s", jsonDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		if filepath.Ext(f.Name()) != ".json" || strings.HasSuffix(f.Name(), "_diff.json") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var r models.ScanResult
 | 
			
		||||
		path := filepath.Join(jsonDir, f.Name())
 | 
			
		||||
		if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		err = fmt.Errorf("There is no json file under %s", jsonDir)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanHistory = models.ScanHistory{
 | 
			
		||||
		ScanResults: results,
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillCveInfoFromCveDB(r models.ScanResult) (*models.ScanResult, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	var vs []models.VulnInfo
 | 
			
		||||
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	vs, err = scanVulnByCpeNames(sInfo.CpeNames, r.ScannedCves)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	r.ScannedCves = vs
 | 
			
		||||
	return r.FillCveDetail()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPreviousScanHistory(current models.ScanHistory) (previous models.ScanHistory, err error) {
 | 
			
		||||
	var dirs jsonDirs
 | 
			
		||||
	if dirs, err = lsValidJSONDirs(); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range current.ScanResults {
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			var r models.ScanResult
 | 
			
		||||
			path := filepath.Join(dir, result.ServerName+".json")
 | 
			
		||||
			if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				previous.ScanResults = append(previous.ScanResults, r)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return previous, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(currentHistory, previousHistory models.ScanHistory) (diffHistory models.ScanHistory, err error) {
 | 
			
		||||
	for _, currentResult := range currentHistory.ScanResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previousResult models.ScanResult
 | 
			
		||||
		for _, previousResult = range previousHistory.ScanResults {
 | 
			
		||||
			if currentResult.ServerName == previousResult.ServerName {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			currentResult.ScannedCves = getNewCves(previousResult, currentResult)
 | 
			
		||||
 | 
			
		||||
			currentResult.KnownCves = []models.CveInfo{}
 | 
			
		||||
			currentResult.UnknownCves = []models.CveInfo{}
 | 
			
		||||
 | 
			
		||||
			currentResult.Packages = models.PackageInfoList{}
 | 
			
		||||
			for _, s := range currentResult.ScannedCves {
 | 
			
		||||
				currentResult.Packages = append(currentResult.Packages, s.Packages...)
 | 
			
		||||
			}
 | 
			
		||||
			currentResult.Packages = currentResult.Packages.UniqByName()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diffHistory.ScanResults = append(diffHistory.ScanResults, currentResult)
 | 
			
		||||
	}
 | 
			
		||||
	return diffHistory, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getNewCves(previousResult, currentResult models.ScanResult) (newVulninfos []models.VulnInfo) {
 | 
			
		||||
	previousCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, previousVulnInfo := range previousResult.ScannedCves {
 | 
			
		||||
		previousCveIDsSet[previousVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range currentResult.ScannedCves {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(currentResult, previousResult, v.CveID) {
 | 
			
		||||
				newVulninfos = append(newVulninfos, v)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			newVulninfos = append(newVulninfos, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveInfoUpdated(currentResult, previousResult models.ScanResult, CveID string) bool {
 | 
			
		||||
	type lastModified struct {
 | 
			
		||||
		Nvd time.Time
 | 
			
		||||
		Jvn time.Time
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	previousModifies := lastModified{}
 | 
			
		||||
	for _, c := range previousResult.KnownCves {
 | 
			
		||||
		if CveID == c.CveID {
 | 
			
		||||
			previousModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
 | 
			
		||||
			previousModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	currentModifies := lastModified{}
 | 
			
		||||
	for _, c := range currentResult.KnownCves {
 | 
			
		||||
		if CveID == c.CveDetail.CveID {
 | 
			
		||||
			currentModifies.Nvd = c.CveDetail.Nvd.LastModifiedDate
 | 
			
		||||
			currentModifies.Jvn = c.CveDetail.Jvn.LastModifiedDate
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return !currentModifies.Nvd.Equal(previousModifies.Nvd) ||
 | 
			
		||||
		!currentModifies.Jvn.Equal(previousModifies.Jvn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := c.Conf.FormatJSON
 | 
			
		||||
	beforeDiff := c.Conf.Diff
 | 
			
		||||
	c.Conf.FormatJSON = true
 | 
			
		||||
	c.Conf.Diff = false
 | 
			
		||||
	w := report.LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	c.Conf.FormatJSON = before
 | 
			
		||||
	c.Conf.Diff = beforeDiff
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanVulnByCpeNames(cpeNames []string, scannedVulns []models.VulnInfo) ([]models.VulnInfo, error) {
 | 
			
		||||
	// To remove duplicate
 | 
			
		||||
	set := map[string]models.VulnInfo{}
 | 
			
		||||
	for _, v := range scannedVulns {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := set[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				val.Confidence = models.CpeNameMatch
 | 
			
		||||
				set[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:      detail.CveID,
 | 
			
		||||
					CpeNames:   []string{name},
 | 
			
		||||
					Confidence: models.CpeNameMatch,
 | 
			
		||||
				}
 | 
			
		||||
				v.NilSliceToEmpty()
 | 
			
		||||
				set[detail.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vinfos := []models.VulnInfo{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		vinfos = append(vinfos, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	return r.Lang != c.Conf.Lang || len(r.KnownCves) == 0 &&
 | 
			
		||||
		len(r.UnknownCves) == 0 &&
 | 
			
		||||
		len(r.IgnoredCves) == 0
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,279 +16,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"reflect"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanHistory
 | 
			
		||||
		inPrevious models.ScanHistory
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atCurrent,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2012-6702",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libexpat1",
 | 
			
		||||
										Version:    "2.1.0-7",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.1.0-7ubuntu0.16.04.2",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2014-9761",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libc-bin",
 | 
			
		||||
										Version:    "2.21-0ubuntu5",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.23-0ubuntu5",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves:   []models.CveInfo{},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
						Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
						Errors:   []string{},
 | 
			
		||||
						Optional: [][]interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atPrevious,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2012-6702",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libexpat1",
 | 
			
		||||
										Version:    "2.1.0-7",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.1.0-7ubuntu0.16.04.2",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2014-9761",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "libc-bin",
 | 
			
		||||
										Version:    "2.21-0ubuntu5",
 | 
			
		||||
										Release:    "",
 | 
			
		||||
										NewVersion: "2.23-0ubuntu5",
 | 
			
		||||
										NewRelease: "",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves:   []models.CveInfo{},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
						Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
						Errors:   []string{},
 | 
			
		||||
						Optional: [][]interface{}{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				Family:      "ubuntu",
 | 
			
		||||
				Release:     "16.04",
 | 
			
		||||
				KnownCves:   []models.CveInfo{},
 | 
			
		||||
				UnknownCves: []models.CveInfo{},
 | 
			
		||||
				IgnoredCves: []models.CveInfo{},
 | 
			
		||||
 | 
			
		||||
				// Packages: models.PackageInfoList{},
 | 
			
		||||
 | 
			
		||||
				Errors:   []string{},
 | 
			
		||||
				Optional: [][]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atCurrent,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2016-6662",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "mysql-libs",
 | 
			
		||||
										Version:    "5.1.73",
 | 
			
		||||
										Release:    "7.el6",
 | 
			
		||||
										NewVersion: "5.1.73",
 | 
			
		||||
										NewRelease: "8.el6_8",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves: []models.CveInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveDetail: cve.CveDetail{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
									Nvd: cve.Nvd{
 | 
			
		||||
										LastModifiedDate: time.Date(2016, 1, 1, 0, 0, 0, 0, time.Local),
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								VulnInfo: models.VulnInfo{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanHistory{
 | 
			
		||||
				ScanResults: models.ScanResults{
 | 
			
		||||
					{
 | 
			
		||||
						ScannedAt:  atPrevious,
 | 
			
		||||
						ServerName: "u16",
 | 
			
		||||
						Family:     "ubuntu",
 | 
			
		||||
						Release:    "16.04",
 | 
			
		||||
						ScannedCves: []models.VulnInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2016-6662",
 | 
			
		||||
								Packages: models.PackageInfoList{
 | 
			
		||||
									{
 | 
			
		||||
										Name:       "mysql-libs",
 | 
			
		||||
										Version:    "5.1.73",
 | 
			
		||||
										Release:    "7.el6",
 | 
			
		||||
										NewVersion: "5.1.73",
 | 
			
		||||
										NewRelease: "8.el6_8",
 | 
			
		||||
										Repository: "",
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
								CpeNames:         []string{},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						KnownCves: []models.CveInfo{
 | 
			
		||||
							{
 | 
			
		||||
								CveDetail: cve.CveDetail{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
									Nvd: cve.Nvd{
 | 
			
		||||
										LastModifiedDate: time.Date(2017, 3, 15, 13, 40, 57, 0, time.Local),
 | 
			
		||||
									},
 | 
			
		||||
								},
 | 
			
		||||
								VulnInfo: models.VulnInfo{
 | 
			
		||||
									CveID: "CVE-2016-6662",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						UnknownCves: []models.CveInfo{},
 | 
			
		||||
						IgnoredCves: []models.CveInfo{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				ScannedCves: []models.VulnInfo{
 | 
			
		||||
					{
 | 
			
		||||
						CveID: "CVE-2016-6662",
 | 
			
		||||
						Packages: models.PackageInfoList{
 | 
			
		||||
							{
 | 
			
		||||
								Name:       "mysql-libs",
 | 
			
		||||
								Version:    "5.1.73",
 | 
			
		||||
								Release:    "7.el6",
 | 
			
		||||
								NewVersion: "5.1.73",
 | 
			
		||||
								NewRelease: "8.el6_8",
 | 
			
		||||
								Repository: "",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeNames:         []string{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				KnownCves:   []models.CveInfo{},
 | 
			
		||||
				UnknownCves: []models.CveInfo{},
 | 
			
		||||
				IgnoredCves: []models.CveInfo{},
 | 
			
		||||
				Packages: models.PackageInfoList{
 | 
			
		||||
					models.PackageInfo{
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
						Changelog: models.Changelog{
 | 
			
		||||
							Contents: "",
 | 
			
		||||
							Method:   "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var s models.ScanHistory
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, _ = diff(tt.inCurrent, tt.inPrevious)
 | 
			
		||||
		for _, actual := range s.ScanResults {
 | 
			
		||||
			if !reflect.DeepEqual(actual, tt.out) {
 | 
			
		||||
				h := pp.Sprint(actual)
 | 
			
		||||
				x := pp.Sprint(tt.out)
 | 
			
		||||
				t.Errorf("diff result : \n %s \n output result : \n %s", h, x)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						@@ -19,17 +19,47 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	valid "github.com/asaskevich/govalidator"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Conf has Configuration
 | 
			
		||||
var Conf Config
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// RedHat is
 | 
			
		||||
	RedHat = "redhat"
 | 
			
		||||
 | 
			
		||||
	// Debian is
 | 
			
		||||
	Debian = "debian"
 | 
			
		||||
 | 
			
		||||
	// Ubuntu is
 | 
			
		||||
	Ubuntu = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// CentOS is
 | 
			
		||||
	CentOS = "centos"
 | 
			
		||||
 | 
			
		||||
	// Fedora is
 | 
			
		||||
	Fedora = "fedora"
 | 
			
		||||
 | 
			
		||||
	// Amazon is
 | 
			
		||||
	Amazon = "amazon"
 | 
			
		||||
 | 
			
		||||
	// Oracle is
 | 
			
		||||
	Oracle = "oracle"
 | 
			
		||||
 | 
			
		||||
	// FreeBSD is
 | 
			
		||||
	FreeBSD = "freebsd"
 | 
			
		||||
 | 
			
		||||
	// Raspbian is
 | 
			
		||||
	Raspbian = "raspbian"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Debug    bool
 | 
			
		||||
@@ -46,17 +76,25 @@ type Config struct {
 | 
			
		||||
 | 
			
		||||
	SSHNative      bool
 | 
			
		||||
	ContainersOnly bool
 | 
			
		||||
	Deep           bool
 | 
			
		||||
	SkipBroken     bool
 | 
			
		||||
 | 
			
		||||
	HTTPProxy  string `valid:"url"`
 | 
			
		||||
	LogDir     string
 | 
			
		||||
	ResultsDir string
 | 
			
		||||
 | 
			
		||||
	CveDBType   string
 | 
			
		||||
	CveDBPath   string
 | 
			
		||||
	CveDBURL    string
 | 
			
		||||
	CveDBType string
 | 
			
		||||
	CveDBPath string
 | 
			
		||||
	CveDBURL  string
 | 
			
		||||
 | 
			
		||||
	OvalDBType string
 | 
			
		||||
	OvalDBPath string
 | 
			
		||||
	OvalDBURL  string
 | 
			
		||||
 | 
			
		||||
	CacheDBPath string
 | 
			
		||||
 | 
			
		||||
	RefreshCve bool
 | 
			
		||||
 | 
			
		||||
	FormatXML         bool
 | 
			
		||||
	FormatJSON        bool
 | 
			
		||||
	FormatOneEMail    bool
 | 
			
		||||
@@ -66,12 +104,13 @@ type Config struct {
 | 
			
		||||
 | 
			
		||||
	GZIP bool
 | 
			
		||||
 | 
			
		||||
	AwsProfile string
 | 
			
		||||
	AwsRegion  string
 | 
			
		||||
	S3Bucket   string
 | 
			
		||||
	AwsProfile   string
 | 
			
		||||
	AwsRegion    string
 | 
			
		||||
	S3Bucket     string
 | 
			
		||||
	S3ResultsDir string
 | 
			
		||||
 | 
			
		||||
	AzureAccount   string
 | 
			
		||||
	AzureKey       string
 | 
			
		||||
	AzureKey       string `json:"-"`
 | 
			
		||||
	AzureContainer string
 | 
			
		||||
 | 
			
		||||
	Pipe bool
 | 
			
		||||
@@ -155,26 +194,17 @@ func (c Config) ValidateOnReport() bool {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch c.CveDBType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cvedb-path: %s",
 | 
			
		||||
				c.CveDBPath))
 | 
			
		||||
	if err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if c.CveDBURL == "" {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				`MySQL connection string is needed. -cvedb-url="user:pass@tcp(localhost:3306)/dbname"`))
 | 
			
		||||
		}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		if c.CveDBURL == "" {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				`PostgreSQL connection string is needed. -cvedb-url=""host=myhost user=user dbname=dbname sslmode=disable password=password""`))
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3', 'mysql' or 'postgres'.  -cvedb-type: %s", c.CveDBType))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("ovaldb", c.OvalDBType, c.OvalDBPath, c.OvalDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := valid.ValidateStruct(c)
 | 
			
		||||
@@ -208,15 +238,12 @@ func (c Config) ValidateOnTui() bool {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" && c.CveDBType != "postgres" {
 | 
			
		||||
		errs = append(errs, fmt.Errorf(
 | 
			
		||||
			"CVE DB type must be either 'sqlite3', 'mysql' or 'postgres'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
			
		||||
	if err := validateDB("cvedb", c.CveDBType, c.CveDBPath, c.CveDBURL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.CveDBType == "sqlite3" {
 | 
			
		||||
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
			
		||||
			errs = append(errs, fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
			
		||||
		if _, err := os.Stat(c.CveDBPath); os.IsNotExist(err) {
 | 
			
		||||
			errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -227,13 +254,53 @@ func (c Config) ValidateOnTui() bool {
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateDB validates configuration
 | 
			
		||||
//  dictionaryDB name is 'cvedb' or 'ovaldb'
 | 
			
		||||
func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if ok, _ := valid.IsFilePath(dbPath); !ok {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				"SQLite3 DB path (%s) must be a *Absolute* file path. -%s-path: %s",
 | 
			
		||||
				dictionaryDBName,
 | 
			
		||||
				dictionaryDBName,
 | 
			
		||||
				dbPath)
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "redis":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return fmt.Errorf(
 | 
			
		||||
				`Redis connection string is needed. -%s-url="redis://localhost/0"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf(
 | 
			
		||||
			"%s type must be either 'sqlite3', 'mysql', 'postgres' or 'redis'.  -%s-type: %s",
 | 
			
		||||
			dictionaryDBName,
 | 
			
		||||
			dictionaryDBName,
 | 
			
		||||
			dbType)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SMTPConf is smtp config
 | 
			
		||||
type SMTPConf struct {
 | 
			
		||||
	SMTPAddr string
 | 
			
		||||
	SMTPPort string `valid:"port"`
 | 
			
		||||
 | 
			
		||||
	User          string
 | 
			
		||||
	Password      string
 | 
			
		||||
	Password      string `json:"-"`
 | 
			
		||||
	From          string
 | 
			
		||||
	To            []string
 | 
			
		||||
	Cc            []string
 | 
			
		||||
@@ -293,7 +360,7 @@ func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
 | 
			
		||||
// SlackConf is slack config
 | 
			
		||||
type SlackConf struct {
 | 
			
		||||
	HookURL   string `valid:"url"`
 | 
			
		||||
	HookURL   string `valid:"url" json:"-"`
 | 
			
		||||
	Channel   string `json:"channel"`
 | 
			
		||||
	IconEmoji string `json:"icon_emoji"`
 | 
			
		||||
	AuthUser  string `json:"username"`
 | 
			
		||||
@@ -343,7 +410,7 @@ type ServerInfo struct {
 | 
			
		||||
	Host        string
 | 
			
		||||
	Port        string
 | 
			
		||||
	KeyPath     string
 | 
			
		||||
	KeyPassword string
 | 
			
		||||
	KeyPassword string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	CpeNames               []string
 | 
			
		||||
	DependencyCheckXMLPath string
 | 
			
		||||
@@ -357,7 +424,7 @@ type ServerInfo struct {
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
 | 
			
		||||
	// For CentOS, RHEL, Amazon
 | 
			
		||||
	Enablerepo string
 | 
			
		||||
	Enablerepo []string
 | 
			
		||||
 | 
			
		||||
	// used internal
 | 
			
		||||
	LogMsgAnsiColor string // DebugLog Color
 | 
			
		||||
 
 | 
			
		||||
@@ -20,11 +20,10 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TOMLLoader loads config
 | 
			
		||||
@@ -164,7 +163,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
			s.Enablerepo = d.Enablerepo
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.Enablerepo) != 0 {
 | 
			
		||||
			for _, repo := range strings.Split(s.Enablerepo, ",") {
 | 
			
		||||
			for _, repo := range s.Enablerepo {
 | 
			
		||||
				switch repo {
 | 
			
		||||
				case "base", "updates":
 | 
			
		||||
					// nop
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -35,18 +34,18 @@ func appendIfMissing(slice []string, str string) []string {
 | 
			
		||||
func Parse(path string) ([]string, error) {
 | 
			
		||||
	file, err := os.Open(path)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, fmt.Errorf("Failed to open: %s", err)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to open: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	b, err := ioutil.ReadAll(file)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []string{}, fmt.Errorf("Failed to read: %s", err)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var anal analysis
 | 
			
		||||
	if err := xml.Unmarshal(b, &anal); err != nil {
 | 
			
		||||
		fmt.Errorf("Failed to unmarshal: %s", err)
 | 
			
		||||
		return nil, fmt.Errorf("Failed to unmarshal: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cpes := []string{}
 | 
			
		||||
@@ -59,6 +58,5 @@ func Parse(path string) ([]string, error) {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(cpes)
 | 
			
		||||
	return cpes, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								img/vuls-abstract.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 123 KiB  | 
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
@@ -17,10 +17,10 @@
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.cloud">
 | 
			
		||||
          <y:Geometry height="50.0" width="80.0" x="918.6458873748779" y="138.1740214029948"/>
 | 
			
		||||
          <y:Geometry height="50.0" width="80.0" x="927.4999999999995" y="148.1740214029948"/>
 | 
			
		||||
          <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="38.0" y="23.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="23.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -37,20 +37,20 @@
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="289.5891316731771" width="171.0" x="1053.145887374878" y="29.37945556640625"/>
 | 
			
		||||
              <y:Geometry height="311.6660156250001" width="171.0" x="1053.145887374878" y="24.166992187499943"/>
 | 
			
		||||
              <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="171.0" x="0.0" y="0.0">Vulnerbility Database</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.0" x="0.0" y="0.0">Vulnerbility Database</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="29" bottomF="28.96858723958337" left="29" leftF="29.35411262512207" right="27" rightF="26.64588737487793" top="27" topF="27.242065429687557"/>
 | 
			
		||||
              <y:BorderInsets bottom="35" bottomF="35.31797281901038" left="29" leftF="29.35411262512207" right="27" rightF="26.64588737487793" 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 1</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</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"/>
 | 
			
		||||
@@ -63,10 +63,10 @@
 | 
			
		||||
        <node id="n1::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1097.5" y="205.0"/>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1097.5" y="138.1740214029948"/>
 | 
			
		||||
              <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.279296875" x="10.8603515625" y="18.8671875">JVN
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
 | 
			
		||||
(Japanese)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -81,10 +81,27 @@
 | 
			
		||||
        <node id="n1::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1097.5" y="93.2875366210938"/>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1097.5" y="60.83300781249994"/>
 | 
			
		||||
              <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="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<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="n1::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1097.5" y="215.51503499348968"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.845703125" x="24.5771484375" y="25.93359375">OVAL<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -106,17 +123,17 @@
 | 
			
		||||
              <y:Geometry height="311.6660156250001" width="171.61765336990447" x="1264.1911733150478" y="24.166992187499943"/>
 | 
			
		||||
              <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="171.61765336990447" x="0.0" y="0.0">Distribution Support</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.61765336990447" x="0.0" y="0.0">Distribution Support</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="27" leftF="26.617653369904474" right="13" rightF="13.0" top="0" topF="0.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="27" leftF="26.617653369904474" right="13" rightF="13.292458057404474" 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 2</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 2</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"/>
 | 
			
		||||
@@ -132,7 +149,7 @@
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1305.8088266849522" y="131.29349772135407"/>
 | 
			
		||||
              <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="64.158203125" x="17.9208984375" y="8.8671875">apptitude
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">apptitude
 | 
			
		||||
changelog<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -150,7 +167,7 @@ changelog<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1306.8088266849522" y="60.83300781249994"/>
 | 
			
		||||
              <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="64.158203125" x="17.9208984375" y="8.8671875">yum
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">yum
 | 
			
		||||
changelog<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -165,11 +182,12 @@ changelog<y:LabelModel>
 | 
			
		||||
        <node id="n2::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1307.8088266849522" y="199.8717651367188"/>
 | 
			
		||||
              <y:Geometry height="64.96826171875" width="99.38234663009553" x="1307.8088266849522" y="193.57912190755206"/>
 | 
			
		||||
              <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="92.828125" x="3.5859375" y="8.8671875">RHSA (RedHat)
 | 
			
		||||
ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.828125" x="3.277110815047763" y="9.284912109375">RHSA (RedHat)
 | 
			
		||||
ALAS (Amazon)
 | 
			
		||||
ELSA(Oracle)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -186,7 +204,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1307.1911733150478" y="270.83300781250006"/>
 | 
			
		||||
              <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="100.650390625" x="-0.3251953125" y="15.93359375">FreeBSD Support<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="100.650390625" x="-0.3251953125" y="15.93359375">FreeBSD Support<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -205,7 +223,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="50.0" width="56.554100036621094" x="1461.7229499816895" y="455.0"/>
 | 
			
		||||
          <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="26.277050018310547" y="23.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="26.277050018310547" y="23.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -222,7 +240,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="64.96826171875" width="56.554100036621094" x="700.4821946087437" y="772.1779226918643"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -242,7 +260,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="37.0" width="109.57881927490234" x="543.9698349896031" y="806.1620535512393"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="sandwich" modelPosition="s" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
 | 
			
		||||
          <y:SVGNodeProperties usingVisualBounds="true"/>
 | 
			
		||||
          <y:SVGModel svgBoundsPolicy="0">
 | 
			
		||||
            <y:SVGContent refid="2"/>
 | 
			
		||||
@@ -259,7 +277,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="293.63460286458337" width="322.9999999999998" x="574.4999999999998" y="53.505286524178246"/>
 | 
			
		||||
              <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="322.9999999999998" 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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="322.9999999999998" x="0.0" y="0.0">go-cve-dictionary / goval-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"/>
 | 
			
		||||
@@ -269,7 +287,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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 4</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</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"/>
 | 
			
		||||
@@ -285,7 +303,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="70.0" width="60.5" x="779.75" y="235.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" hasText="false" height="4.0" modelName="custom" textColor="#000000" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
                <y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -293,7 +311,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
                  <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="48.4140625" x="6.04296875" y="25.93359375">SQLite3<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -309,7 +327,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="101.0" x="626.7499999999997" y="246.92130214917827"/>
 | 
			
		||||
              <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.933593749999972">HTTP server<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.943359375" x="13.5283203125" y="15.933593749999972">HTTP server<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -326,7 +344,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="800.0" y="139.68717128980336"/>
 | 
			
		||||
              <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="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -345,7 +363,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="116.06321112515802" width="97.39570164348925" x="443.18908027812887" y="624.5596727180784"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="46.69785082174462" y="120.06321112515798"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="sandwich" modelPosition="s" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="46.69785082174462" y="120.06321112515798"/>
 | 
			
		||||
          <y:Image alphaImage="true" refid="3"/>
 | 
			
		||||
        </y:ImageNode>
 | 
			
		||||
      </data>
 | 
			
		||||
@@ -356,7 +374,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="50.0" width="85.0" x="449.3869310998735" y="537.5912782806574"/>
 | 
			
		||||
          <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="38.201171875" x="23.3994140625" y="8.8671875">Azure
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.201171875" x="23.3994140625" y="8.8671875">Azure
 | 
			
		||||
BLOB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -374,7 +392,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="388.72953539823004" y="542.5912782806574"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.828125" x="4.6007921144121156" y="10.93359375">.xml<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.828125" x="4.6007921144121156" y="10.93359375">.xml<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -390,7 +408,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="387.2223068773705" y="640.0"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="24.1328125" x="7.448448364412172" y="10.93359375">.txt<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="24.1328125" x="7.448448364412172" y="10.93359375">.txt<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -406,7 +424,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="387.69456235145407" y="591.4808135524652"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="3.3234483644121724" y="10.93359375">.json<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="3.3234483644121724" y="10.93359375">.json<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -422,7 +440,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="387.50427410872305" y="692.5912782806574"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="22.158203125" x="8.435753051912116" y="10.93359375">.gz<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.158203125" x="8.435753051912116" y="10.93359375">.gz<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -435,10 +453,10 @@ BLOB<y:LabelModel>
 | 
			
		||||
    <node id="n13">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.bpmn.Artifact.withShadow">
 | 
			
		||||
          <y:Geometry height="24.0" width="35.0" x="672.5" y="723.6620535512393"/>
 | 
			
		||||
          <y:Geometry height="24.0" width="35.0" x="665.4821946087437" y="740.5686144683882"/>
 | 
			
		||||
          <y:Fill color="#FFFFFFE6" 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="15.5" y="28.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="15.5" y="28.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -464,7 +482,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="302.8032445617764" width="362.4815107458912" x="568.7592446270544" y="407.7653699066118"/>
 | 
			
		||||
              <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="362.4815107458912" x="0.0" y="0.0">Vuls Reporting Server</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="362.4815107458912" x="0.0" y="0.0">Vuls Reporting Server</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"/>
 | 
			
		||||
@@ -474,7 +492,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
              <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="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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"/>
 | 
			
		||||
@@ -493,7 +511,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="251.1372289367764" width="332.4815107458912" x="583.7592446270544" y="444.4313855316118"/>
 | 
			
		||||
                  <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="332.4815107458912" 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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="332.4815107458912" 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"/>
 | 
			
		||||
@@ -503,7 +521,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <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 9</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 9</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"/>
 | 
			
		||||
@@ -519,7 +537,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="50.0" width="90.96302149178268" x="614.5184892541087" y="531.5686144683882"/>
 | 
			
		||||
                  <y:Fill color="#FFCC00" 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="24.18365918339134" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="24.18365918339134" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
                      <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                    </y:LabelModel>
 | 
			
		||||
                    <y:ModelParameter>
 | 
			
		||||
@@ -536,7 +554,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="50.0" width="90.96302149178268" x="716.024731352718" y="630.5686144683882"/>
 | 
			
		||||
                  <y:Fill color="#FFCC00" 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="58.076171875" x="16.44342480839134" y="8.8671875">VulsRepo
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.076171875" x="16.44342480839134" y="8.8671875">VulsRepo
 | 
			
		||||
(WebUI)<y:LabelModel>
 | 
			
		||||
                      <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                    </y:LabelModel>
 | 
			
		||||
@@ -554,7 +572,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="50.0" width="90.96302149178268" x="810.2777338811629" y="630.5686144683882"/>
 | 
			
		||||
                  <y:Fill color="#FFCC00" 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="23.359375" x="33.80182324589134" y="15.93359375">TUI<y:LabelModel>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="23.359375" x="33.80182324589134" y="15.93359375">TUI<y:LabelModel>
 | 
			
		||||
                      <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                    </y:LabelModel>
 | 
			
		||||
                    <y:ModelParameter>
 | 
			
		||||
@@ -574,7 +592,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="108.94242662355282" width="124.0" x="751.2407553729457" y="481.0974011566118"/>
 | 
			
		||||
                      <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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -584,7 +602,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -600,7 +618,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="766.2407553729457" y="517.7634167816118"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -617,7 +635,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="773.2407553729457" y="527.3379464487607"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -634,7 +652,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="784.2407553729457" y="539.0398277801646"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -660,7 +678,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="369.38665771484386" width="427.5" x="1016.25" y="415.3066711425781"/>
 | 
			
		||||
              <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="427.5" x="0.0" y="0.0">Scan Target Server</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="427.5" x="0.0" y="0.0">Scan Target Server</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"/>
 | 
			
		||||
@@ -670,7 +688,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
              <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="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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"/>
 | 
			
		||||
@@ -689,7 +707,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="115.49428304036473" width="147.0" x="1274.75" y="481.9726867675781"/>
 | 
			
		||||
                  <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="147.0" x="0.0" y="0.0">Docker/LXD</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="147.0" x="0.0" y="0.0">Docker/LXD</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"/>
 | 
			
		||||
@@ -699,7 +717,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <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 5</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</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"/>
 | 
			
		||||
@@ -715,7 +733,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="50.0" width="85.0" x="1311.75" y="528.4669698079429"/>
 | 
			
		||||
                  <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="60.748046875" x="12.1259765625" y="15.93359375">Container<y:LabelModel>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="60.748046875" x="12.1259765625" y="15.93359375">Container<y:LabelModel>
 | 
			
		||||
                      <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                    </y:LabelModel>
 | 
			
		||||
                    <y:ModelParameter>
 | 
			
		||||
@@ -734,7 +752,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="104.0" width="149.0" x="1279.75" y="651.4669698079429"/>
 | 
			
		||||
              <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="105.126953125" x="21.9365234375" y="42.93359375">Package Manager<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="105.126953125" x="21.9365234375" y="42.93359375">Package Manager<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -754,7 +772,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="295.1606569803161" width="154.0" x="1031.25" y="467.75445641081495"/>
 | 
			
		||||
                  <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="154.0" 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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="154.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"/>
 | 
			
		||||
@@ -764,7 +782,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <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="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.2431640625" x="-11.62158203125" y="0.0">Folder 10</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"/>
 | 
			
		||||
@@ -780,7 +798,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                  <y:Geometry height="50.0" width="90.96302149178246" x="1068.7684892541088" y="528.4669698079429"/>
 | 
			
		||||
                  <y:Fill color="#FFCC00" 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="30.68359375" x="30.139713870891228" y="15.93359375">Scan<y:LabelModel>
 | 
			
		||||
                  <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="30.68359375" x="30.139713870891228" y="15.93359375">Scan<y:LabelModel>
 | 
			
		||||
                      <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                    </y:LabelModel>
 | 
			
		||||
                    <y:ModelParameter>
 | 
			
		||||
@@ -800,7 +818,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="108.94242662355293" width="124.0" x="1046.25" y="638.9726867675781"/>
 | 
			
		||||
                      <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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -810,7 +828,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -826,7 +844,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="1061.25" y="675.6387023925781"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -843,7 +861,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="1068.25" y="685.2132320597271"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -860,7 +878,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
                      <y:Geometry height="36.0" width="76.0" x="1079.25" y="696.9151133911311"/>
 | 
			
		||||
                      <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                          <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                        </y:LabelModel>
 | 
			
		||||
                        <y:ModelParameter>
 | 
			
		||||
@@ -877,32 +895,13 @@ BLOB<y:LabelModel>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n6::n2" target="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="none"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="113.986328125" x="30.532685822272697" y="-68.65962969657895">Fetch 
 | 
			
		||||
Vulnerability data<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="51.40637506342563" distanceToCenter="true" position="left" ratio="49.539548566656244" 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>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n3" target="n2">
 | 
			
		||||
    <edge id="e0" source="n3" target="n2">
 | 
			
		||||
      <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="-16.97942515444379" y="-79.29177619236555">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="34.626953125" x="-16.97942515444379" y="-79.29177619236555">HTTP<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -914,38 +913,12 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="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="17.81251335144043" y="23.136142435046395">HTTP<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="e3" source="n4" target="n14::n0::n1">
 | 
			
		||||
    <edge id="e1" source="n4" target="n14::n0::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="2.1630847029074403" ty="11.890644753476522"/>
 | 
			
		||||
          <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="40.275390625" x="-16.05774472670157" y="-25.101374183135704">WebUI<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="124.43524707167697" distanceToCenter="true" position="center" ratio="0.0" 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>
 | 
			
		||||
@@ -956,7 +929,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="45.46294808585753" sy="4.081810185279039" 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="bold" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="77.576171875" x="50.52168085731432" y="-18.006733762306112">docker exec
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="77.576171875" x="50.52168085731432" y="-18.006733762306112">docker exec
 | 
			
		||||
lxc exec<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -969,7 +942,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n15::n0::n0" target="n3">
 | 
			
		||||
    <edge id="e2" source="n15::n0::n0" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -985,7 +958,7 @@ lxc exec<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="37.10546875" x="-56.200249246840485" y="13.590015088261055">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.10546875" x="-56.200249246840485" y="13.590015088261055">Insert<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -997,31 +970,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n5" target="n4">
 | 
			
		||||
      <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="38.875" x="-5.725583803513814" y="-46.98669248029603">Notify<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="35.394351766092626" distanceToCenter="true" position="left" ratio="0.644733250412419" 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>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="n6::e1" source="n6::n1" target="n6::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="7.031249999999773" y="20.560447548932814">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="37.9375" x="7.031249999999773" y="20.560447548932814">Select<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1043,7 +998,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n14::n0::n0" target="n4">
 | 
			
		||||
    <edge id="e3" source="n14::n0::n0" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1053,7 +1008,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n14::n0::n0" target="n5">
 | 
			
		||||
    <edge id="e4" source="n14::n0::n0" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1073,13 +1028,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n14::n0::n0" target="n7">
 | 
			
		||||
    <edge id="e5" source="n14::n0::n0" target="n7">
 | 
			
		||||
      <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="22.568359375" x="-69.79481320678701" y="-2.6984373210885906">Put<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.568359375" x="-69.79481320678701" y="-2.6984373210885906">Put<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1091,7 +1046,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n14::n0::n0" target="n8">
 | 
			
		||||
    <edge id="e6" source="n14::n0::n0" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1111,13 +1066,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n4" target="n14::n0::n2">
 | 
			
		||||
    <edge id="e7" source="n4" target="n14::n0::n2">
 | 
			
		||||
      <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="95.939453125" x="28.897803242470673" y="-60.108331548755814">     View Results
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="95.939453125" x="28.897803242470673" y="-60.108331548755814">     View Results
 | 
			
		||||
 on Terminal<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -1136,7 +1091,7 @@ lxc exec<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="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="48.953125" x="57.01879933351279" y="77.24561396863191">os.exec<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="48.953125" x="57.01879933351279" y="77.24561396863191">os.exec<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1148,13 +1103,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n14::n0::n0" target="n6::n1">
 | 
			
		||||
    <edge id="e8" source="n14::n0::n0" target="n6::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="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="37.28125" x="-38.41298470873414" y="-178.74336216335405">HTTP<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="37.28125" x="-38.41298470873414" y="-178.74336216335405">HTTP<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1176,13 +1131,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n15::n2::n1" target="n14::n0::n3">
 | 
			
		||||
    <edge id="e9" source="n15::n2::n1" target="n14::n0::n3">
 | 
			
		||||
      <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="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="53.5" x="-97.64801827097926" y="-81.03347374530358">transfer<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="53.5" x="-97.64801827097926" y="-81.03347374530358">transfer<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1194,7 +1149,17 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n15::n1" target="n3">
 | 
			
		||||
    <edge id="e10" source="n15::n1" target="n3">
 | 
			
		||||
      <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="e11" source="n6::n2" target="n1">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 97 KiB  | 
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
          <y:Geometry height="50.0" width="80.0" x="1046.0141170024865" y="70.63541666666657"/>
 | 
			
		||||
          <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="38.0" y="23.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="23.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -37,20 +37,20 @@
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="289.5891316731771" width="171.0" x="1230.5282340049735" y="-28.09765625"/>
 | 
			
		||||
              <y:Geometry height="292.34706624348956" width="171.0" x="1230.5282340049735" y="-30.855590820312443"/>
 | 
			
		||||
              <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="171.0" x="0.0" y="0.0">Vulnerbility Database</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.0" x="0.0" y="0.0">Vulnerbility Database</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="29" bottomF="28.96858723958337" left="29" leftF="29.35411262512207" right="27" rightF="26.64588737487793" top="27" topF="27.242065429687557"/>
 | 
			
		||||
              <y:BorderInsets bottom="11" bottomF="11.256123860677178" left="29" leftF="29.35411262512207" right="27" rightF="26.64588737487793" 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 1</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</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"/>
 | 
			
		||||
@@ -63,10 +63,10 @@
 | 
			
		||||
        <node id="n1::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="147.52288818359375"/>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="85.52288818359375"/>
 | 
			
		||||
              <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.279296875" x="10.8603515625" y="18.8671875">JVN
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.279296875" x="10.8603515625" y="18.8671875">JVN
 | 
			
		||||
(Japanese)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -81,10 +81,27 @@
 | 
			
		||||
        <node id="n1::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="35.81042480468756"/>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="5.810424804687557"/>
 | 
			
		||||
              <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="29.69921875" x="27.650390625" y="25.93359375">NVD<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.69921875" x="27.650390625" y="25.93359375">NVD<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="n1::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="70.0" width="85.0" x="1274.8823466300955" y="165.23535156249994"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="35.845703125" x="24.5771484375" y="25.93359375">OVAL<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -103,20 +120,20 @@
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="336.1141560872396" width="171.61765336990447" x="1227.3823466300955" y="315.76495361328136"/>
 | 
			
		||||
              <y:Geometry height="336.82033284505223" width="171.61765336990447" x="1227.3823466300955" y="315.76495361328136"/>
 | 
			
		||||
              <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="171.61765336990447" x="0.0" y="0.0">Distribution Support</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="171.61765336990447" x="0.0" y="0.0">Distribution Support</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="12" bottomF="11.879109700520985" left="27" leftF="26.617653369904474" right="13" rightF="13.0" top="13" topF="12.569030761718636"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="27" leftF="26.617653369904474" right="13" rightF="13.292458057404474" top="13" topF="12.569030761718636"/>
 | 
			
		||||
            </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 2</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 2</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"/>
 | 
			
		||||
@@ -132,7 +149,7 @@
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1269.0" y="435.46048990885413"/>
 | 
			
		||||
              <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="64.158203125" x="17.9208984375" y="8.8671875">apptitude
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">apptitude
 | 
			
		||||
changelog<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -150,7 +167,7 @@ changelog<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1270.0" y="365.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="64.158203125" x="17.9208984375" y="8.8671875">yum
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="64.158203125" x="17.9208984375" y="8.8671875">yum
 | 
			
		||||
changelog<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -165,11 +182,12 @@ changelog<y:LabelModel>
 | 
			
		||||
        <node id="n2::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1271.0" y="504.03875732421886"/>
 | 
			
		||||
              <y:Geometry height="64.96826171875" width="100.0" x="1269.0" y="504.03875732421886"/>
 | 
			
		||||
              <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="92.828125" x="3.5859375" y="8.8671875">RHSA (RedHat)
 | 
			
		||||
ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="92.828125" x="3.5859375" y="9.284912109375">RHSA (RedHat)
 | 
			
		||||
ALAS (Amazon)
 | 
			
		||||
ELSA (Oracle)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -183,10 +201,10 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
        <node id="n2::n3">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1270.3823466300955" y="575.0"/>
 | 
			
		||||
              <y:Geometry height="50.0" width="100.0" x="1270.3823466300955" y="587.5852864583336"/>
 | 
			
		||||
              <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="100.650390625" x="-0.3251953125" y="15.93359375">FreeBSD Support<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="100.650390625" x="-0.3251953125" y="15.93359375">FreeBSD Support<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -205,7 +223,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="50.0" width="56.554100036621094" x="1141.7229499816895" y="425.0"/>
 | 
			
		||||
          <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="26.277050018310547" y="23.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="26.277050018310547" y="23.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -225,7 +243,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="371.666015625" width="341.1769911504424" x="575.0" y="298.333984375"/>
 | 
			
		||||
              <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="341.1769911504424" 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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="341.1769911504424" 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"/>
 | 
			
		||||
@@ -235,7 +253,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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 3</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 3</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"/>
 | 
			
		||||
@@ -251,7 +269,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="90.96302149178246" x="749.634165613148" y="349.1798612773512"/>
 | 
			
		||||
              <y:Fill color="#FFCC00" 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="30.68359375" x="30.139713870891228" y="15.93359375">Scan<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="30.68359375" x="30.139713870891228" y="15.93359375">Scan<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -268,7 +286,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="90.96302149178268" x="598.6954804045512" y="455.0"/>
 | 
			
		||||
              <y:Fill color="#FFCC00" 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="24.18365918339134" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="24.18365918339134" y="15.93359375">Report<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -285,7 +303,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="90.96302149178268" x="715.9609671302148" y="575.0"/>
 | 
			
		||||
              <y:Fill color="#FFCC00" 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="58.076171875" x="16.44342480839134" y="8.8671875">VulsRepo
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.076171875" x="16.44342480839134" y="8.8671875">VulsRepo
 | 
			
		||||
(WebUI)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -303,7 +321,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="90.96302149178268" x="810.2139696586597" y="575.0"/>
 | 
			
		||||
              <y:Fill color="#FFCC00" 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="23.359375" x="33.80182324589134" y="15.93359375">TUI<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="23.359375" x="33.80182324589134" y="15.93359375">TUI<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -322,7 +340,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="64.96826171875" width="56.554100036621094" x="691.7229499816895" y="717.515869140625"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="100.890625" x="-22.168262481689453" y="68.96826171875">System Operator<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -342,7 +360,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="37.0" width="109.57881927490234" x="575.2105903625488" y="701.5"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="sandwich" modelPosition="s" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="52.78940963745117" y="41.0"/>
 | 
			
		||||
          <y:SVGNodeProperties usingVisualBounds="true"/>
 | 
			
		||||
          <y:SVGModel svgBoundsPolicy="0">
 | 
			
		||||
            <y:SVGContent refid="2"/>
 | 
			
		||||
@@ -356,7 +374,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="24.0" width="35.0" x="689.5518331226297" y="663.3072060682681"/>
 | 
			
		||||
          <y:Fill color="#FFFFFFE6" 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="15.5" y="28.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="15.5" y="28.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -382,7 +400,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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="322.9999999999998" 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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="322.9999999999998" x="0.0" y="0.0">go-cve-dictionary / goval-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"/>
 | 
			
		||||
@@ -392,7 +410,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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 4</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 4</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"/>
 | 
			
		||||
@@ -408,7 +426,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="28.25" y="33.0">
 | 
			
		||||
                <y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
@@ -416,7 +434,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
                  <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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="48.4140625" x="6.04296875" y="25.93359375">SQLite3<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -432,7 +450,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="73.943359375" x="13.5283203125" y="15.93359375">HTTP server<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -449,7 +467,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="80.0" x="800.0" y="35.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="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.796875" x="16.6015625" y="15.93359375">Fetcher<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -471,7 +489,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="201.49428304036473" width="161.48764324188164" x="964.6223485469814" y="296.7320760091144"/>
 | 
			
		||||
              <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="161.48764324188164" x="0.0" y="0.0">Docker/LXD</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="161.48764324188164" x="0.0" y="0.0">Docker/LXD</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"/>
 | 
			
		||||
@@ -481,7 +499,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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 5</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</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"/>
 | 
			
		||||
@@ -497,7 +515,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="433.22635904947913"/>
 | 
			
		||||
              <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="30.794921875" x="27.1025390625" y="15.93359375">Host<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="30.794921875" x="27.1025390625" y="15.93359375">Host<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -514,7 +532,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="333.3980916341144"/>
 | 
			
		||||
              <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="60.748046875" x="12.1259765625" y="15.93359375">Container<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="60.748046875" x="12.1259765625" y="15.93359375">Container<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -536,7 +554,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="131.666015625" width="168.0" x="964.6223485469814" y="516.3727416992189"/>
 | 
			
		||||
              <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="168.0" x="0.0" y="0.0">Linux/FreeBSD</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="168.0" x="0.0" y="0.0">Linux/FreeBSD</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"/>
 | 
			
		||||
@@ -546,7 +564,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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 6</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 6</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"/>
 | 
			
		||||
@@ -562,7 +580,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1011.6223485469814" y="553.0387573242189"/>
 | 
			
		||||
              <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="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -579,7 +597,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="50.0" width="85.0" x="1032.6223485469814" y="566.4311319986981"/>
 | 
			
		||||
              <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="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="39.865234375" x="22.5673828125" y="15.93359375">Server<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -601,7 +619,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="108.94242662355293" width="124.0" x="739.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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -611,7 +629,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <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:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" 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"/>
 | 
			
		||||
@@ -627,7 +645,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="754.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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -644,7 +662,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="761.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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -661,7 +679,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
              <y:Geometry height="36.0" width="76.0" x="772.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:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="21.80859375" y="8.93359375">JSON<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
@@ -680,7 +698,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="116.06321112515802" width="97.39570164348925" x="445.4298356510746" y="571.968394437421"/>
 | 
			
		||||
          <y:Fill color="#CCCCFF" 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="sandwich" modelPosition="s" textColor="#000000" visible="true" width="4.0" x="46.69785082174462" y="120.06321112515798"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="sandwich" modelPosition="s" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="46.69785082174462" y="120.06321112515798"/>
 | 
			
		||||
          <y:Image alphaImage="true" refid="3"/>
 | 
			
		||||
        </y:ImageNode>
 | 
			
		||||
      </data>
 | 
			
		||||
@@ -691,7 +709,7 @@ ALAS (Amazon)<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="50.0" width="85.0" x="451.6276864728192" y="485.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="38.201171875" x="23.3994140625" y="8.8671875">Azure
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="38.201171875" x="23.3994140625" y="8.8671875">Azure
 | 
			
		||||
BLOB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -709,7 +727,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="390.97029077117577" y="490.0"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="29.828125" x="4.6007921144121156" y="10.93359375">.xml<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="29.828125" x="4.6007921144121156" y="10.93359375">.xml<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -725,7 +743,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="389.4630622503162" y="587.4087217193426"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="24.1328125" x="7.448448364412172" y="10.93359375">.txt<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="24.1328125" x="7.448448364412172" y="10.93359375">.txt<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -741,7 +759,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="389.9353177243998" y="538.8895352718077"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="32.3828125" x="3.3234483644121724" y="10.93359375">.json<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="32.3828125" x="3.3234483644121724" y="10.93359375">.json<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -757,7 +775,7 @@ BLOB<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="40.0" width="39.02970922882429" x="389.7450294816688" y="640.0"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="22.158203125" x="8.435753051912116" y="10.93359375">.gz<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.158203125" x="8.435753051912116" y="10.93359375">.gz<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -773,7 +791,7 @@ BLOB<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="none"/>
 | 
			
		||||
          <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="106.240234375" x="28.568307252002455" y="48.099340559462576">Fetch 
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="106.240234375" x="28.568307252002455" y="48.099340559462576">Fetch 
 | 
			
		||||
Vulnerability data<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -792,7 +810,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="34.626953125" x="-4.560574684247513" y="24.771374369551154">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="34.626953125" x="-4.628977826813298" y="24.803594807609784">HTTP<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -810,7 +828,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="34.626953125" x="42.345150113104864" y="26.521800272057305">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="34.626953125" x="42.457454800604864" y="26.157886474797976">HTTP<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -828,14 +846,6 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="2.1630847029074403" ty="11.890644753476522"/>
 | 
			
		||||
          <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="40.275390625" x="-14.861825373422448" y="-25.77642822265625">WebUI<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="124.43524707167697" distanceToCenter="true" position="center" ratio="0.0" 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>
 | 
			
		||||
@@ -846,7 +856,7 @@ Vulnerability data<y:LabelModel>
 | 
			
		||||
          <y:Path sx="44.363642405794096" sy="24.54915453361525" 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="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="76.88603771593068" y="105.56250755816848">SSH<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="27.07421875" x="76.88603771593068" y="105.56250755816848">SSH<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -864,7 +874,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="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="27.07421875" x="71.96010962283094" y="-0.07972829529296632">SSH<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="27.07421875" x="71.96010962283094" y="-0.07972829529296632">SSH<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -882,7 +892,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="bold" hasBackgroundColor="false" hasLineColor="false" height="32.265625" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" visible="true" width="77.576171875" x="-86.46309207519198" y="-41.560991819769924">docker exec
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#0000FF" verticalTextPosition="bottom" visible="true" width="77.576171875" x="-86.46309207519198" y="-41.560991819769924">docker exec
 | 
			
		||||
lxc exec<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -921,7 +931,7 @@ lxc exec<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="37.10546875" x="-56.200249246840485" y="13.59000810509832">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -933,31 +943,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n6" target="n5">
 | 
			
		||||
      <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="38.875" x="3.063580934131096" y="-107.12398849561202">Notify<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="100.14105708039112" distanceToCenter="true" position="left" ratio="-56.992693208601096" 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>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <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="7.031249999999773" y="20.56044056577005">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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -969,7 +961,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n4::n0" target="n11">
 | 
			
		||||
    <edge id="e8" source="n4::n0" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -979,7 +971,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n4::n1" target="n11">
 | 
			
		||||
    <edge id="e9" source="n4::n1" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -989,7 +981,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n4::n1" target="n5">
 | 
			
		||||
    <edge id="e10" source="n4::n1" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -999,7 +991,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n4::n1" target="n6">
 | 
			
		||||
    <edge id="e11" source="n4::n1" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1009,7 +1001,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n4::n1" target="n8">
 | 
			
		||||
    <edge id="e12" source="n4::n1" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1019,7 +1011,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n4::n2" target="n11">
 | 
			
		||||
    <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"/>
 | 
			
		||||
@@ -1029,13 +1021,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e15" source="n4::n1" target="n12">
 | 
			
		||||
    <edge id="e14" source="n4::n1" target="n12">
 | 
			
		||||
      <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="22.568359375" x="-77.34538676739476" y="14.553670286396482">Put<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="22.568359375" x="-77.34538676739476" y="14.553670286396482">Put<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -1047,7 +1039,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e16" source="n4::n1" target="n13">
 | 
			
		||||
    <edge id="e15" source="n4::n1" target="n13">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1057,7 +1049,7 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e17" source="n4::n3" target="n11">
 | 
			
		||||
    <edge id="e16" source="n4::n3" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
@@ -1067,13 +1059,13 @@ lxc exec<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e18" source="n5" target="n4::n3">
 | 
			
		||||
    <edge id="e17" source="n5" target="n4::n3">
 | 
			
		||||
      <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="95.939453125" x="30.74393308155709" y="-58.42560122139275">     View Results
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="95.939453125" x="30.74393308155709" y="-58.42560122139275">     View Results
 | 
			
		||||
 on Terminal<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 91 KiB  | 
							
								
								
									
										415
									
								
								img/vuls-scan-flow-fast.graphml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,415 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.decision">
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="18.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:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <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="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" 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="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="630.0546766682629"/>
 | 
			
		||||
          <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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" 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="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<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="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="750.4705298628534"/>
 | 
			
		||||
          <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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<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="n6" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="234.29467728596296" y="709.1901021013174"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</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="n6:">
 | 
			
		||||
        <node id="n6::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="416.1341210280616" y="745.8561177263174"/>
 | 
			
		||||
              <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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" 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="n6::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="249.29467728596296" y="745.8561177263174"/>
 | 
			
		||||
              <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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" 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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
            <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="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="92.54867256637169" y="376.28592169721867"/>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" y="459.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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<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="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="92.64475347661187" y="545.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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<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>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n1" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.7796030035582084" 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="e2" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
          <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="e3" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n1" target="n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-123.36984126984123" ty="0.0">
 | 
			
		||||
            <y:Point x="443.6849206349206" y="658.0546766682629"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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="74.6640625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">CentOS
 | 
			
		||||
RHEL
 | 
			
		||||
Ubuntu
 | 
			
		||||
Debian
 | 
			
		||||
Oracle Linux<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="59.14459455430983" distanceToCenter="true" position="right" ratio="0.0" 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="e5" source="n4" target="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="e6" source="n7" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
 | 
			
		||||
          <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="e7" source="n8" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.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="e8" source="n9" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.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="e9" source="n3" target="n5">
 | 
			
		||||
      <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="e10" source="n1" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-196.80057112212188" y="20.933597260871807">Raspbian<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.6447921222409765" 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="n10" target="n3">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-125.78842258255952" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" y="658.0546766682629"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								img/vuls-scan-flow-fast.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 74 KiB  | 
@@ -1,6 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
@@ -20,7 +20,7 @@
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <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="38.0" y="18.0">
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -53,7 +53,7 @@
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <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="60.53125" modelName="custom" textColor="#000000" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="60.53125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="14.95561393805309">Get installed packages
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
@@ -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">Check upgradable packages
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" 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>
 | 
			
		||||
@@ -89,7 +89,7 @@ Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
 | 
			
		||||
          <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="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -106,7 +106,7 @@ upgradable  packages<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="459.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="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
@@ -123,7 +123,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="75.5" y="545.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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -139,7 +139,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="194.904296875" x="36.5478515625" y="18.93359375">Select the CVE detail information<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -155,7 +155,7 @@ Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" 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="46.3984375" modelName="custom" textColor="#000000" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon/RHEL: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
@@ -168,29 +168,12 @@ FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
          <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="687.385587863464"/>
 | 
			
		||||
          <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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" 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="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
 | 
			
		||||
          <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="152.634765625" x="57.6826171875" y="11.8671875">Write results to JSON files
 | 
			
		||||
Reporting<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -200,14 +183,14 @@ Reporting<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="287.8409153761062"/>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="371.39590905499364"/>
 | 
			
		||||
          <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="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -217,13 +200,13 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="373.8409153761062"/>
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.68492063492056" y="459.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="18.1328125" modelName="custom" textColor="#000000" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -233,6 +216,87 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="373.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" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999886" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
Amazon / RHEL: yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" 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="n13" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="229.74083438685204" y="675.1748997511062"/>
 | 
			
		||||
              <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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</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" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</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="n13:">
 | 
			
		||||
        <node id="n13::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="711.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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" 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="n13::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="244.74083438685204" y="711.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="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
@@ -251,8 +315,9 @@ CentOS: yum update --changelog<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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="46.697265625" x="-56.79057374984495" y="-34.26562148912808">Debian
 | 
			
		||||
Ubuntu<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-66.95987036992159" y="-48.39843398912808">Debian
 | 
			
		||||
Ubuntu
 | 
			
		||||
Raspbian<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
@@ -314,7 +379,7 @@ Ubuntu<y:LabelModel>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <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="46.3984375" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="10.125014629061297" y="-48.39843398912805">Amazon
 | 
			
		||||
RHEL
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
@@ -328,17 +393,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n8" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.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="e8" source="n0" target="n2">
 | 
			
		||||
    <edge id="e7" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
@@ -348,7 +403,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n7" target="n10">
 | 
			
		||||
    <edge id="e8" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -358,29 +413,17 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-134.01566143419018" sy="6.159084623893818" tx="0.0" ty="-29.333162136535975">
 | 
			
		||||
            <y:Point x="480.0" y="660.0"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n1" target="n11">
 | 
			
		||||
    <edge id="e9" source="n1" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.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="46.708984375" x="-53.35447755843876" y="11.632816010871807">CentOS<y:LabelModel>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.708984375" x="-53.35447755843876" y="5.000003510871807">CentOS<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:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.0" 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>
 | 
			
		||||
@@ -388,7 +431,7 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n11" target="n12">
 | 
			
		||||
    <edge id="e10" source="n10" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
@@ -398,11 +441,12 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
    <edge id="e11" source="n11" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="134.00000000000006" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="401.8409153761062"/>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-24.34091537610618">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="487.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
@@ -410,6 +454,39 @@ FreeBSD<y:LabelModel>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n8" target="n12">
 | 
			
		||||
      <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="e13" source="n12" target="n7">
 | 
			
		||||
      <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="e14" source="n9" target="n13">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						@@ -29,7 +29,7 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Version of Vuls
 | 
			
		||||
var version = "0.3.0"
 | 
			
		||||
var version = "0.4.0"
 | 
			
		||||
 | 
			
		||||
// Revision of Git
 | 
			
		||||
var revision string
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										272
									
								
								models/cvecontents.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,272 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveContents has CveContent
 | 
			
		||||
type CveContents map[CveContentType]CveContent
 | 
			
		||||
 | 
			
		||||
// NewCveContents create CveContents
 | 
			
		||||
func NewCveContents(conts ...CveContent) CveContents {
 | 
			
		||||
	m := CveContents{}
 | 
			
		||||
	for _, cont := range conts {
 | 
			
		||||
		m[cont.Type] = cont
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentStr has CveContentType and Value
 | 
			
		||||
type CveContentStr struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Except returns CveContents except given keys for enumeration
 | 
			
		||||
func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents) {
 | 
			
		||||
	values = CveContents{}
 | 
			
		||||
	for ctype, content := range v {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, exceptCtype := range exceptCtypes {
 | 
			
		||||
			if ctype == exceptCtype {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			values[ctype] = content
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SourceLinks returns link of source
 | 
			
		||||
func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v[JVN]; found && 0 < len(cont.SourceLink) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.SourceLink})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found {
 | 
			
		||||
			values = append(values, CveContentStr{ctype, cont.SourceLink})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  NVD,
 | 
			
		||||
			Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
	return values
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
// Severities returns Severities
 | 
			
		||||
func (v CveContents) Severities(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.Severity) {
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.Severity,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
// CveContentCpes has CveContentType and Value
 | 
			
		||||
type CveContentCpes struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value []Cpe
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cpes returns affected CPEs of this Vulnerability
 | 
			
		||||
func (v CveContents) Cpes(myFamily string) (values []CveContentCpes) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.Cpes) {
 | 
			
		||||
			values = append(values, CveContentCpes{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.Cpes,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentRefs has CveContentType and Cpes
 | 
			
		||||
type CveContentRefs struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value []Reference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// References returns References
 | 
			
		||||
func (v CveContents) References(myFamily string) (values []CveContentRefs) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.References) {
 | 
			
		||||
			values = append(values, CveContentRefs{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: cont.References,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweIDs returns related CweIDs of the vulnerability
 | 
			
		||||
func (v CveContents) CweIDs(myFamily string) (values []CveContentStr) {
 | 
			
		||||
	order := CveContentTypes{NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order)...)...)
 | 
			
		||||
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v[ctype]; found && 0 < len(cont.CweID) {
 | 
			
		||||
			// RedHat's OVAL sometimes contains multiple CWE-IDs separated by spaces
 | 
			
		||||
			for _, cweID := range strings.Fields(cont.CweID) {
 | 
			
		||||
				values = append(values, CveContentStr{
 | 
			
		||||
					Type:  ctype,
 | 
			
		||||
					Value: cweID,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContent has abstraction of various vulnerability information
 | 
			
		||||
type CveContent struct {
 | 
			
		||||
	Type         CveContentType
 | 
			
		||||
	CveID        string
 | 
			
		||||
	Title        string
 | 
			
		||||
	Summary      string
 | 
			
		||||
	Severity     string
 | 
			
		||||
	Cvss2Score   float64
 | 
			
		||||
	Cvss2Vector  string
 | 
			
		||||
	Cvss3Score   float64
 | 
			
		||||
	Cvss3Vector  string
 | 
			
		||||
	SourceLink   string
 | 
			
		||||
	Cpes         []Cpe
 | 
			
		||||
	References   References
 | 
			
		||||
	CweID        string
 | 
			
		||||
	Published    time.Time
 | 
			
		||||
	LastModified time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Empty checks the content is empty
 | 
			
		||||
func (c CveContent) Empty() bool {
 | 
			
		||||
	return c.Summary == ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentType is a source of CVE information
 | 
			
		||||
type CveContentType string
 | 
			
		||||
 | 
			
		||||
// NewCveContentType create CveContentType
 | 
			
		||||
func NewCveContentType(name string) CveContentType {
 | 
			
		||||
	switch name {
 | 
			
		||||
	case "nvd":
 | 
			
		||||
		return NVD
 | 
			
		||||
	case "jvn":
 | 
			
		||||
		return JVN
 | 
			
		||||
	case "redhat", "centos":
 | 
			
		||||
		return RedHat
 | 
			
		||||
	case "oracle":
 | 
			
		||||
		return Oracle
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return Ubuntu
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return Debian
 | 
			
		||||
	default:
 | 
			
		||||
		return Unknown
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NVD is NVD
 | 
			
		||||
	NVD CveContentType = "nvd"
 | 
			
		||||
 | 
			
		||||
	// JVN is JVN
 | 
			
		||||
	JVN CveContentType = "jvn"
 | 
			
		||||
 | 
			
		||||
	// RedHat is RedHat
 | 
			
		||||
	RedHat CveContentType = "redhat"
 | 
			
		||||
 | 
			
		||||
	// Debian is Debian
 | 
			
		||||
	Debian CveContentType = "debian"
 | 
			
		||||
 | 
			
		||||
	// Ubuntu is Ubuntu
 | 
			
		||||
	Ubuntu CveContentType = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// Oracle is Oracle Linux
 | 
			
		||||
	Oracle CveContentType = "oracle"
 | 
			
		||||
 | 
			
		||||
	// Unknown is Unknown
 | 
			
		||||
	Unknown CveContentType = "unknown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveContentTypes has slide of CveContentType
 | 
			
		||||
type CveContentTypes []CveContentType
 | 
			
		||||
 | 
			
		||||
// AllCveContetTypes has all of CveContentTypes
 | 
			
		||||
var AllCveContetTypes = CveContentTypes{NVD, JVN, RedHat, Debian, Ubuntu}
 | 
			
		||||
 | 
			
		||||
// Except returns CveContentTypes except for given args
 | 
			
		||||
func (c CveContentTypes) Except(excepts ...CveContentType) (excepted CveContentTypes) {
 | 
			
		||||
	for _, ctype := range c {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, except := range excepts {
 | 
			
		||||
			if ctype == except {
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			excepted = append(excepted, ctype)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cpe is Common Platform Enumeration
 | 
			
		||||
type Cpe struct {
 | 
			
		||||
	CpeName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// References is a slice of Reference
 | 
			
		||||
type References []Reference
 | 
			
		||||
 | 
			
		||||
// Reference has a related link of the CVE
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	Source string
 | 
			
		||||
	Link   string
 | 
			
		||||
	RefID  string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										206
									
								
								models/cvecontents_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,206 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestExcept(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  CveContents
 | 
			
		||||
		out CveContents
 | 
			
		||||
	}{{
 | 
			
		||||
		in: CveContents{
 | 
			
		||||
			RedHat: {Type: RedHat},
 | 
			
		||||
			Ubuntu: {Type: Ubuntu},
 | 
			
		||||
			Debian: {Type: Debian},
 | 
			
		||||
		},
 | 
			
		||||
		out: CveContents{
 | 
			
		||||
			RedHat: {Type: RedHat},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.Except(Ubuntu, Debian)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSourceLinks(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang  string
 | 
			
		||||
		cveID string
 | 
			
		||||
		cont  CveContents
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "ja",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "en",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang:  "en",
 | 
			
		||||
				cveID: "CVE-2017-6074",
 | 
			
		||||
				cont:  CveContents{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.SourceLinks(tt.in.lang, "redhat", tt.in.cveID)
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVendorLink(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		family string
 | 
			
		||||
		vinfo  VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "redhat",
 | 
			
		||||
				vinfo: VulnInfo{
 | 
			
		||||
					CveID: "CVE-2017-6074",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:       JVN,
 | 
			
		||||
							SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]string{
 | 
			
		||||
				"RHEL-CVE": "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "ubuntu",
 | 
			
		||||
				vinfo: VulnInfo{
 | 
			
		||||
					CveID: "CVE-2017-6074",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       Ubuntu,
 | 
			
		||||
							SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]string{
 | 
			
		||||
				"Ubuntu-CVE": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2017-6074",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.vinfo.VendorLinks(tt.in.family)
 | 
			
		||||
		for k := range tt.out {
 | 
			
		||||
			if tt.out[k] != actual[k] {
 | 
			
		||||
				t.Errorf("\nexpected: %s\n  actual: %s\n", tt.out[k], actual[k])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										534
									
								
								models/models.go
									
									
									
									
									
								
							
							
						
						@@ -17,535 +17,5 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/cveapi"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanHistory is the history of Scanning.
 | 
			
		||||
type ScanHistory struct {
 | 
			
		||||
	ScanResults ScanResults
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResults is slice of ScanResult.
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s ScanResults) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s ScanResults) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s ScanResults) Less(i, j int) bool {
 | 
			
		||||
	if s[i].ServerName == s[j].ServerName {
 | 
			
		||||
		return s[i].Container.ContainerID < s[i].Container.ContainerID
 | 
			
		||||
	}
 | 
			
		||||
	return s[i].ServerName < s[j].ServerName
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	ScannedAt time.Time
 | 
			
		||||
 | 
			
		||||
	Lang       string
 | 
			
		||||
	ServerName string // TOML Section key
 | 
			
		||||
	Family     string
 | 
			
		||||
	Release    string
 | 
			
		||||
	Container  Container
 | 
			
		||||
	Platform   Platform
 | 
			
		||||
 | 
			
		||||
	// Scanned Vulns via SSH + CPE Vulns
 | 
			
		||||
	ScannedCves []VulnInfo
 | 
			
		||||
 | 
			
		||||
	KnownCves   []CveInfo
 | 
			
		||||
	UnknownCves []CveInfo
 | 
			
		||||
	IgnoredCves []CveInfo
 | 
			
		||||
 | 
			
		||||
	Packages PackageInfoList
 | 
			
		||||
 | 
			
		||||
	Errors   []string
 | 
			
		||||
	Optional [][]interface{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveDetail fetches CVE detailed information from
 | 
			
		||||
// CVE Database, and then set to fields.
 | 
			
		||||
func (r ScanResult) FillCveDetail() (*ScanResult, error) {
 | 
			
		||||
	set := map[string]VulnInfo{}
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		set[v.CveID] = v
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := cveapi.CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	known, unknown, ignored := CveInfos{}, CveInfos{}, CveInfos{}
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		cinfo := CveInfo{
 | 
			
		||||
			CveDetail: d,
 | 
			
		||||
			VulnInfo:  set[d.CveID],
 | 
			
		||||
		}
 | 
			
		||||
		cinfo.NilSliceToEmpty()
 | 
			
		||||
 | 
			
		||||
		// ignored
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, icve := range config.Conf.Servers[r.ServerName].IgnoreCves {
 | 
			
		||||
			if icve == d.CveID {
 | 
			
		||||
				ignored = append(ignored, cinfo)
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if found {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// unknown
 | 
			
		||||
		if d.CvssScore(config.Conf.Lang) <= 0 {
 | 
			
		||||
			unknown = append(unknown, cinfo)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// known
 | 
			
		||||
		known = append(known, cinfo)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(known)
 | 
			
		||||
	sort.Sort(unknown)
 | 
			
		||||
	sort.Sort(ignored)
 | 
			
		||||
	r.KnownCves = known
 | 
			
		||||
	r.UnknownCves = unknown
 | 
			
		||||
	r.IgnoredCves = ignored
 | 
			
		||||
	return &r, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver() ScanResult {
 | 
			
		||||
	cveInfos := []CveInfo{}
 | 
			
		||||
	for _, cveInfo := range r.KnownCves {
 | 
			
		||||
		if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) {
 | 
			
		||||
			cveInfos = append(cveInfos, cveInfo)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.KnownCves = cveInfos
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extention
 | 
			
		||||
func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s", r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
 | 
			
		||||
func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s / %s (%s%s) on %s",
 | 
			
		||||
		r.Container.Name,
 | 
			
		||||
		r.Container.ContainerID,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"|-- %s (%s%s)",
 | 
			
		||||
		r.Container.Name,
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		//  r.Container.ContainerID,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatServerName returns server and container name
 | 
			
		||||
func (r ScanResult) FormatServerName() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return r.ServerName
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s",
 | 
			
		||||
		r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (r ScanResult) CveSummary() string {
 | 
			
		||||
	var high, medium, low, unknown int
 | 
			
		||||
	cves := append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		score := cveInfo.CveDetail.CvssScore(config.Conf.Lang)
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 <= score:
 | 
			
		||||
			high++
 | 
			
		||||
		case 4.0 <= score:
 | 
			
		||||
			medium++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			low++
 | 
			
		||||
		default:
 | 
			
		||||
			unknown++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			high+medium+low, high, medium, low)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		high+medium+low+unknown, high, medium, low, unknown)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllCves returns Known and Unknown CVEs
 | 
			
		||||
func (r ScanResult) AllCves() []CveInfo {
 | 
			
		||||
	return append(r.KnownCves, r.UnknownCves...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NWLink has network link information.
 | 
			
		||||
type NWLink struct {
 | 
			
		||||
	IPAddress string
 | 
			
		||||
	Netmask   string
 | 
			
		||||
	DevName   string
 | 
			
		||||
	LinkState string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
// Score: 0 - 100
 | 
			
		||||
type Confidence struct {
 | 
			
		||||
	Score           int
 | 
			
		||||
	DetectionMethod string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Confidence) String() string {
 | 
			
		||||
	return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CpeNameMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeNameMatchStr = "CpeNameMatch"
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
 | 
			
		||||
	YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatchStr is a String representation of PkgAuditMatch
 | 
			
		||||
	PkgAuditMatchStr = "PkgAuditMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
 | 
			
		||||
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
 | 
			
		||||
	ChangelogLenientMatchStr = "ChangelogLenientMatch"
 | 
			
		||||
 | 
			
		||||
	// FailedToGetChangelog is a String representation of FailedToGetChangelog
 | 
			
		||||
	FailedToGetChangelog = "FailedToGetChangelog"
 | 
			
		||||
 | 
			
		||||
	// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
 | 
			
		||||
	FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var CpeNameMatch = Confidence{100, CpeNameMatchStr}
 | 
			
		||||
 | 
			
		||||
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
 | 
			
		||||
 | 
			
		||||
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
 | 
			
		||||
 | 
			
		||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
 | 
			
		||||
 | 
			
		||||
// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
var ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
 | 
			
		||||
 | 
			
		||||
// VulnInfos is VulnInfo list, getter/setter, sortable methods.
 | 
			
		||||
type VulnInfos []VulnInfo
 | 
			
		||||
 | 
			
		||||
// VulnInfo holds a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Confidence       Confidence
 | 
			
		||||
	Packages         PackageInfoList
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
 | 
			
		||||
func (v *VulnInfo) NilSliceToEmpty() {
 | 
			
		||||
	if v.CpeNames == nil {
 | 
			
		||||
		v.CpeNames = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.DistroAdvisories == nil {
 | 
			
		||||
		v.DistroAdvisories = []DistroAdvisory{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.Packages == nil {
 | 
			
		||||
		v.Packages = PackageInfoList{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByCveID find by CVEID
 | 
			
		||||
func (s VulnInfos) FindByCveID(cveID string) (VulnInfo, bool) {
 | 
			
		||||
	for _, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return VulnInfo{CveID: cveID}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// immutable
 | 
			
		||||
func (s VulnInfos) set(cveID string, v VulnInfo) VulnInfos {
 | 
			
		||||
	for i, p := range s {
 | 
			
		||||
		if cveID == p.CveID {
 | 
			
		||||
			s[i] = v
 | 
			
		||||
			return s
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(s, v)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Len implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Len() int {
 | 
			
		||||
	return len(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Swap implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Swap(i, j int) {
 | 
			
		||||
	s[i], s[j] = s[j], s[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Less implement Sort Interface
 | 
			
		||||
func (s VulnInfos) Less(i, j int) bool {
 | 
			
		||||
	return s[i].CveID < s[j].CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfos is for sorting
 | 
			
		||||
type CveInfos []CveInfo
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Len() int {
 | 
			
		||||
	return len(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Swap(i, j int) {
 | 
			
		||||
	c[i], c[j] = c[j], c[i]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c CveInfos) Less(i, j int) bool {
 | 
			
		||||
	lang := config.Conf.Lang
 | 
			
		||||
	if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
 | 
			
		||||
		return c[i].CveDetail.CveID < c[j].CveDetail.CveID
 | 
			
		||||
	}
 | 
			
		||||
	return c[j].CveDetail.CvssScore(lang) < c[i].CveDetail.CvssScore(lang)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveInfo has Cve Information.
 | 
			
		||||
type CveInfo struct {
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
	VulnInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilSliceToEmpty set nil slice fields to empty slice to avoid null in JSON
 | 
			
		||||
func (c *CveInfo) NilSliceToEmpty() {
 | 
			
		||||
	if c.CveDetail.Nvd.Cpes == nil {
 | 
			
		||||
		c.CveDetail.Nvd.Cpes = []cve.Cpe{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Jvn.Cpes == nil {
 | 
			
		||||
		c.CveDetail.Jvn.Cpes = []cve.Cpe{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Nvd.References == nil {
 | 
			
		||||
		c.CveDetail.Nvd.References = []cve.Reference{}
 | 
			
		||||
	}
 | 
			
		||||
	if c.CveDetail.Jvn.References == nil {
 | 
			
		||||
		c.CveDetail.Jvn.References = []cve.Reference{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageInfoList is slice of PackageInfo
 | 
			
		||||
type PackageInfoList []PackageInfo
 | 
			
		||||
 | 
			
		||||
// Exists returns true if exists the name
 | 
			
		||||
func (ps PackageInfoList) Exists(name string) bool {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UniqByName be uniq by name.
 | 
			
		||||
func (ps PackageInfoList) UniqByName() (distincted PackageInfoList) {
 | 
			
		||||
	set := make(map[string]PackageInfo)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		set[p.Name] = p
 | 
			
		||||
	}
 | 
			
		||||
	//sort by key
 | 
			
		||||
	keys := []string{}
 | 
			
		||||
	for key := range set {
 | 
			
		||||
		keys = append(keys, key)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(keys)
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		distincted = append(distincted, set[key])
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByName search PackageInfo by name
 | 
			
		||||
func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found bool) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			return p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return PackageInfo{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps PackageInfoList) MergeNewVersion(as PackageInfoList) {
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		for i, p := range ps {
 | 
			
		||||
			if p.Name == a.Name {
 | 
			
		||||
				ps[i].NewVersion = a.NewVersion
 | 
			
		||||
				ps[i].NewRelease = a.NewRelease
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ps PackageInfoList) countUpdatablePacks() int {
 | 
			
		||||
	count := 0
 | 
			
		||||
	set := make(map[string]bool)
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if len(p.NewVersion) != 0 && !set[p.Name] {
 | 
			
		||||
			count++
 | 
			
		||||
			set[p.Name] = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps PackageInfoList) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages",
 | 
			
		||||
		ps.countUpdatablePacks())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find search PackageInfo by name-version-release
 | 
			
		||||
//  func (ps PackageInfoList) find(nameVersionRelease string) (PackageInfo, bool) {
 | 
			
		||||
//      for _, p := range ps {
 | 
			
		||||
//          joined := p.Name
 | 
			
		||||
//          if 0 < len(p.Version) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Version)
 | 
			
		||||
//          }
 | 
			
		||||
//          if 0 < len(p.Release) {
 | 
			
		||||
//              joined = fmt.Sprintf("%s-%s", joined, p.Release)
 | 
			
		||||
//          }
 | 
			
		||||
//          if joined == nameVersionRelease {
 | 
			
		||||
//              return p, true
 | 
			
		||||
//          }
 | 
			
		||||
//      }
 | 
			
		||||
//      return PackageInfo{}, false
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// PackageInfosByName implements sort.Interface for []PackageInfo based on
 | 
			
		||||
// the Name field.
 | 
			
		||||
type PackageInfosByName []PackageInfo
 | 
			
		||||
 | 
			
		||||
func (a PackageInfosByName) Len() int           { return len(a) }
 | 
			
		||||
func (a PackageInfosByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
 | 
			
		||||
func (a PackageInfosByName) Less(i, j int) bool { return a[i].Name < a[j].Name }
 | 
			
		||||
 | 
			
		||||
// PackageInfo has installed packages.
 | 
			
		||||
type PackageInfo struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Repository string
 | 
			
		||||
	Changelog  Changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Changelog has contents of changelog and how to get it.
 | 
			
		||||
// Method: modesl.detectionMethodStr
 | 
			
		||||
type Changelog struct {
 | 
			
		||||
	Contents string
 | 
			
		||||
	Method   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringCurrentVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringCurrentVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.Version) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Version)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToStringNewVersion returns package name-version-release
 | 
			
		||||
func (p PackageInfo) ToStringNewVersion() string {
 | 
			
		||||
	str := p.Name
 | 
			
		||||
	if 0 < len(p.NewVersion) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewVersion)
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		str = fmt.Sprintf("%s-%s", str, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	AdvisoryID string
 | 
			
		||||
	Severity   string
 | 
			
		||||
	Issued     time.Time
 | 
			
		||||
	Updated    time.Time
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
// JSONVersion is JSON Version
 | 
			
		||||
const JSONVersion = 2
 | 
			
		||||
 
 | 
			
		||||
@@ -16,122 +16,3 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPackageInfoListUniqByName(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  PackageInfoList
 | 
			
		||||
		out PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Name: "fuga",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual := test.in.UniqByName()
 | 
			
		||||
	for i, ePack := range test.out {
 | 
			
		||||
		if actual[i].Name == ePack.Name {
 | 
			
		||||
			t.Errorf("expected %#v, actual %#v", ePack.Name, actual[i].Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMergeNewVersion(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        PackageInfoList
 | 
			
		||||
		b        PackageInfoList
 | 
			
		||||
		expected PackageInfoList
 | 
			
		||||
	}{
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		PackageInfoList{
 | 
			
		||||
			{
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	test.a.MergeNewVersion(test.b)
 | 
			
		||||
	if !reflect.DeepEqual(test.a, test.expected) {
 | 
			
		||||
		e := pp.Sprintf("%v", test.a)
 | 
			
		||||
		a := pp.Sprintf("%v", test.expected)
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func TestVulnInfosSetGet(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		in  []string
 | 
			
		||||
		out []string
 | 
			
		||||
	}{
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
		[]string{
 | 
			
		||||
			"CVE1",
 | 
			
		||||
			"CVE2",
 | 
			
		||||
			"CVE3",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  var ps packageCveInfos
 | 
			
		||||
	var ps VulnInfos
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		ps = ps.set(cid, VulnInfo{CveID: cid})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(test.out) != len(ps) {
 | 
			
		||||
		t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, expectedCid := range test.out {
 | 
			
		||||
		if expectedCid != ps[i].CveID {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cid := range test.in {
 | 
			
		||||
		p, _ := ps.FindByCveID(cid)
 | 
			
		||||
		if p.CveID != cid {
 | 
			
		||||
			t.Errorf("expected %s, actual %s", cid, p.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										153
									
								
								models/packages.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,153 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Packages is Map of Package
 | 
			
		||||
// { "package-name": Package }
 | 
			
		||||
type Packages map[string]Package
 | 
			
		||||
 | 
			
		||||
// NewPackages create Packages
 | 
			
		||||
func NewPackages(packs ...Package) Packages {
 | 
			
		||||
	m := Packages{}
 | 
			
		||||
	for _, pack := range packs {
 | 
			
		||||
		m[pack.Name] = pack
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MergeNewVersion merges candidate version information to the receiver struct
 | 
			
		||||
func (ps Packages) MergeNewVersion(as Packages) {
 | 
			
		||||
	for _, a := range as {
 | 
			
		||||
		if pack, ok := ps[a.Name]; ok {
 | 
			
		||||
			pack.NewVersion = a.NewVersion
 | 
			
		||||
			pack.NewRelease = a.NewRelease
 | 
			
		||||
			pack.Repository = a.Repository
 | 
			
		||||
			ps[a.Name] = pack
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Merge returns merged map (immutable)
 | 
			
		||||
func (ps Packages) Merge(other Packages) Packages {
 | 
			
		||||
	merged := Packages{}
 | 
			
		||||
	for k, v := range ps {
 | 
			
		||||
		merged[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range other {
 | 
			
		||||
		merged[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	return merged
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (ps Packages) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	nUpdatable := 0
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if p.NewVersion != "" {
 | 
			
		||||
			nUpdatable++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%d updatable packages", nUpdatable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindOne search a element by name-newver-newrel-arch
 | 
			
		||||
func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
	for key, p := range ps {
 | 
			
		||||
		if f(p) {
 | 
			
		||||
			return key, p, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "", Package{}, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed packages.
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name       string
 | 
			
		||||
	Version    string
 | 
			
		||||
	Release    string
 | 
			
		||||
	NewVersion string
 | 
			
		||||
	NewRelease string
 | 
			
		||||
	Arch       string
 | 
			
		||||
	Repository string
 | 
			
		||||
	Changelog  Changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVer returns package version-release
 | 
			
		||||
func (p Package) FormatVer() string {
 | 
			
		||||
	ver := p.Version
 | 
			
		||||
	if 0 < len(p.Release) {
 | 
			
		||||
		ver = fmt.Sprintf("%s-%s", ver, p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return ver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatNewVer returns package version-release
 | 
			
		||||
func (p Package) FormatNewVer() string {
 | 
			
		||||
	ver := p.NewVersion
 | 
			
		||||
	if 0 < len(p.NewRelease) {
 | 
			
		||||
		ver = fmt.Sprintf("%s-%s", ver, p.NewRelease)
 | 
			
		||||
	}
 | 
			
		||||
	return ver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatVersionFromTo formats installed and new package version
 | 
			
		||||
func (p Package) FormatVersionFromTo(notFixedYet bool) string {
 | 
			
		||||
	to := p.FormatNewVer()
 | 
			
		||||
	if notFixedYet {
 | 
			
		||||
		to = "Not Fixed Yet"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s-%s -> %s", p.Name, p.FormatVer(), to)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatChangelog formats the changelog
 | 
			
		||||
func (p Package) FormatChangelog() string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	packVer := fmt.Sprintf("%s-%s -> %s",
 | 
			
		||||
		p.Name, p.FormatVer(), p.FormatNewVer())
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(packVer); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := p.Changelog.Contents
 | 
			
		||||
	if lines := strings.Split(clog, "\n"); len(lines) != 0 {
 | 
			
		||||
		clog = strings.Join(lines[0:len(lines)-1], "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch p.Changelog.Method {
 | 
			
		||||
	case FailedToGetChangelog:
 | 
			
		||||
		clog = "No changelogs"
 | 
			
		||||
	case FailedToFindVersionInChangelog:
 | 
			
		||||
		clog = "Failed to parse changelogs. For detials, check yourself"
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, packVer, delim.String(), clog)
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Changelog has contents of changelog and how to get it.
 | 
			
		||||
// Method: models.detectionMethodStr
 | 
			
		||||
type Changelog struct {
 | 
			
		||||
	Contents string
 | 
			
		||||
	Method   DetectionMethod
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										89
									
								
								models/packages_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,89 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMergeNewVersion(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        Packages
 | 
			
		||||
		b        Packages
 | 
			
		||||
		expected Packages
 | 
			
		||||
	}{
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				Name: "hoge",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {
 | 
			
		||||
				Name:       "hoge",
 | 
			
		||||
				NewVersion: "1.0.0",
 | 
			
		||||
				NewRelease: "release1",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	test.a.MergeNewVersion(test.b)
 | 
			
		||||
	if !reflect.DeepEqual(test.a, test.expected) {
 | 
			
		||||
		e := pp.Sprintf("%v", test.a)
 | 
			
		||||
		a := pp.Sprintf("%v", test.expected)
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMerge(t *testing.T) {
 | 
			
		||||
	var test = struct {
 | 
			
		||||
		a        Packages
 | 
			
		||||
		b        Packages
 | 
			
		||||
		expected Packages
 | 
			
		||||
	}{
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {Name: "hoge"},
 | 
			
		||||
			"fuga": {Name: "fuga"},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hega": {Name: "hega"},
 | 
			
		||||
			"hage": {Name: "hage"},
 | 
			
		||||
		},
 | 
			
		||||
		Packages{
 | 
			
		||||
			"hoge": {Name: "hoge"},
 | 
			
		||||
			"fuga": {Name: "fuga"},
 | 
			
		||||
			"hega": {Name: "hega"},
 | 
			
		||||
			"hage": {Name: "hage"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	actual := test.a.Merge(test.b)
 | 
			
		||||
	if !reflect.DeepEqual(actual, test.expected) {
 | 
			
		||||
		e := pp.Sprintf("%v", test.expected)
 | 
			
		||||
		a := pp.Sprintf("%v", actual)
 | 
			
		||||
		t.Errorf("expected %s, actual %s", e, a)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										191
									
								
								models/scanresults.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,191 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanResults is a slide of ScanResult
 | 
			
		||||
type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	ScannedAt   time.Time
 | 
			
		||||
	ReportedAt  time.Time
 | 
			
		||||
	JSONVersion int
 | 
			
		||||
	Lang        string
 | 
			
		||||
	ServerUUID  string
 | 
			
		||||
	ServerName  string // TOML Section key
 | 
			
		||||
	Family      string
 | 
			
		||||
	Release     string
 | 
			
		||||
	Container   Container
 | 
			
		||||
	Platform    Platform
 | 
			
		||||
 | 
			
		||||
	// Scanned Vulns by SSH scan + CPE + OVAL
 | 
			
		||||
	ScannedCves VulnInfos
 | 
			
		||||
 | 
			
		||||
	RunningKernel Kernel
 | 
			
		||||
	Packages      Packages
 | 
			
		||||
	Errors        []string
 | 
			
		||||
	Optional      [][]interface{}
 | 
			
		||||
 | 
			
		||||
	Config struct {
 | 
			
		||||
		Scan   config.Config
 | 
			
		||||
		Report config.Config
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kernel has the Release, version and whether need restart
 | 
			
		||||
type Kernel struct {
 | 
			
		||||
	Release        string
 | 
			
		||||
	Version        string
 | 
			
		||||
	RebootRequired bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		v2Max := v.MaxCvss2Score()
 | 
			
		||||
		v3Max := v.MaxCvss3Score()
 | 
			
		||||
		max := v2Max.Value.Score
 | 
			
		||||
		if max < v3Max.Value.Score {
 | 
			
		||||
			max = v3Max.Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		if over <= max {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	copiedScanResult := r
 | 
			
		||||
	copiedScanResult.ScannedCves = filtered
 | 
			
		||||
	return copiedScanResult
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnoreCves is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnoreCves(cveIDs []string) ScanResult {
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		for _, c := range cveIDs {
 | 
			
		||||
			if v.CveID == c {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	copiedScanResult := r
 | 
			
		||||
	copiedScanResult.ScannedCves = filtered
 | 
			
		||||
	return copiedScanResult
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extention
 | 
			
		||||
func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s", r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
 | 
			
		||||
func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.FormatServerName(), r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(
 | 
			
		||||
		"%s (%s%s) on %s",
 | 
			
		||||
		r.FormatServerName(),
 | 
			
		||||
		r.Family,
 | 
			
		||||
		r.Release,
 | 
			
		||||
		r.ServerName,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server infromation for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		line := fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
		if r.RunningKernel.RebootRequired {
 | 
			
		||||
			return "[Reboot] " + line
 | 
			
		||||
		}
 | 
			
		||||
		return line
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fmtstr := "|-- %s (%s%s)"
 | 
			
		||||
	if r.RunningKernel.RebootRequired {
 | 
			
		||||
		fmtstr = "|-- [Reboot] %s (%s%s)"
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf(fmtstr, r.Container.Name, r.Family, r.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatServerName returns server and container name
 | 
			
		||||
func (r ScanResult) FormatServerName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
		name = r.ServerName
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s@%s",
 | 
			
		||||
			r.Container.Name, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	if r.RunningKernel.RebootRequired {
 | 
			
		||||
		name = "[Reboot Required] " + name
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatTextReportHeadedr returns header of text report
 | 
			
		||||
func (r ScanResult) FormatTextReportHeadedr() string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
	Type        string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										257
									
								
								models/scanresults_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,257 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestFilterByCvssOver(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		over float64
 | 
			
		||||
		rs   ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				over: 7.0,
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Cvss2Score:   7.1,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   6.9,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         JVN,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Cvss2Score:   7.2,
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Cvss2Score:   7.1,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         NVD,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   6.9,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         JVN,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Cvss2Score:   7.2,
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL Severity
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				over: 7.0,
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Ubuntu,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									Severity:     "HIGH",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         RedHat,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									Severity:     "CRITICAL",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: NewCveContents(
 | 
			
		||||
								CveContent{
 | 
			
		||||
									Type:         Oracle,
 | 
			
		||||
									CveID:        "CVE-2017-0003",
 | 
			
		||||
									Severity:     "IMPORTANT",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Ubuntu,
 | 
			
		||||
								CveID:        "CVE-2017-0001",
 | 
			
		||||
								Severity:     "HIGH",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0002": {
 | 
			
		||||
						CveID: "CVE-2017-0002",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         RedHat,
 | 
			
		||||
								CveID:        "CVE-2017-0002",
 | 
			
		||||
								Severity:     "CRITICAL",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
						CveContents: NewCveContents(
 | 
			
		||||
							CveContent{
 | 
			
		||||
								Type:         Oracle,
 | 
			
		||||
								CveID:        "CVE-2017-0003",
 | 
			
		||||
								Severity:     "IMPORTANT",
 | 
			
		||||
								LastModified: time.Time{},
 | 
			
		||||
							},
 | 
			
		||||
						),
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.rs.FilterByCvssOver(tt.in.over)
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFilterIgnoreCveIDs(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		cves []string
 | 
			
		||||
		rs   ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				cves: []string{"CVE-2017-0002"},
 | 
			
		||||
				rs: ScanResult{
 | 
			
		||||
					ScannedCves: VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: ScanResult{
 | 
			
		||||
				ScannedCves: VulnInfos{
 | 
			
		||||
					"CVE-2017-0001": {
 | 
			
		||||
						CveID: "CVE-2017-0001",
 | 
			
		||||
					},
 | 
			
		||||
					"CVE-2017-0003": {
 | 
			
		||||
						CveID: "CVE-2017-0003",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.rs.FilterIgnoreCves(tt.in.cves)
 | 
			
		||||
		for k := range tt.out.ScannedCves {
 | 
			
		||||
			if !reflect.DeepEqual(tt.out.ScannedCves[k], actual.ScannedCves[k]) {
 | 
			
		||||
				o := pp.Sprintf("%v", tt.out.ScannedCves[k])
 | 
			
		||||
				a := pp.Sprintf("%v", actual.ScannedCves[k])
 | 
			
		||||
				t.Errorf("[%s] expected: %v\n  actual: %v\n", k, o, a)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								models/utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,114 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	cvedict "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ConvertNvdToModel convert NVD to CveContent
 | 
			
		||||
func ConvertNvdToModel(cveID string, nvd cvedict.Nvd) *CveContent {
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range nvd.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []Reference
 | 
			
		||||
	for _, r := range nvd.References {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			Link:   r.Link,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	validVec := true
 | 
			
		||||
	for _, v := range []string{
 | 
			
		||||
		nvd.AccessVector,
 | 
			
		||||
		nvd.AccessComplexity,
 | 
			
		||||
		nvd.Authentication,
 | 
			
		||||
		nvd.ConfidentialityImpact,
 | 
			
		||||
		nvd.IntegrityImpact,
 | 
			
		||||
		nvd.AvailabilityImpact,
 | 
			
		||||
	} {
 | 
			
		||||
		if len(v) == 0 {
 | 
			
		||||
			validVec = false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := ""
 | 
			
		||||
	if validVec {
 | 
			
		||||
		vector = fmt.Sprintf("AV:%s/AC:%s/Au:%s/C:%s/I:%s/A:%s",
 | 
			
		||||
			string(nvd.AccessVector[0]),
 | 
			
		||||
			string(nvd.AccessComplexity[0]),
 | 
			
		||||
			string(nvd.Authentication[0]),
 | 
			
		||||
			string(nvd.ConfidentialityImpact[0]),
 | 
			
		||||
			string(nvd.IntegrityImpact[0]),
 | 
			
		||||
			string(nvd.AvailabilityImpact[0]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//TODO CVSSv3
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         NVD,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Summary:      nvd.Summary,
 | 
			
		||||
		Cvss2Score:   nvd.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		Severity:     "", // severity is not contained in NVD
 | 
			
		||||
		SourceLink:   "https://nvd.nist.gov/vuln/detail/" + cveID,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		CweID:        nvd.CweID,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    nvd.PublishedDate,
 | 
			
		||||
		LastModified: nvd.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertJvnToModel convert JVN to CveContent
 | 
			
		||||
func ConvertJvnToModel(cveID string, jvn cvedict.Jvn) *CveContent {
 | 
			
		||||
	var cpes []Cpe
 | 
			
		||||
	for _, c := range jvn.Cpes {
 | 
			
		||||
		cpes = append(cpes, Cpe{CpeName: c.CpeName})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	refs := []Reference{}
 | 
			
		||||
	for _, r := range jvn.References {
 | 
			
		||||
		refs = append(refs, Reference{
 | 
			
		||||
			Link:   r.Link,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vector := strings.TrimSuffix(strings.TrimPrefix(jvn.Vector, "("), ")")
 | 
			
		||||
	return &CveContent{
 | 
			
		||||
		Type:         JVN,
 | 
			
		||||
		CveID:        cveID,
 | 
			
		||||
		Title:        jvn.Title,
 | 
			
		||||
		Summary:      jvn.Summary,
 | 
			
		||||
		Severity:     jvn.Severity,
 | 
			
		||||
		Cvss2Score:   jvn.Score,
 | 
			
		||||
		Cvss2Vector:  vector,
 | 
			
		||||
		SourceLink:   jvn.JvnLink,
 | 
			
		||||
		Cpes:         cpes,
 | 
			
		||||
		References:   refs,
 | 
			
		||||
		Published:    jvn.PublishedDate,
 | 
			
		||||
		LastModified: jvn.LastModifiedDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										666
									
								
								models/vulninfos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,666 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VulnInfos has a map of VulnInfo
 | 
			
		||||
// Key: CveID
 | 
			
		||||
type VulnInfos map[string]VulnInfo
 | 
			
		||||
 | 
			
		||||
// Find elements that matches the function passed in argument
 | 
			
		||||
func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
 | 
			
		||||
	filtered := VulnInfos{}
 | 
			
		||||
	for _, vv := range v {
 | 
			
		||||
		if f(vv) {
 | 
			
		||||
			filtered[vv.CveID] = vv
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return filtered
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindScoredVulns return scored vulnerabilities
 | 
			
		||||
func (v VulnInfos) FindScoredVulns() VulnInfos {
 | 
			
		||||
	return v.Find(func(vv VulnInfo) bool {
 | 
			
		||||
		if 0 < vv.MaxCvss2Score().Value.Score ||
 | 
			
		||||
			0 < vv.MaxCvss3Score().Value.Score {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ToSortedSlice returns slice of VulnInfos that is sorted by Score, CVE-ID
 | 
			
		||||
func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) {
 | 
			
		||||
	for k := range v {
 | 
			
		||||
		sorted = append(sorted, v[k])
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(sorted, func(i, j int) bool {
 | 
			
		||||
		maxI := sorted[i].MaxCvssScore()
 | 
			
		||||
		maxJ := sorted[j].MaxCvssScore()
 | 
			
		||||
		if maxI.Value.Score != maxJ.Value.Score {
 | 
			
		||||
			return maxJ.Value.Score < maxI.Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		return sorted[i].CveID < sorted[j].CveID
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountGroupBySeverity summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (v VulnInfos) CountGroupBySeverity() map[string]int {
 | 
			
		||||
	m := map[string]int{}
 | 
			
		||||
	for _, vInfo := range v {
 | 
			
		||||
		score := vInfo.MaxCvss2Score().Value.Score
 | 
			
		||||
		if score < 0.1 {
 | 
			
		||||
			score = vInfo.MaxCvss3Score().Value.Score
 | 
			
		||||
		}
 | 
			
		||||
		switch {
 | 
			
		||||
		case 7.0 <= score:
 | 
			
		||||
			m["High"]++
 | 
			
		||||
		case 4.0 <= score:
 | 
			
		||||
			m["Medium"]++
 | 
			
		||||
		case 0 < score:
 | 
			
		||||
			m["Low"]++
 | 
			
		||||
		default:
 | 
			
		||||
			m["Unknown"]++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatCveSummary summarize the number of CVEs group by CVSSv2 Severity
 | 
			
		||||
func (v VulnInfos) FormatCveSummary() string {
 | 
			
		||||
	m := v.CountGroupBySeverity()
 | 
			
		||||
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatuses is a list of PackageStatus
 | 
			
		||||
type PackageStatuses []PackageStatus
 | 
			
		||||
 | 
			
		||||
// Sort by Name
 | 
			
		||||
func (p PackageStatuses) Sort() {
 | 
			
		||||
	sort.Slice(p, func(i, j int) bool {
 | 
			
		||||
		return p[i].Name < p[j].Name
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageStatus has name and other status abount the package
 | 
			
		||||
type PackageStatus struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	NotFixedYet bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnInfo has a vulnerability information and unsecure packages
 | 
			
		||||
type VulnInfo struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	Confidence       Confidence
 | 
			
		||||
	AffectedPackages PackageStatuses
 | 
			
		||||
	DistroAdvisories []DistroAdvisory // for Aamazon, RHEL, FreeBSD
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	CveContents      CveContents
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Titles returns tilte (TUI)
 | 
			
		||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Title) {
 | 
			
		||||
			values = append(values, CveContentStr{JVN, cont.Title})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		// Only JVN has meaningful title. so return first 100 char of summary
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: summary,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		values = append(values, CveContentStr{
 | 
			
		||||
			Type:  "Vendor",
 | 
			
		||||
			Value: strings.Replace(adv.Description, "\n", " ", -1),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		values = []CveContentStr{{
 | 
			
		||||
			Type:  Unknown,
 | 
			
		||||
			Value: "-",
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Summaries returns summaries
 | 
			
		||||
func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
		if cont, found := v.CveContents[JVN]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := cont.Title
 | 
			
		||||
			summary += "\n" + strings.Replace(
 | 
			
		||||
				strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{JVN, summary})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	order := CveContentTypes{NVD, NewCveContentType(myFamily)}
 | 
			
		||||
	order = append(order, AllCveContetTypes.Except(append(order, JVN)...)...)
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
 | 
			
		||||
			summary := strings.Replace(cont.Summary, "\n", " ", -1)
 | 
			
		||||
			values = append(values, CveContentStr{
 | 
			
		||||
				Type:  ctype,
 | 
			
		||||
				Value: summary,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		values = append(values, CveContentStr{
 | 
			
		||||
			Type:  "Vendor",
 | 
			
		||||
			Value: adv.Description,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(values) == 0 {
 | 
			
		||||
		return []CveContentStr{{
 | 
			
		||||
			Type:  Unknown,
 | 
			
		||||
			Value: "-",
 | 
			
		||||
		}}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2Scores returns CVSS V2 Scores
 | 
			
		||||
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
			}
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		if adv.Severity != "" {
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: "Vendor",
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                severityToV2ScoreRoughly(adv.Severity),
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Vector:               "-",
 | 
			
		||||
					Severity:             strings.ToUpper(adv.Severity),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss3Scores returns CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < cont.Cvss3Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			values = append(values, CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: strings.ToUpper(sev),
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvss3Score returns Max CVSS V3 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
 | 
			
		||||
	// TODO implement NVD
 | 
			
		||||
	order := []CveContentType{RedHat}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
		Value: Cvss{Type: CVSS3},
 | 
			
		||||
	}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    cont.Cvss3Score,
 | 
			
		||||
					Vector:   cont.Cvss3Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss3Score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvssScore returns max CVSS Score
 | 
			
		||||
// If there is no CVSS Score, return Severity as a numerical value.
 | 
			
		||||
func (v VulnInfo) MaxCvssScore() CveContentCvss {
 | 
			
		||||
	v3Max := v.MaxCvss3Score()
 | 
			
		||||
	v2Max := v.MaxCvss2Score()
 | 
			
		||||
	max := v3Max
 | 
			
		||||
	if max.Type == Unknown {
 | 
			
		||||
		return v2Max
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if max.Value.Score < v2Max.Value.Score && !v2Max.Value.CalculatedBySeverity {
 | 
			
		||||
		max = v2Max
 | 
			
		||||
	}
 | 
			
		||||
	return max
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MaxCvss2Score returns Max CVSS V2 Score
 | 
			
		||||
func (v VulnInfo) MaxCvss2Score() CveContentCvss {
 | 
			
		||||
	order := []CveContentType{NVD, RedHat, JVN}
 | 
			
		||||
	max := 0.0
 | 
			
		||||
	value := CveContentCvss{
 | 
			
		||||
		Type:  Unknown,
 | 
			
		||||
		Value: Cvss{Type: CVSS2},
 | 
			
		||||
	}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score {
 | 
			
		||||
			// https://nvd.nist.gov/vuln-metrics/cvss
 | 
			
		||||
			sev := cont.Severity
 | 
			
		||||
			if ctype == NVD {
 | 
			
		||||
				sev = cvss2ScoreToSeverity(cont.Cvss2Score)
 | 
			
		||||
			}
 | 
			
		||||
			value = CveContentCvss{
 | 
			
		||||
				Type: ctype,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    cont.Cvss2Score,
 | 
			
		||||
					Vector:   cont.Cvss2Vector,
 | 
			
		||||
					Severity: sev,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			max = cont.Cvss2Score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < max {
 | 
			
		||||
		return value
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If CVSS score isn't on NVD, RedHat and JVN, use OVAL and advisory Severity.
 | 
			
		||||
	// Convert severity to cvss srore roughly, then returns max severity.
 | 
			
		||||
	// Only Ubuntu, RedHat and Oracle have severity data in OVAL.
 | 
			
		||||
	order = []CveContentType{Ubuntu, RedHat, Oracle}
 | 
			
		||||
	for _, ctype := range order {
 | 
			
		||||
		if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Severity) {
 | 
			
		||||
			score := severityToV2ScoreRoughly(cont.Severity)
 | 
			
		||||
			if max < score {
 | 
			
		||||
				value = CveContentCvss{
 | 
			
		||||
					Type: ctype,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:                 CVSS2,
 | 
			
		||||
						Score:                score,
 | 
			
		||||
						CalculatedBySeverity: true,
 | 
			
		||||
						Vector:               cont.Cvss2Vector,
 | 
			
		||||
						Severity:             cont.Severity,
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			max = score
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only RedHat, Oracle and Amazon has severity data in advisory.
 | 
			
		||||
	for _, adv := range v.DistroAdvisories {
 | 
			
		||||
		if adv.Severity != "" {
 | 
			
		||||
			score := severityToV2ScoreRoughly(adv.Severity)
 | 
			
		||||
			if max < score {
 | 
			
		||||
				value = CveContentCvss{
 | 
			
		||||
					Type: "Vendor",
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:                 CVSS2,
 | 
			
		||||
						Score:                score,
 | 
			
		||||
						CalculatedBySeverity: true,
 | 
			
		||||
						Vector:               "-",
 | 
			
		||||
						Severity:             adv.Severity,
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return value
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss has CveContentType and Cvss2
 | 
			
		||||
type CveContentCvss struct {
 | 
			
		||||
	Type  CveContentType
 | 
			
		||||
	Value Cvss
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CvssType Represent the type of CVSS
 | 
			
		||||
type CvssType string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CVSS2 means CVSS vesion2
 | 
			
		||||
	CVSS2 CvssType = "2"
 | 
			
		||||
 | 
			
		||||
	// CVSS3 means CVSS vesion3
 | 
			
		||||
	CVSS3 CvssType = "3"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Cvss has CVSS Score
 | 
			
		||||
type Cvss struct {
 | 
			
		||||
	Type                 CvssType
 | 
			
		||||
	Score                float64
 | 
			
		||||
	CalculatedBySeverity bool
 | 
			
		||||
	Vector               string
 | 
			
		||||
	Severity             string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
func (c Cvss) Format() string {
 | 
			
		||||
	switch c.Type {
 | 
			
		||||
	case CVSS2:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/%s", c.Score, c.Vector)
 | 
			
		||||
	case CVSS3:
 | 
			
		||||
		return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cvss2ScoreToSeverity(score float64) string {
 | 
			
		||||
	if 7.0 <= score {
 | 
			
		||||
		return "HIGH"
 | 
			
		||||
	} else if 4.0 <= score {
 | 
			
		||||
		return "MEDIUM"
 | 
			
		||||
	}
 | 
			
		||||
	return "LOW"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Amazon Linux Security Advisory
 | 
			
		||||
// Critical, Important, Medium, Low
 | 
			
		||||
// https://alas.aws.amazon.com/
 | 
			
		||||
//
 | 
			
		||||
// RedHat, Oracle OVAL
 | 
			
		||||
// Critical, Important, Moderate, Low
 | 
			
		||||
// https://access.redhat.com/security/updates/classification
 | 
			
		||||
//
 | 
			
		||||
// Ubuntu OVAL
 | 
			
		||||
// Critical, High, Medium, Low
 | 
			
		||||
// https://wiki.ubuntu.com/Bugs/Importance
 | 
			
		||||
// https://people.canonical.com/~ubuntu-security/cve/priority.html
 | 
			
		||||
func severityToV2ScoreRoughly(severity string) float64 {
 | 
			
		||||
	switch strings.ToUpper(severity) {
 | 
			
		||||
	case "CRITICAL":
 | 
			
		||||
		return 10.0
 | 
			
		||||
	case "IMPORTANT", "HIGH":
 | 
			
		||||
		return 8.9
 | 
			
		||||
	case "MODERATE", "MEDIUM":
 | 
			
		||||
		return 6.9
 | 
			
		||||
	case "LOW":
 | 
			
		||||
		return 3.9
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CveContentCvss3 has CveContentType and Cvss3
 | 
			
		||||
//  type CveContentCvss3 struct {
 | 
			
		||||
//      Type  CveContentType
 | 
			
		||||
//      Value Cvss3
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Cvss3 has CVSS v3 Score, Vector and  Severity
 | 
			
		||||
//  type Cvss3 struct {
 | 
			
		||||
//      Score    float64
 | 
			
		||||
//      Vector   string
 | 
			
		||||
//      Severity string
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// Format CVSS Score and Vector
 | 
			
		||||
//  func (c Cvss3) Format() string {
 | 
			
		||||
//      return fmt.Sprintf("%3.1f/CVSS:3.0/%s", c.Score, c.Vector)
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
//  func cvss3ScoreToSeverity(score float64) string {
 | 
			
		||||
//      if 9.0 <= score {
 | 
			
		||||
//          return "CRITICAL"
 | 
			
		||||
//      } else if 7.0 <= score {
 | 
			
		||||
//          return "HIGH"
 | 
			
		||||
//      } else if 4.0 <= score {
 | 
			
		||||
//          return "MEDIUM"
 | 
			
		||||
//      }
 | 
			
		||||
//      return "LOW"
 | 
			
		||||
//  }
 | 
			
		||||
 | 
			
		||||
// FormatMaxCvssScore returns Max CVSS Score
 | 
			
		||||
func (v VulnInfo) FormatMaxCvssScore() string {
 | 
			
		||||
	v2Max := v.MaxCvss2Score()
 | 
			
		||||
	v3Max := v.MaxCvss3Score()
 | 
			
		||||
	if v2Max.Value.Score <= v3Max.Value.Score {
 | 
			
		||||
		return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
			v3Max.Value.Score,
 | 
			
		||||
			strings.ToUpper(v3Max.Value.Severity),
 | 
			
		||||
			v3Max.Type)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%3.1f %s (%s)",
 | 
			
		||||
		v2Max.Value.Score,
 | 
			
		||||
		strings.ToUpper(v2Max.Value.Severity),
 | 
			
		||||
		v2Max.Type)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss2CalcURL returns CVSS v2 caluclator's URL
 | 
			
		||||
func (v VulnInfo) Cvss2CalcURL() string {
 | 
			
		||||
	return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cvss3CalcURL returns CVSS v3 caluclator's URL
 | 
			
		||||
func (v VulnInfo) Cvss3CalcURL() string {
 | 
			
		||||
	return "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=" + v.CveID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VendorLinks returns links of vendor support's URL
 | 
			
		||||
func (v VulnInfo) VendorLinks(family string) map[string]string {
 | 
			
		||||
	links := map[string]string{}
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.RedHat, config.CentOS:
 | 
			
		||||
		links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
 | 
			
		||||
			links[advisory.AdvisoryID] = fmt.Sprintf("https://rhn.redhat.com/errata/%s.html", aidURL)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Oracle:
 | 
			
		||||
		links["Oracle-CVE"] = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", v.CveID)
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links[advisory.AdvisoryID] =
 | 
			
		||||
				fmt.Sprintf("https://linux.oracle.com/errata/%s.html", advisory.AdvisoryID)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Amazon:
 | 
			
		||||
		links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links[advisory.AdvisoryID] =
 | 
			
		||||
				fmt.Sprintf("https://alas.aws.amazon.com/%s.html", advisory.AdvisoryID)
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Ubuntu:
 | 
			
		||||
		links["Ubuntu-CVE"] = "http://people.ubuntu.com/~ubuntu-security/cve/" + v.CveID
 | 
			
		||||
		return links
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID
 | 
			
		||||
	case config.FreeBSD:
 | 
			
		||||
		for _, advisory := range v.DistroAdvisories {
 | 
			
		||||
			links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID)
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	}
 | 
			
		||||
	return links
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NilToEmpty set nil slice or map fields to empty to avoid null in JSON
 | 
			
		||||
func (v *VulnInfo) NilToEmpty() *VulnInfo {
 | 
			
		||||
	if v.CpeNames == nil {
 | 
			
		||||
		v.CpeNames = []string{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.DistroAdvisories == nil {
 | 
			
		||||
		v.DistroAdvisories = []DistroAdvisory{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.AffectedPackages == nil {
 | 
			
		||||
		v.AffectedPackages = PackageStatuses{}
 | 
			
		||||
	}
 | 
			
		||||
	if v.CveContents == nil {
 | 
			
		||||
		v.CveContents = NewCveContents()
 | 
			
		||||
	}
 | 
			
		||||
	for key := range v.CveContents {
 | 
			
		||||
		if v.CveContents[key].Cpes == nil {
 | 
			
		||||
			cont := v.CveContents[key]
 | 
			
		||||
			cont.Cpes = []Cpe{}
 | 
			
		||||
			v.CveContents[key] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DistroAdvisory has Amazon Linux, RHEL, FreeBSD Security Advisory information.
 | 
			
		||||
type DistroAdvisory struct {
 | 
			
		||||
	AdvisoryID  string
 | 
			
		||||
	Severity    string
 | 
			
		||||
	Issued      time.Time
 | 
			
		||||
	Updated     time.Time
 | 
			
		||||
	Description string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Format the distro advisory information
 | 
			
		||||
func (p DistroAdvisory) Format() string {
 | 
			
		||||
	if p.AdvisoryID == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(p.AdvisoryID); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	}
 | 
			
		||||
	buf := []string{p.AdvisoryID, delim.String(), p.Description}
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
// Score: 0 - 100
 | 
			
		||||
type Confidence struct {
 | 
			
		||||
	Score           int
 | 
			
		||||
	DetectionMethod DetectionMethod
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Confidence) String() string {
 | 
			
		||||
	return fmt.Sprintf("%d / %s", c.Score, c.DetectionMethod)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectionMethod indicates
 | 
			
		||||
// - How to detect the CveID
 | 
			
		||||
// - How to get the changelog difference between installed and candidate version
 | 
			
		||||
type DetectionMethod string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CpeNameMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeNameMatchStr = "CpeNameMatch"
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
 | 
			
		||||
	YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatchStr is a String representation of PkgAuditMatch
 | 
			
		||||
	PkgAuditMatchStr = "PkgAuditMatch"
 | 
			
		||||
 | 
			
		||||
	// OvalMatchStr is a String representation of OvalMatch
 | 
			
		||||
	OvalMatchStr = "OvalMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatchStr is a String representation of ChangelogExactMatch
 | 
			
		||||
	ChangelogExactMatchStr = "ChangelogExactMatch"
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatchStr is a String representation of ChangelogLenientMatch
 | 
			
		||||
	ChangelogLenientMatchStr = "ChangelogLenientMatch"
 | 
			
		||||
 | 
			
		||||
	// FailedToGetChangelog is a String representation of FailedToGetChangelog
 | 
			
		||||
	FailedToGetChangelog = "FailedToGetChangelog"
 | 
			
		||||
 | 
			
		||||
	// FailedToFindVersionInChangelog is a String representation of FailedToFindVersionInChangelog
 | 
			
		||||
	FailedToFindVersionInChangelog = "FailedToFindVersionInChangelog"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	CpeNameMatch = Confidence{100, CpeNameMatchStr}
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr}
 | 
			
		||||
 | 
			
		||||
	// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	PkgAuditMatch = Confidence{100, PkgAuditMatchStr}
 | 
			
		||||
 | 
			
		||||
	// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	OvalMatch = Confidence{100, OvalMatchStr}
 | 
			
		||||
 | 
			
		||||
	// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr}
 | 
			
		||||
 | 
			
		||||
	// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
 | 
			
		||||
	ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr}
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										936
									
								
								models/vulninfos_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,936 @@
 | 
			
		||||
/* 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 models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTitles(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang string
 | 
			
		||||
		cont VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "Title1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:  JVN,
 | 
			
		||||
							Title: "Title1",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  Unknown,
 | 
			
		||||
					Value: "-",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.Titles(tt.in.lang, "redhat")
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSummaries(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		lang string
 | 
			
		||||
		cont VulnInfo
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out []CveContentStr
 | 
			
		||||
	}{
 | 
			
		||||
		// lang: ja
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "ja",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  JVN,
 | 
			
		||||
					Value: "Title JVN\nSummary JVN",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: en
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						JVN: {
 | 
			
		||||
							Type:    JVN,
 | 
			
		||||
							Title:   "Title JVN",
 | 
			
		||||
							Summary: "Summary JVN",
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:    RedHat,
 | 
			
		||||
							Summary: "Summary RedHat",
 | 
			
		||||
						},
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:    NVD,
 | 
			
		||||
							Summary: "Summary NVD",
 | 
			
		||||
							// Severity is NIOT included in NVD
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  NVD,
 | 
			
		||||
					Value: "Summary NVD",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type:  RedHat,
 | 
			
		||||
					Value: "Summary RedHat",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// lang: empty
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				lang: "en",
 | 
			
		||||
				cont: VulnInfo{},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentStr{
 | 
			
		||||
				{
 | 
			
		||||
					Type:  Unknown,
 | 
			
		||||
					Value: "-",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.cont.Summaries(tt.in.lang, "redhat")
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCountGroupBySeverity(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfos
 | 
			
		||||
		out map[string]int
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 2.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0004": {
 | 
			
		||||
					CveID: "CVE-2017-0004",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 5.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0005": {
 | 
			
		||||
					CveID: "CVE-2017-0005",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: map[string]int{
 | 
			
		||||
				"High":    1,
 | 
			
		||||
				"Medium":  1,
 | 
			
		||||
				"Low":     1,
 | 
			
		||||
				"Unknown": 1,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.CountGroupBySeverity()
 | 
			
		||||
		for k := range tt.out {
 | 
			
		||||
			if tt.out[k] != actual[k] {
 | 
			
		||||
				t.Errorf("\nexpected %s: %d\n  actual %d\n",
 | 
			
		||||
					k, tt.out[k], actual[k])
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestToSortedSlice(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfos
 | 
			
		||||
		out []VulnInfo
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 8.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 8.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// When max scores are the same, sort by CVE-ID
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss2Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						NVD: {
 | 
			
		||||
							Type:       NVD,
 | 
			
		||||
							Cvss2Score: 6.0,
 | 
			
		||||
						},
 | 
			
		||||
						RedHat: {
 | 
			
		||||
							Type:       RedHat,
 | 
			
		||||
							Cvss3Score: 7.0,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// When there are no cvss scores, sort by severity
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []VulnInfo{
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "High",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: CveContents{
 | 
			
		||||
						Ubuntu: {
 | 
			
		||||
							Type:     Ubuntu,
 | 
			
		||||
							Severity: "Low",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.ToSortedSlice()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCvss2Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out []CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentCvss{
 | 
			
		||||
				{
 | 
			
		||||
					Type: NVD,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.1,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type: RedHat,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.0,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Type: JVN,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS2,
 | 
			
		||||
						Score:    8.2,
 | 
			
		||||
						Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in:  VulnInfo{},
 | 
			
		||||
			out: nil,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.Cvss2Scores()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvss2Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:        JVN,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.2,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss2Score:  8.0,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss2Score:  8.1,
 | 
			
		||||
						Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: JVN,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    8.2,
 | 
			
		||||
					Vector:   "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Severity in OVAL
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Ubuntu,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    0.0,
 | 
			
		||||
					Vector:   "",
 | 
			
		||||
					Severity: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvss2Score()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCvss3Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out []CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:        NVD,
 | 
			
		||||
						Cvss3Score:  8.1,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: []CveContentCvss{
 | 
			
		||||
				{
 | 
			
		||||
					Type: RedHat,
 | 
			
		||||
					Value: Cvss{
 | 
			
		||||
						Type:     CVSS3,
 | 
			
		||||
						Score:    8.0,
 | 
			
		||||
						Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in:  VulnInfo{},
 | 
			
		||||
			out: nil,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.Cvss3Scores()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvss3Scores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:        RedHat,
 | 
			
		||||
						Severity:    "HIGH",
 | 
			
		||||
						Cvss3Score:  8.0,
 | 
			
		||||
						Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    8.0,
 | 
			
		||||
					Vector:   "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS3,
 | 
			
		||||
					Score:    0.0,
 | 
			
		||||
					Vector:   "",
 | 
			
		||||
					Severity: "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvss3Score()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMaxCvssScores(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out CveContentCvss
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss3Score: 7.0,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Cvss2Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS2,
 | 
			
		||||
					Score: 8.0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Cvss3Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: RedHat,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS3,
 | 
			
		||||
					Score: 8.0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//2
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Ubuntu,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//3
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 7.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    7.0,
 | 
			
		||||
					Severity: "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//4
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				DistroAdvisories: []DistroAdvisory{
 | 
			
		||||
					{
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: "Vendor",
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:                 CVSS2,
 | 
			
		||||
					Score:                8.9,
 | 
			
		||||
					CalculatedBySeverity: true,
 | 
			
		||||
					Vector:               "-",
 | 
			
		||||
					Severity:             "HIGH",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					Ubuntu: {
 | 
			
		||||
						Type:     Ubuntu,
 | 
			
		||||
						Severity: "MEDIUM",
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 4.0,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				DistroAdvisories: []DistroAdvisory{
 | 
			
		||||
					{
 | 
			
		||||
						Severity: "HIGH",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: NVD,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:     CVSS2,
 | 
			
		||||
					Score:    4,
 | 
			
		||||
					Severity: "MEDIUM",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// Empty
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{},
 | 
			
		||||
			out: CveContentCvss{
 | 
			
		||||
				Type: Unknown,
 | 
			
		||||
				Value: Cvss{
 | 
			
		||||
					Type:  CVSS2,
 | 
			
		||||
					Score: 0,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.MaxCvssScore()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\n[%d] expected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFormatMaxCvssScore(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  VulnInfo
 | 
			
		||||
		out string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss3Score: 8.0,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
						// Severity is NIOT included in NVD
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: "8.3 HIGH (jvn)",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: VulnInfo{
 | 
			
		||||
				CveContents: CveContents{
 | 
			
		||||
					JVN: {
 | 
			
		||||
						Type:       JVN,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.3,
 | 
			
		||||
					},
 | 
			
		||||
					RedHat: {
 | 
			
		||||
						Type:       RedHat,
 | 
			
		||||
						Severity:   "HIGH",
 | 
			
		||||
						Cvss2Score: 8.0,
 | 
			
		||||
						Cvss3Score: 9.9,
 | 
			
		||||
					},
 | 
			
		||||
					NVD: {
 | 
			
		||||
						Type:       NVD,
 | 
			
		||||
						Cvss2Score: 8.1,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: "9.9 HIGH (redhat)",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		actual := tt.in.FormatMaxCvssScore()
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, actual) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSortPackageStatues(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  PackageStatuses
 | 
			
		||||
		out PackageStatuses
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: PackageStatuses{
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
			},
 | 
			
		||||
			out: PackageStatuses{
 | 
			
		||||
				{Name: "a"},
 | 
			
		||||
				{Name: "b"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		tt.in.Sort()
 | 
			
		||||
		if !reflect.DeepEqual(tt.in, tt.out) {
 | 
			
		||||
			t.Errorf("\nexpected: %v\n  actual: %v\n", tt.out, tt.in)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										266
									
								
								oval/debian.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,266 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DebianBase is the base struct of Debian and Ubuntu
 | 
			
		||||
type DebianBase struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ovalContent := *o.convertToModel(&defPacks.def)
 | 
			
		||||
	ovalContent.Type = models.NewCveContentType(o.family)
 | 
			
		||||
	vinfo, ok := r.ScannedCves[defPacks.def.Debian.CveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Debian.CveID,
 | 
			
		||||
			Confidence:  models.OvalMatch,
 | 
			
		||||
			CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cveContents := vinfo.CveContents
 | 
			
		||||
		ctype := models.NewCveContentType(o.family)
 | 
			
		||||
		if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
			util.Log.Debugf("%s OVAL will be overwritten",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("%s is also detected by OVAL",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
			vinfo.Confidence = models.OvalMatch
 | 
			
		||||
		}
 | 
			
		||||
		cveContents[ctype] = ovalContent
 | 
			
		||||
		vinfo.CveContents = cveContents
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
	for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
		defPacks.actuallyAffectedPackNames[pack.Name] = true
 | 
			
		||||
	}
 | 
			
		||||
	vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	r.ScannedCves[defPacks.def.Debian.CveID] = vinfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o DebianBase) convertToModel(def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	for _, r := range def.References {
 | 
			
		||||
		refs = append(refs, models.Reference{
 | 
			
		||||
			Link:   r.RefURL,
 | 
			
		||||
			Source: r.Source,
 | 
			
		||||
			RefID:  r.RefID,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		CveID:      def.Debian.CveID,
 | 
			
		||||
		Title:      def.Title,
 | 
			
		||||
		Summary:    def.Description,
 | 
			
		||||
		Severity:   def.Advisory.Severity,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Debian is the interface for Debian OVAL
 | 
			
		||||
type Debian struct {
 | 
			
		||||
	DebianBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian creates OVAL client for Debian
 | 
			
		||||
func NewDebian() Debian {
 | 
			
		||||
	return Debian{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Debian,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Debian) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
 | 
			
		||||
	//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
	// Add linux and set the version of running kernel to search OVAL.
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		r.Packages["linux"] = models.Package{
 | 
			
		||||
			Name:    "linux",
 | 
			
		||||
			Version: r.RunningKernel.Version,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(r.Packages, "linux")
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		// Remove linux added above to search for oval
 | 
			
		||||
		// linux is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if _, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = true
 | 
			
		||||
			delete(defPacks.actuallyAffectedPackNames, "linux")
 | 
			
		||||
			for i, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == "linux" {
 | 
			
		||||
					p.Name = linuxImage
 | 
			
		||||
					defPacks.def.AffectedPacks[i] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.Debian]; ok {
 | 
			
		||||
			cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.Debian] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Ubuntu is the interface for Debian OVAL
 | 
			
		||||
type Ubuntu struct {
 | 
			
		||||
	DebianBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUbuntu creates OVAL client for Debian
 | 
			
		||||
func NewUbuntu() Ubuntu {
 | 
			
		||||
	return Ubuntu{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Ubuntu,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Ubuntu) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	ovalKernelImageNames := []string{
 | 
			
		||||
		"linux-aws",
 | 
			
		||||
		"linux-azure",
 | 
			
		||||
		"linux-flo",
 | 
			
		||||
		"linux-gcp",
 | 
			
		||||
		"linux-gke",
 | 
			
		||||
		"linux-goldfish",
 | 
			
		||||
		"linux-hwe",
 | 
			
		||||
		"linux-hwe-edge",
 | 
			
		||||
		"linux-kvm",
 | 
			
		||||
		"linux-mako",
 | 
			
		||||
		"linux-raspi2",
 | 
			
		||||
		"linux-snapdragon",
 | 
			
		||||
	}
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
 | 
			
		||||
	found := false
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		for _, n := range ovalKernelImageNames {
 | 
			
		||||
			if _, ok := r.Packages[n]; ok {
 | 
			
		||||
				v, ok := r.Packages[linuxImage]
 | 
			
		||||
				if ok {
 | 
			
		||||
					// Set running kernel version
 | 
			
		||||
					p := r.Packages[n]
 | 
			
		||||
					p.Version = v.Version
 | 
			
		||||
					p.NewVersion = v.NewVersion
 | 
			
		||||
					r.Packages[n] = p
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Warnf("Running kernel image %s is not found: %s",
 | 
			
		||||
						linuxImage, r.RunningKernel.Version)
 | 
			
		||||
				}
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			// linux-generic is described as "linux" in Ubuntu's oval.
 | 
			
		||||
			// Add "linux" and set the version of running kernel to search OVAL.
 | 
			
		||||
			v, ok := r.Packages[linuxImage]
 | 
			
		||||
			if ok {
 | 
			
		||||
				r.Packages["linux"] = models.Package{
 | 
			
		||||
					Name:       "linux",
 | 
			
		||||
					Version:    v.Version,
 | 
			
		||||
					NewVersion: v.NewVersion,
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Warnf("%s is not found. Running: %s",
 | 
			
		||||
					linuxImage, r.RunningKernel.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !found {
 | 
			
		||||
		delete(r.Packages, "linux")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
 | 
			
		||||
		// Remove "linux" added above to search for oval
 | 
			
		||||
		// "linux" is not a real package name (key of affected packages in OVAL)
 | 
			
		||||
		if _, ok := defPacks.actuallyAffectedPackNames["linux"]; !found && ok {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[linuxImage] = true
 | 
			
		||||
			delete(defPacks.actuallyAffectedPackNames, "linux")
 | 
			
		||||
			for i, p := range defPacks.def.AffectedPacks {
 | 
			
		||||
				if p.Name == "linux" {
 | 
			
		||||
					p.Name = linuxImage
 | 
			
		||||
					defPacks.def.AffectedPacks[i] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		if cont, ok := vuln.CveContents[models.Ubuntu]; ok {
 | 
			
		||||
			cont.SourceLink = "http://people.ubuntu.com/~ubuntu-security/cve/" + cont.CveID
 | 
			
		||||
			vuln.CveContents[models.Ubuntu] = cont
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								oval/debian_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,75 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPackNamesOfUpdateDebian(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       models.ScanResult
 | 
			
		||||
		defPacks defPacks
 | 
			
		||||
		out      models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "packA"}},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			defPacks: defPacks{
 | 
			
		||||
				def: ovalmodels.Definition{
 | 
			
		||||
					Debian: ovalmodels.Debian{
 | 
			
		||||
						CveID: "CVE-2000-1000",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
					"packB": true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		Debian{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		if !reflect.DeepEqual(a, e) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										150
									
								
								oval/oval.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,150 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	CheckHTTPHealth() error
 | 
			
		||||
	FillWithOval(r *models.ScanResult) error
 | 
			
		||||
 | 
			
		||||
	// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
	CheckIfOvalFetched(string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(string, string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	family string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", config.Conf.OvalDBURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return fmt.Errorf("Failed to request to OVAL server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		count, err := ovaldb.CountDefs(osFamily, release)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, fmt.Errorf("Failed to count OVAL defs: %s, %s, %v",
 | 
			
		||||
				osFamily, release, err)
 | 
			
		||||
		}
 | 
			
		||||
		return 0 < count, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "count", osFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
			errs, url, resp)
 | 
			
		||||
	}
 | 
			
		||||
	count := 0
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &count); err != nil {
 | 
			
		||||
		return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return 0 < count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	var lastModified time.Time
 | 
			
		||||
	if !b.isFetchViaHTTP() {
 | 
			
		||||
		var ovaldb db.DB
 | 
			
		||||
		if ovaldb, err = db.NewDB(
 | 
			
		||||
			osFamily,
 | 
			
		||||
			config.Conf.OvalDBType,
 | 
			
		||||
			config.Conf.OvalDBPath,
 | 
			
		||||
			config.Conf.DebugSQL,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer ovaldb.CloseDB()
 | 
			
		||||
		lastModified = ovaldb.GetLastModified(osFamily, release)
 | 
			
		||||
	} else {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.OvalDBURL, "lastmodified", osFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return false, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
 | 
			
		||||
			return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
				body, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	major := strings.Split(release, ".")[0]
 | 
			
		||||
	since := time.Now()
 | 
			
		||||
	since = since.AddDate(0, 0, -3)
 | 
			
		||||
	if lastModified.Before(since) {
 | 
			
		||||
		util.Log.Warnf("OVAL for %s %s is old, last modified is %s. It's recommended to update OVAL to improve scanning accuracy. How to update OVAL database, see https://github.com/kotakanbe/goval-dictionary#usage",
 | 
			
		||||
			osFamily, major, lastModified)
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("OVAL is fresh: %s %s ", osFamily, major)
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (b Base) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of OvalDBType is sqlite3
 | 
			
		||||
	return config.Conf.OvalDBURL != "" && config.Conf.OvalDBType == "sqlite3"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										224
									
								
								oval/redhat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,224 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RedHatBase is the base struct for RedHat and CentOS
 | 
			
		||||
type RedHatBase struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o RedHatBase) FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if o.isFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(
 | 
			
		||||
			o.family, r.Release, r.Packages); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, vuln := range r.ScannedCves {
 | 
			
		||||
		switch models.NewCveContentType(o.family) {
 | 
			
		||||
		case models.RedHat:
 | 
			
		||||
			if cont, ok := vuln.CveContents[models.RedHat]; ok {
 | 
			
		||||
				cont.SourceLink = "https://access.redhat.com/security/cve/" + cont.CveID
 | 
			
		||||
				vuln.CveContents[models.RedHat] = cont
 | 
			
		||||
			}
 | 
			
		||||
		case models.Oracle:
 | 
			
		||||
			if cont, ok := vuln.CveContents[models.Oracle]; ok {
 | 
			
		||||
				cont.SourceLink = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", cont.CveID)
 | 
			
		||||
				vuln.CveContents[models.Oracle] = cont
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	ctype := models.NewCveContentType(o.family)
 | 
			
		||||
	for _, cve := range defPacks.def.Advisory.Cves {
 | 
			
		||||
		ovalContent := *o.convertToModel(cve.CveID, &defPacks.def)
 | 
			
		||||
		vinfo, ok := r.ScannedCves[cve.CveID]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			util.Log.Debugf("%s is newly detected by OVAL", cve.CveID)
 | 
			
		||||
			vinfo = models.VulnInfo{
 | 
			
		||||
				CveID:       cve.CveID,
 | 
			
		||||
				Confidence:  models.OvalMatch,
 | 
			
		||||
				CveContents: models.NewCveContents(ovalContent),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			cveContents := vinfo.CveContents
 | 
			
		||||
			if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
				util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("%s also detected by OVAL", cve.CveID)
 | 
			
		||||
				cveContents = models.CveContents{}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if vinfo.Confidence.Score < models.OvalMatch.Score {
 | 
			
		||||
				vinfo.Confidence = models.OvalMatch
 | 
			
		||||
			}
 | 
			
		||||
			cveContents[ctype] = ovalContent
 | 
			
		||||
			vinfo.CveContents = cveContents
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
 | 
			
		||||
		for _, pack := range vinfo.AffectedPackages {
 | 
			
		||||
			defPacks.actuallyAffectedPackNames[pack.Name] = true
 | 
			
		||||
		}
 | 
			
		||||
		vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
 | 
			
		||||
		vinfo.AffectedPackages.Sort()
 | 
			
		||||
		r.ScannedCves[cve.CveID] = vinfo
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *models.CveContent {
 | 
			
		||||
	for _, cve := range def.Advisory.Cves {
 | 
			
		||||
		if cve.CveID != cveID {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var refs []models.Reference
 | 
			
		||||
		for _, r := range def.References {
 | 
			
		||||
			refs = append(refs, models.Reference{
 | 
			
		||||
				Link:   r.RefURL,
 | 
			
		||||
				Source: r.Source,
 | 
			
		||||
				RefID:  r.RefID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		score2, vec2 := o.parseCvss2(cve.Cvss2)
 | 
			
		||||
		score3, vec3 := o.parseCvss3(cve.Cvss3)
 | 
			
		||||
 | 
			
		||||
		severity := def.Advisory.Severity
 | 
			
		||||
		if cve.Impact != "" {
 | 
			
		||||
			severity = cve.Impact
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return &models.CveContent{
 | 
			
		||||
			Type:         models.NewCveContentType(o.family),
 | 
			
		||||
			CveID:        cve.CveID,
 | 
			
		||||
			Title:        def.Title,
 | 
			
		||||
			Summary:      def.Description,
 | 
			
		||||
			Severity:     severity,
 | 
			
		||||
			Cvss2Score:   score2,
 | 
			
		||||
			Cvss2Vector:  vec2,
 | 
			
		||||
			Cvss3Score:   score3,
 | 
			
		||||
			Cvss3Vector:  vec3,
 | 
			
		||||
			References:   refs,
 | 
			
		||||
			CweID:        cve.Cwe,
 | 
			
		||||
			Published:    def.Advisory.Issued,
 | 
			
		||||
			LastModified: def.Advisory.Updated,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseCvss2 divide CVSSv2 string into score and vector
 | 
			
		||||
// 5/AV:N/AC:L/Au:N/C:N/I:N/A:P
 | 
			
		||||
func (o RedHatBase) parseCvss2(scoreVector string) (score float64, vector string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ss := strings.Split(scoreVector, "/")
 | 
			
		||||
	if 1 < len(ss) {
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:len(ss)], "/")
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParseCvss3 divide CVSSv3 string into score and vector
 | 
			
		||||
// 5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L
 | 
			
		||||
func (o RedHatBase) parseCvss3(scoreVector string) (score float64, vector string) {
 | 
			
		||||
	var err error
 | 
			
		||||
	ss := strings.Split(scoreVector, "/CVSS:3.0/")
 | 
			
		||||
	if 1 < len(ss) {
 | 
			
		||||
		if score, err = strconv.ParseFloat(ss[0], 64); err != nil {
 | 
			
		||||
			return 0, ""
 | 
			
		||||
		}
 | 
			
		||||
		return score, strings.Join(ss[1:len(ss)], "/")
 | 
			
		||||
	}
 | 
			
		||||
	return 0, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RedHat is the interface for RedhatBase OVAL
 | 
			
		||||
type RedHat struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat creates OVAL client for Redhat
 | 
			
		||||
func NewRedhat() RedHat {
 | 
			
		||||
	return RedHat{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.RedHat,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CentOS is the interface for CentOS OVAL
 | 
			
		||||
type CentOS struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCentOS creates OVAL client for CentOS
 | 
			
		||||
func NewCentOS() CentOS {
 | 
			
		||||
	return CentOS{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.CentOS,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Oracle is the interface for CentOS OVAL
 | 
			
		||||
type Oracle struct {
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewOracle creates OVAL client for Oracle
 | 
			
		||||
func NewOracle() Oracle {
 | 
			
		||||
	return Oracle{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Oracle,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										145
									
								
								oval/redhat_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,145 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseCvss2(t *testing.T) {
 | 
			
		||||
	type out struct {
 | 
			
		||||
		score  float64
 | 
			
		||||
		vector string
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out out
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: "5/AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  5.0,
 | 
			
		||||
				vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: "",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  0,
 | 
			
		||||
				vector: "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, v := RedHatBase{}.parseCvss2(tt.in)
 | 
			
		||||
		if s != tt.out.score || v != tt.out.vector {
 | 
			
		||||
			t.Errorf("\nexpected: %f, %s\n  actual: %f, %s",
 | 
			
		||||
				tt.out.score, tt.out.vector, s, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseCvss3(t *testing.T) {
 | 
			
		||||
	type out struct {
 | 
			
		||||
		score  float64
 | 
			
		||||
		vector string
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  string
 | 
			
		||||
		out out
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: "5.6/CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  5.6,
 | 
			
		||||
				vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: "",
 | 
			
		||||
			out: out{
 | 
			
		||||
				score:  0,
 | 
			
		||||
				vector: "",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		s, v := RedHatBase{}.parseCvss3(tt.in)
 | 
			
		||||
		if s != tt.out.score || v != tt.out.vector {
 | 
			
		||||
			t.Errorf("\nexpected: %f, %s\n  actual: %f, %s",
 | 
			
		||||
				tt.out.score, tt.out.vector, s, v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPackNamesOfUpdate(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       models.ScanResult
 | 
			
		||||
		defPacks defPacks
 | 
			
		||||
		out      models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "packA"}},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			defPacks: defPacks{
 | 
			
		||||
				def: ovalmodels.Definition{
 | 
			
		||||
					Advisory: ovalmodels.Advisory{
 | 
			
		||||
						Cves: []ovalmodels.Cve{
 | 
			
		||||
							{
 | 
			
		||||
								CveID: "CVE-2000-1000",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
					"packB": true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2000-1000": models.VulnInfo{
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{
 | 
			
		||||
							{Name: "packA"},
 | 
			
		||||
							{Name: "packB"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		RedHat{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		a := tt.in.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
		if !reflect.DeepEqual(a, e) {
 | 
			
		||||
			t.Errorf("[%d] expected: %v\n  actual: %v\n", i, e, a)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										332
									
								
								oval/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,332 @@
 | 
			
		||||
/* 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 oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	debver "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	rpmver "github.com/knqyf263/go-rpm-version"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovallog "github.com/kotakanbe/goval-dictionary/log"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ovalResult struct {
 | 
			
		||||
	entries []defPacks
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type defPacks struct {
 | 
			
		||||
	def                       ovalmodels.Definition
 | 
			
		||||
	actuallyAffectedPackNames map[string]bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e defPacks) toPackStatuses(family string, packs models.Packages) (ps models.PackageStatuses) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.Ubuntu:
 | 
			
		||||
		packNotFixedYet := map[string]bool{}
 | 
			
		||||
		for _, p := range e.def.AffectedPacks {
 | 
			
		||||
			packNotFixedYet[p.Name] = p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		for k := range e.actuallyAffectedPackNames {
 | 
			
		||||
			ps = append(ps, models.PackageStatus{
 | 
			
		||||
				Name:        k,
 | 
			
		||||
				NotFixedYet: packNotFixedYet[k],
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	case config.CentOS, config.Debian:
 | 
			
		||||
		// There are many packages that has been fixed in RedHat, but not been fixed in CentOS
 | 
			
		||||
		for name := range e.actuallyAffectedPackNames {
 | 
			
		||||
			pack, ok := packs[name]
 | 
			
		||||
			if !ok {
 | 
			
		||||
				util.Log.Warnf("Faild to find in Package list: %s", name)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ovalPackVer := ""
 | 
			
		||||
			for _, p := range e.def.AffectedPacks {
 | 
			
		||||
				if p.Name == name {
 | 
			
		||||
					ovalPackVer = p.Version
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if ovalPackVer == "" {
 | 
			
		||||
				util.Log.Warnf("Faild to find in Oval Package list: %s", name)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if pack.NewVersion == "" {
 | 
			
		||||
				// compare version: installed vs oval
 | 
			
		||||
				vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", pack.Version, pack.Release))
 | 
			
		||||
				verb := rpmver.NewVersion(ovalPackVer)
 | 
			
		||||
				notFixedYet := false
 | 
			
		||||
				if vera.LessThan(verb) {
 | 
			
		||||
					notFixedYet = true
 | 
			
		||||
				}
 | 
			
		||||
				ps = append(ps, models.PackageStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					NotFixedYet: notFixedYet,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				// compare version: newVer vs oval
 | 
			
		||||
				packNewVer := fmt.Sprintf("%s-%s", pack.NewVersion, pack.NewRelease)
 | 
			
		||||
				vera := rpmver.NewVersion(packNewVer)
 | 
			
		||||
				verb := rpmver.NewVersion(ovalPackVer)
 | 
			
		||||
				notFixedYet := false
 | 
			
		||||
				if vera.LessThan(verb) {
 | 
			
		||||
					notFixedYet = true
 | 
			
		||||
				}
 | 
			
		||||
				ps = append(ps, models.PackageStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					NotFixedYet: notFixedYet,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		for k := range e.actuallyAffectedPackNames {
 | 
			
		||||
			ps = append(ps, models.PackageStatus{
 | 
			
		||||
				Name: k,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (e *ovalResult) upsert(def ovalmodels.Definition, packName string) (upserted bool) {
 | 
			
		||||
	for i, entry := range e.entries {
 | 
			
		||||
		if entry.def.DefinitionID == def.DefinitionID {
 | 
			
		||||
			e.entries[i].actuallyAffectedPackNames[packName] = true
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	e.entries = append(e.entries, defPacks{
 | 
			
		||||
		def: def,
 | 
			
		||||
		actuallyAffectedPackNames: map[string]bool{packName: true},
 | 
			
		||||
	})
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	pack models.Package
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	pack *models.Package
 | 
			
		||||
	defs []ovalmodels.Definition
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getDefsByPackNameViaHTTP fetches OVAL information via HTTP
 | 
			
		||||
func getDefsByPackNameViaHTTP(r *models.ScanResult) (
 | 
			
		||||
	relatedDefs ovalResult, err error) {
 | 
			
		||||
 | 
			
		||||
	reqChan := make(chan request, len(r.Packages))
 | 
			
		||||
	resChan := make(chan response, len(r.Packages))
 | 
			
		||||
	errChan := make(chan error, len(r.Packages))
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				pack: pack,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range r.Packages {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					config.Conf.OvalDBURL,
 | 
			
		||||
					"packs",
 | 
			
		||||
					r.Family,
 | 
			
		||||
					r.Release,
 | 
			
		||||
					req.pack.Name,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, &req.pack, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for range r.Packages {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			for _, def := range res.defs {
 | 
			
		||||
				for _, p := range def.AffectedPacks {
 | 
			
		||||
					if res.pack.Name != p.Name {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if p.NotFixedYet {
 | 
			
		||||
						relatedDefs.upsert(def, p.Name)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if less, err := lessThan(r.Family, *res.pack, p); err != nil {
 | 
			
		||||
						util.Log.Debugf("Failed to parse versions: %s", err)
 | 
			
		||||
						util.Log.Debugf("%#v\n%#v", *res.pack, p)
 | 
			
		||||
					} else if less {
 | 
			
		||||
						relatedDefs.upsert(def, p.Name)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return relatedDefs, fmt.Errorf("Failed to fetch OVAL. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, pack *models.Package, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	count, retryMax := 0, 3
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP 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)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- fmt.Errorf("HRetry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defs := []ovalmodels.Definition{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &defs); err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		pack: pack,
 | 
			
		||||
		defs: defs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDefsByPackNameFromOvalDB(family, osRelease string,
 | 
			
		||||
	installedPacks models.Packages) (relatedDefs ovalResult, err error) {
 | 
			
		||||
 | 
			
		||||
	ovallog.Initialize(config.Conf.LogDir)
 | 
			
		||||
	path := config.Conf.OvalDBURL
 | 
			
		||||
	if config.Conf.OvalDBType == "sqlite3" {
 | 
			
		||||
		path = config.Conf.OvalDBPath
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("Open oval-dictionary db (%s): %s", config.Conf.OvalDBType, path)
 | 
			
		||||
 | 
			
		||||
	var ovaldb db.DB
 | 
			
		||||
	if ovaldb, err = db.NewDB(
 | 
			
		||||
		family,
 | 
			
		||||
		config.Conf.OvalDBType,
 | 
			
		||||
		path,
 | 
			
		||||
		config.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer ovaldb.CloseDB()
 | 
			
		||||
	for _, installedPack := range installedPacks {
 | 
			
		||||
		definitions, err := ovaldb.GetByPackName(osRelease, installedPack.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package name: %v", family, err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, def := range definitions {
 | 
			
		||||
			for _, ovalPack := range def.AffectedPacks {
 | 
			
		||||
				if installedPack.Name != ovalPack.Name {
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if ovalPack.NotFixedYet {
 | 
			
		||||
					relatedDefs.upsert(def, installedPack.Name)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				less, err := lessThan(family, installedPack, ovalPack)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					util.Log.Debugf("Failed to parse versions: %s", err)
 | 
			
		||||
					util.Log.Debugf("%#v\n%#v", installedPack, ovalPack)
 | 
			
		||||
				} else if less {
 | 
			
		||||
					relatedDefs.upsert(def, installedPack.Name)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bool, error) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case config.Debian, config.Ubuntu:
 | 
			
		||||
		vera, err := debver.NewVersion(packA.Version)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		verb, err := debver.NewVersion(packB.Version)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	case config.RedHat, config.CentOS, config.Oracle:
 | 
			
		||||
		vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", packA.Version, packA.Release))
 | 
			
		||||
		verb := rpmver.NewVersion(packB.Version)
 | 
			
		||||
		return vera.LessThan(verb), nil
 | 
			
		||||
	}
 | 
			
		||||
	return false, fmt.Errorf("Package version comparison not supported: %s", family)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										246
									
								
								oval/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,246 @@
 | 
			
		||||
package oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUpsert(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		res      ovalResult
 | 
			
		||||
		def      ovalmodels.Definition
 | 
			
		||||
		packName string
 | 
			
		||||
		upserted bool
 | 
			
		||||
		out      ovalResult
 | 
			
		||||
	}{
 | 
			
		||||
		//insert
 | 
			
		||||
		{
 | 
			
		||||
			res: ovalResult{},
 | 
			
		||||
			def: ovalmodels.Definition{
 | 
			
		||||
				DefinitionID: "1111",
 | 
			
		||||
			},
 | 
			
		||||
			packName: "pack1",
 | 
			
		||||
			upserted: false,
 | 
			
		||||
			out: ovalResult{
 | 
			
		||||
				[]defPacks{
 | 
			
		||||
					{
 | 
			
		||||
						def: ovalmodels.Definition{
 | 
			
		||||
							DefinitionID: "1111",
 | 
			
		||||
						},
 | 
			
		||||
						actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
							"pack1": true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//update
 | 
			
		||||
		{
 | 
			
		||||
			res: ovalResult{
 | 
			
		||||
				[]defPacks{
 | 
			
		||||
					{
 | 
			
		||||
						def: ovalmodels.Definition{
 | 
			
		||||
							DefinitionID: "1111",
 | 
			
		||||
						},
 | 
			
		||||
						actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
							"pack1": true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						def: ovalmodels.Definition{
 | 
			
		||||
							DefinitionID: "2222",
 | 
			
		||||
						},
 | 
			
		||||
						actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
							"pack3": true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			def: ovalmodels.Definition{
 | 
			
		||||
				DefinitionID: "1111",
 | 
			
		||||
			},
 | 
			
		||||
			packName: "pack2",
 | 
			
		||||
			upserted: true,
 | 
			
		||||
			out: ovalResult{
 | 
			
		||||
				[]defPacks{
 | 
			
		||||
					{
 | 
			
		||||
						def: ovalmodels.Definition{
 | 
			
		||||
							DefinitionID: "1111",
 | 
			
		||||
						},
 | 
			
		||||
						actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
							"pack1": true,
 | 
			
		||||
							"pack2": true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						def: ovalmodels.Definition{
 | 
			
		||||
							DefinitionID: "2222",
 | 
			
		||||
						},
 | 
			
		||||
						actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
							"pack3": true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		upserted := tt.res.upsert(tt.def, tt.packName)
 | 
			
		||||
		if tt.upserted != upserted {
 | 
			
		||||
			t.Errorf("[%d]\nexpected: %t\n  actual: %t\n", i, tt.upserted, upserted)
 | 
			
		||||
		}
 | 
			
		||||
		if !reflect.DeepEqual(tt.out, tt.res) {
 | 
			
		||||
			t.Errorf("[%d]\nexpected: %v\n  actual: %v\n", i, tt.out, tt.res)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDefpacksToPackStatuses(t *testing.T) {
 | 
			
		||||
	type in struct {
 | 
			
		||||
		dp     defPacks
 | 
			
		||||
		family string
 | 
			
		||||
		packs  models.Packages
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  in
 | 
			
		||||
		out models.PackageStatuses
 | 
			
		||||
	}{
 | 
			
		||||
		// Ubuntu
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "ubuntu",
 | 
			
		||||
				packs:  models.Packages{},
 | 
			
		||||
				dp: defPacks{
 | 
			
		||||
					def: ovalmodels.Definition{
 | 
			
		||||
						AffectedPacks: []ovalmodels.Package{
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "a",
 | 
			
		||||
								NotFixedYet: true,
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:        "b",
 | 
			
		||||
								NotFixedYet: false,
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
						"a": true,
 | 
			
		||||
						"b": true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.PackageStatuses{
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "a",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "b",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// RedHat, Amazon, Debian
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "redhat",
 | 
			
		||||
				packs:  models.Packages{},
 | 
			
		||||
				dp: defPacks{
 | 
			
		||||
					def: ovalmodels.Definition{
 | 
			
		||||
						AffectedPacks: []ovalmodels.Package{
 | 
			
		||||
							{
 | 
			
		||||
								Name: "a",
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name: "b",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
						"a": true,
 | 
			
		||||
						"b": true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.PackageStatuses{
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "a",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "b",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		// CentOS
 | 
			
		||||
		{
 | 
			
		||||
			in: in{
 | 
			
		||||
				family: "centos",
 | 
			
		||||
				packs: models.Packages{
 | 
			
		||||
					"a": {Version: "1.0.0"},
 | 
			
		||||
					"b": {
 | 
			
		||||
						Version:    "1.0.0",
 | 
			
		||||
						NewVersion: "2.0.0",
 | 
			
		||||
					},
 | 
			
		||||
					"c": {
 | 
			
		||||
						Version:    "1.0.0",
 | 
			
		||||
						NewVersion: "1.5.0",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				dp: defPacks{
 | 
			
		||||
					def: ovalmodels.Definition{
 | 
			
		||||
						AffectedPacks: []ovalmodels.Package{
 | 
			
		||||
							{
 | 
			
		||||
								Name:    "a",
 | 
			
		||||
								Version: "1.0.1",
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:    "b",
 | 
			
		||||
								Version: "1.5.0",
 | 
			
		||||
							},
 | 
			
		||||
							{
 | 
			
		||||
								Name:    "c",
 | 
			
		||||
								Version: "2.0.0",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					actuallyAffectedPackNames: map[string]bool{
 | 
			
		||||
						"a": true,
 | 
			
		||||
						"b": true,
 | 
			
		||||
						"c": true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.PackageStatuses{
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "a",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "b",
 | 
			
		||||
					NotFixedYet: false,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:        "c",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := tt.in.dp.toPackStatuses(tt.in.family, tt.in.packs)
 | 
			
		||||
		sort.Slice(actual, func(i, j int) bool {
 | 
			
		||||
			return actual[i].Name < actual[j].Name
 | 
			
		||||
		})
 | 
			
		||||
		if !reflect.DeepEqual(actual, tt.out) {
 | 
			
		||||
			t.Errorf("[%d]\nexpected: %v\n  actual: %v\n", i, tt.out, actual)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -24,7 +24,7 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Azure/azure-storage-go"
 | 
			
		||||
	storage "github.com/Azure/azure-sdk-for-go/storage"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
@@ -139,13 +139,9 @@ func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error {
 | 
			
		||||
		k = k + ".gz"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := cli.CreateBlockBlobFromReader(
 | 
			
		||||
		c.Conf.AzureContainer,
 | 
			
		||||
		k,
 | 
			
		||||
		uint64(len(b)),
 | 
			
		||||
		bytes.NewReader(b),
 | 
			
		||||
		map[string]string{},
 | 
			
		||||
	); err != nil {
 | 
			
		||||
	ref := cli.GetContainerReference(c.Conf.AzureContainer)
 | 
			
		||||
	blob := ref.GetBlobReference(k)
 | 
			
		||||
	if err := blob.CreateBlockBlobFromReader(bytes.NewReader(b), nil); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
			c.Conf.AzureContainer, k, err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,12 @@ 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 cveapi
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
@@ -32,6 +31,7 @@ import (
 | 
			
		||||
	cveconfig "github.com/kotakanbe/go-cve-dictionary/config"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE disctionary service.
 | 
			
		||||
@@ -46,10 +46,10 @@ func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	if config.Conf.CveDBURL == "" || config.Conf.CveDBType == "mysql" || config.Conf.CveDBType == "postgres" {
 | 
			
		||||
func (api cvedictClient) CheckHealth() error {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDBType)
 | 
			
		||||
		return true, nil
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.initialize()
 | 
			
		||||
@@ -59,9 +59,10 @@ func (api cvedictClient) CheckHealth() (ok bool, err error) {
 | 
			
		||||
	resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", url, errs)
 | 
			
		||||
		return fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return true, nil
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
@@ -69,9 +70,8 @@ type response struct {
 | 
			
		||||
	CveDetail cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
	switch config.Conf.CveDBType {
 | 
			
		||||
	case "sqlite3", "mysql", "postgres":
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	if !api.isFetchViaHTTP() {
 | 
			
		||||
		return api.FetchCveDetailsFromCveDB(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -112,28 +112,26 @@ func (api cvedictClient) FetchCveDetails(cveIDs []string) (cveDetails cve.CveDet
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			if len(res.CveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
				cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
					CveID: res.Key,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, res.CveDetail)
 | 
			
		||||
				cveDetails = append(cveDetails, &res.CveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return []cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
			return []*cve.CveDetail{}, fmt.Errorf("Timeout Fetching CVE")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to fetch CVE. err: %v", errs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails cve.CveDetails, err error) {
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
@@ -142,23 +140,33 @@ func (api cvedictClient) FetchCveDetailsFromCveDB(cveIDs []string) (cveDetails c
 | 
			
		||||
		cveconfig.Conf.DBPath = config.Conf.CveDBURL
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Opening DB (%s).", driver.Name())
 | 
			
		||||
	if err := driver.OpenDB(
 | 
			
		||||
		cveconfig.Conf.DBType,
 | 
			
		||||
		cveconfig.Conf.DBPath,
 | 
			
		||||
		cveconfig.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail := cvedb.Get(cveID)
 | 
			
		||||
		cveDetail := driver.Get(cveID)
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, cve.CveDetail{
 | 
			
		||||
			cveDetails = append(cveDetails, &cve.CveDetail{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// order by CVE ID desc
 | 
			
		||||
	sort.Sort(cveDetails)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -170,20 +178,25 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", errs, url, resp)
 | 
			
		||||
			return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v",
 | 
			
		||||
				errs, url, resp)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP 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)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cveDetail := cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
		errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s",
 | 
			
		||||
			body, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		key,
 | 
			
		||||
@@ -196,29 +209,37 @@ type responseGetCveDetailByCpeName struct {
 | 
			
		||||
	CveDetails []cve.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
	switch config.Conf.CveDBType {
 | 
			
		||||
	case "sqlite3", "mysql", "postgres":
 | 
			
		||||
		return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
func (api cvedictClient) isFetchViaHTTP() bool {
 | 
			
		||||
	// Default value of CveDBType is sqlite3
 | 
			
		||||
	if config.Conf.CveDBURL != "" && config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
	url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	query := map[string]string{"name": cpeName}
 | 
			
		||||
	util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
	return api.httpPost(cpeName, url, query)
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cve.CveDetail, error) {
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(cpeName string) ([]*cve.CveDetail, error) {
 | 
			
		||||
	if api.isFetchViaHTTP() {
 | 
			
		||||
		api.baseURL = config.Conf.CveDBURL
 | 
			
		||||
		url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return []*cve.CveDetail{}, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		query := map[string]string{"name": cpeName}
 | 
			
		||||
		util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
		return api.httpPost(cpeName, url, query)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return api.FetchCveDetailsByCpeNameFromDB(cpeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]*cve.CveDetail, error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		//  req := gorequest.New().SetDebug(config.Conf.Debug).Post(url)
 | 
			
		||||
		req := gorequest.New().Post(url)
 | 
			
		||||
		for key := range query {
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
@@ -233,18 +254,18 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("HTTP Error %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveDetails := []cve.CveDetail{}
 | 
			
		||||
	cveDetails := []*cve.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.CveDetail, error) {
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) (cveDetails []*cve.CveDetail, err error) {
 | 
			
		||||
	util.Log.Debugf("open cve-dictionary db (%s)", config.Conf.CveDBType)
 | 
			
		||||
	cveconfig.Conf.DBType = config.Conf.CveDBType
 | 
			
		||||
	if config.Conf.CveDBType == "sqlite3" {
 | 
			
		||||
@@ -254,9 +275,20 @@ func (api cvedictClient) FetchCveDetailsByCpeNameFromDB(cpeName string) ([]cve.C
 | 
			
		||||
	}
 | 
			
		||||
	cveconfig.Conf.DebugSQL = config.Conf.DebugSQL
 | 
			
		||||
 | 
			
		||||
	if err := cvedb.OpenDB(); err != nil {
 | 
			
		||||
		return []cve.CveDetail{},
 | 
			
		||||
	var driver cvedb.DB
 | 
			
		||||
	if driver, err = cvedb.NewDB(cveconfig.Conf.DBType); err != nil {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		return []*cve.CveDetail{}, fmt.Errorf("Failed to New DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Debugf("Opening DB (%s).", driver.Name())
 | 
			
		||||
	if err = driver.OpenDB(
 | 
			
		||||
		cveconfig.Conf.DBType,
 | 
			
		||||
		cveconfig.Conf.DBPath,
 | 
			
		||||
		cveconfig.Conf.DebugSQL,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return []*cve.CveDetail{},
 | 
			
		||||
			fmt.Errorf("Failed to open DB. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return cvedb.GetByCpeName(cpeName), nil
 | 
			
		||||
	return driver.GetByCpeName(cpeName), nil
 | 
			
		||||
}
 | 
			
		||||
@@ -35,14 +35,18 @@ type EMailWriter struct{}
 | 
			
		||||
func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
	conf := config.Conf
 | 
			
		||||
	var message string
 | 
			
		||||
	var totalResult models.ScanResult
 | 
			
		||||
	sender := NewEMailSender()
 | 
			
		||||
 | 
			
		||||
	m := map[string]int{}
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if conf.FormatOneEMail {
 | 
			
		||||
			message += formatFullPlainText(r) + "\r\n\r\n"
 | 
			
		||||
			totalResult.KnownCves = append(totalResult.KnownCves, r.KnownCves...)
 | 
			
		||||
			totalResult.UnknownCves = append(totalResult.UnknownCves, r.UnknownCves...)
 | 
			
		||||
 | 
			
		||||
			mm := r.ScannedCves.CountGroupBySeverity()
 | 
			
		||||
			keys := []string{"High", "Medium", "Low", "Unknown"}
 | 
			
		||||
			for _, k := range keys {
 | 
			
		||||
				m[k] += mm[k]
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			var subject string
 | 
			
		||||
			if len(r.Errors) != 0 {
 | 
			
		||||
@@ -50,7 +54,9 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
					conf.EMail.SubjectPrefix, r.ServerInfo())
 | 
			
		||||
			} else {
 | 
			
		||||
				subject = fmt.Sprintf("%s%s %s",
 | 
			
		||||
					conf.EMail.SubjectPrefix, r.ServerInfo(), r.CveSummary())
 | 
			
		||||
					conf.EMail.SubjectPrefix,
 | 
			
		||||
					r.ServerInfo(),
 | 
			
		||||
					r.ScannedCves.FormatCveSummary())
 | 
			
		||||
			}
 | 
			
		||||
			message = formatFullPlainText(r)
 | 
			
		||||
			if err := sender.Send(subject, message); err != nil {
 | 
			
		||||
@@ -59,6 +65,15 @@ func (w EMailWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	summary := ""
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	}
 | 
			
		||||
	summary = fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
 | 
			
		||||
	if conf.FormatOneEMail {
 | 
			
		||||
		message = fmt.Sprintf(
 | 
			
		||||
			`
 | 
			
		||||
@@ -71,9 +86,7 @@ One Line Summary
 | 
			
		||||
			formatOneLineSummary(rs...), message)
 | 
			
		||||
 | 
			
		||||
		subject := fmt.Sprintf("%s %s",
 | 
			
		||||
			conf.EMail.SubjectPrefix,
 | 
			
		||||
			totalResult.CveSummary(),
 | 
			
		||||
		)
 | 
			
		||||
			conf.EMail.SubjectPrefix, summary)
 | 
			
		||||
		return sender.Send(subject, message)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
 
 | 
			
		||||
@@ -58,8 +58,14 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var b []byte
 | 
			
		||||
			if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
			if c.Conf.Debug {
 | 
			
		||||
				if b, err = json.MarshalIndent(r, "", "    "); err != nil {
 | 
			
		||||
					return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if b, err = json.Marshal(r); err != nil {
 | 
			
		||||
					return fmt.Errorf("Failed to Marshal to JSON: %s", err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if err := writeFile(p, b, 0600); err != nil {
 | 
			
		||||
				return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										227
									
								
								report/report.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,227 @@
 | 
			
		||||
/* 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 report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	vulsOpenTag  = "<vulsreport>"
 | 
			
		||||
	vulsCloseTag = "</vulsreport>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillCveInfos fills CVE Detailed Information
 | 
			
		||||
func FillCveInfos(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
 | 
			
		||||
	var filled []models.ScanResult
 | 
			
		||||
	reportedAt := time.Now()
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if c.Conf.RefreshCve || needToRefreshCve(r) {
 | 
			
		||||
			if err := FillCveInfo(&r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			r.Lang = c.Conf.Lang
 | 
			
		||||
			r.ReportedAt = reportedAt
 | 
			
		||||
			r.Config.Report = c.Conf
 | 
			
		||||
			r.Config.Report.Servers = map[string]c.ServerInfo{
 | 
			
		||||
				r.ServerName: c.Conf.Servers[r.ServerName],
 | 
			
		||||
			}
 | 
			
		||||
			if err := overwriteJSONFile(dir, r); err != nil {
 | 
			
		||||
				return nil, fmt.Errorf("Failed to write JSON: %s", err)
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("No need to refresh")
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		previous, err := loadPrevious(filled)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diff, err := diff(filled, previous)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		filled = []models.ScanResult{}
 | 
			
		||||
		for _, r := range diff {
 | 
			
		||||
			if err := fillCveDetail(&r); err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			filled = append(filled, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := []models.ScanResult{}
 | 
			
		||||
	for _, r := range filled {
 | 
			
		||||
		r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
 | 
			
		||||
		r = r.FilterIgnoreCves(c.Conf.Servers[r.ServerName].IgnoreCves)
 | 
			
		||||
		filtered = append(filtered, r)
 | 
			
		||||
	}
 | 
			
		||||
	return filtered, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCveInfo fill scanResult with cve info.
 | 
			
		||||
func FillCveInfo(r *models.ScanResult) error {
 | 
			
		||||
	util.Log.Debugf("need to refresh")
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with OVAL")
 | 
			
		||||
	if err := FillWithOval(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill OVAL information: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("Fill CVE detailed information with CVE-DB")
 | 
			
		||||
	if err := fillWithCveDB(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to fill CVE information: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID := range r.ScannedCves {
 | 
			
		||||
		vinfo := r.ScannedCves[cveID]
 | 
			
		||||
		r.ScannedCves[cveID] = *vinfo.NilToEmpty()
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// fillCveDetail fetches NVD, JVN from CVE Database, and then set to fields.
 | 
			
		||||
func fillCveDetail(r *models.ScanResult) error {
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ds, err := CveClient.FetchCveDetails(cveIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		nvd := models.ConvertNvdToModel(d.CveID, d.Nvd)
 | 
			
		||||
		jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
 | 
			
		||||
		for cveID, vinfo := range r.ScannedCves {
 | 
			
		||||
			if vinfo.CveID == d.CveID {
 | 
			
		||||
				if vinfo.CveContents == nil {
 | 
			
		||||
					vinfo.CveContents = models.CveContents{}
 | 
			
		||||
				}
 | 
			
		||||
				for _, con := range []models.CveContent{*nvd, *jvn} {
 | 
			
		||||
					if !con.Empty() {
 | 
			
		||||
						vinfo.CveContents[con.Type] = con
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				r.ScannedCves[cveID] = vinfo
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillWithCveDB(r *models.ScanResult) error {
 | 
			
		||||
	sInfo := c.Conf.Servers[r.ServerName]
 | 
			
		||||
	if err := fillVulnByCpeNames(sInfo.CpeNames, r.ScannedCves); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := fillCveDetail(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval fetches OVAL database, and then set to fields.
 | 
			
		||||
func FillWithOval(r *models.ScanResult) (err error) {
 | 
			
		||||
	var ovalClient oval.Client
 | 
			
		||||
	var ovalFamily string
 | 
			
		||||
 | 
			
		||||
	// TODO
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case c.Debian:
 | 
			
		||||
		ovalClient = oval.NewDebian()
 | 
			
		||||
		ovalFamily = c.Debian
 | 
			
		||||
	case c.Ubuntu:
 | 
			
		||||
		ovalClient = oval.NewUbuntu()
 | 
			
		||||
		ovalFamily = c.Ubuntu
 | 
			
		||||
	case c.RedHat:
 | 
			
		||||
		ovalClient = oval.NewRedhat()
 | 
			
		||||
		ovalFamily = c.RedHat
 | 
			
		||||
	case c.CentOS:
 | 
			
		||||
		ovalClient = oval.NewCentOS()
 | 
			
		||||
		//use RedHat's OVAL
 | 
			
		||||
		ovalFamily = c.RedHat
 | 
			
		||||
	case c.Oracle:
 | 
			
		||||
		ovalClient = oval.NewOracle()
 | 
			
		||||
		ovalFamily = c.Oracle
 | 
			
		||||
	case c.Amazon, c.Raspbian, c.FreeBSD:
 | 
			
		||||
		return nil
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("OVAL for %s is not implemented yet", r.Family)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ok, err := ovalClient.CheckIfOvalFetched(ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		major := strings.Split(r.Release, ".")[0]
 | 
			
		||||
		util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, major)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = ovalClient.CheckIfOvalFresh(ovalFamily, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := ovalClient.FillWithOval(r); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillVulnByCpeNames(cpeNames []string, scannedVulns models.VulnInfos) error {
 | 
			
		||||
	for _, name := range cpeNames {
 | 
			
		||||
		details, err := CveClient.FetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			if val, ok := scannedVulns[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeNames
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeNames = names
 | 
			
		||||
				val.Confidence = models.CpeNameMatch
 | 
			
		||||
				scannedVulns[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:      detail.CveID,
 | 
			
		||||
					CpeNames:   []string{name},
 | 
			
		||||
					Confidence: models.CpeNameMatch,
 | 
			
		||||
				}
 | 
			
		||||
				scannedVulns[detail.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								report/report_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1 @@
 | 
			
		||||
package report
 | 
			
		||||
@@ -22,6 +22,7 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"path"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aws/aws-sdk-go/aws"
 | 
			
		||||
@@ -147,8 +148,8 @@ func putObject(svc *s3.S3, k string, b []byte) error {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := svc.PutObject(&s3.PutObjectInput{
 | 
			
		||||
		Bucket: &c.Conf.S3Bucket,
 | 
			
		||||
		Key:    &k,
 | 
			
		||||
		Bucket: aws.String(c.Conf.S3Bucket),
 | 
			
		||||
		Key:    aws.String(path.Join(c.Conf.S3ResultsDir, k)),
 | 
			
		||||
		Body:   bytes.NewReader(b),
 | 
			
		||||
	}); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to upload data to %s/%s, %s",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										185
									
								
								report/slack.go
									
									
									
									
									
								
							
							
						
						@@ -24,11 +24,11 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type field struct {
 | 
			
		||||
@@ -45,6 +45,7 @@ type attachment struct {
 | 
			
		||||
	Color     string   `json:"color"`
 | 
			
		||||
	Fields    []*field `json:"fields"`
 | 
			
		||||
	MrkdwnIn  []string `json:"mrkdwn_in"`
 | 
			
		||||
	Footer    string   `json:"footer"`
 | 
			
		||||
}
 | 
			
		||||
type message struct {
 | 
			
		||||
	Text        string        `json:"text"`
 | 
			
		||||
@@ -69,7 +70,8 @@ func (w SlackWriter) Write(rs ...models.ScanResult) error {
 | 
			
		||||
		if 0 < len(r.Errors) {
 | 
			
		||||
			serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
			notifyUsers := getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
			txt := fmt.Sprintf("%s\n%s\nError: %s", notifyUsers, serverInfo, r.Errors)
 | 
			
		||||
			txt := fmt.Sprintf("%s\n%s\nError: %s",
 | 
			
		||||
				notifyUsers, serverInfo, r.Errors)
 | 
			
		||||
			msg := message{
 | 
			
		||||
				Text:      txt,
 | 
			
		||||
				Username:  conf.AuthUser,
 | 
			
		||||
@@ -152,54 +154,73 @@ func send(msg message) error {
 | 
			
		||||
 | 
			
		||||
func msgText(r models.ScanResult) string {
 | 
			
		||||
	notifyUsers := ""
 | 
			
		||||
	if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
 | 
			
		||||
	if 0 < len(r.ScannedCves) {
 | 
			
		||||
		notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
 | 
			
		||||
	}
 | 
			
		||||
	serverInfo := fmt.Sprintf("*%s*", r.ServerInfo())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary())
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n>%s",
 | 
			
		||||
		notifyUsers,
 | 
			
		||||
		serverInfo,
 | 
			
		||||
		r.ScannedCves.FormatCveSummary())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
	cves := scanResult.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, scanResult.UnknownCves...)
 | 
			
		||||
func toSlackAttachments(r models.ScanResult) (attaches []*attachment) {
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vinfos = r.ScannedCves.FindScoredVulns().ToSortedSlice()
 | 
			
		||||
	} else {
 | 
			
		||||
		vinfos = r.ScannedCves.ToSortedSlice()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveInfo := range cves {
 | 
			
		||||
		cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
		curentPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			curentPackages = append(curentPackages, p.ToStringCurrentVersion())
 | 
			
		||||
	for _, vinfo := range vinfos {
 | 
			
		||||
		curent := []string{}
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			if p, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				curent = append(curent,
 | 
			
		||||
					fmt.Sprintf("%s-%s", p.Name, p.FormatVer()))
 | 
			
		||||
			} else {
 | 
			
		||||
				curent = append(curent, affected.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, n := range cveInfo.CpeNames {
 | 
			
		||||
			curentPackages = append(curentPackages, n)
 | 
			
		||||
		for _, n := range vinfo.CpeNames {
 | 
			
		||||
			curent = append(curent, n)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newPackages := []string{}
 | 
			
		||||
		for _, p := range cveInfo.Packages {
 | 
			
		||||
			newPackages = append(newPackages, p.ToStringNewVersion())
 | 
			
		||||
		new := []string{}
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			if p, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				if affected.NotFixedYet {
 | 
			
		||||
					new = append(new, "Not Fixed Yet")
 | 
			
		||||
				} else {
 | 
			
		||||
					new = append(new, p.FormatNewVer())
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				new = append(new, "?")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for range vinfo.CpeNames {
 | 
			
		||||
			new = append(new, "?")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		a := attachment{
 | 
			
		||||
			Title:     cveID,
 | 
			
		||||
			TitleLink: fmt.Sprintf("%s/%s", nvdBaseURL, cveID),
 | 
			
		||||
			Text:      attachmentText(cveInfo, scanResult.Family),
 | 
			
		||||
			Title:     vinfo.CveID,
 | 
			
		||||
			TitleLink: "https://nvd.nist.gov/vuln/detail/" + vinfo.CveID,
 | 
			
		||||
			Text:      attachmentText(vinfo, r.Family),
 | 
			
		||||
			MrkdwnIn:  []string{"text", "pretext"},
 | 
			
		||||
			Fields: []*field{
 | 
			
		||||
				{
 | 
			
		||||
					//  Title: "Current Package/CPE",
 | 
			
		||||
					// Title: "Current Package/CPE",
 | 
			
		||||
					Title: "Installed",
 | 
			
		||||
					Value: strings.Join(curentPackages, "\n"),
 | 
			
		||||
					Value: strings.Join(curent, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Title: "Candidate",
 | 
			
		||||
					Value: strings.Join(newPackages, "\n"),
 | 
			
		||||
					Value: strings.Join(new, "\n"),
 | 
			
		||||
					Short: true,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
			Color: color(vinfo.MaxCvssScore().Value.Score),
 | 
			
		||||
		}
 | 
			
		||||
		attaches = append(attaches, &a)
 | 
			
		||||
	}
 | 
			
		||||
@@ -220,68 +241,72 @@ func color(cvssScore float64) string {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	linkText := links(cveInfo, osFamily)
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
func attachmentText(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
	maxCvss := vinfo.MaxCvssScore()
 | 
			
		||||
	vectors := []string{}
 | 
			
		||||
	for _, cvss := range vinfo.Cvss2Scores() {
 | 
			
		||||
		calcURL := ""
 | 
			
		||||
		switch cvss.Value.Type {
 | 
			
		||||
		case models.CVSS2:
 | 
			
		||||
			calcURL = fmt.Sprintf(
 | 
			
		||||
				"https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=%s",
 | 
			
		||||
				vinfo.CveID)
 | 
			
		||||
		case models.CVSS3:
 | 
			
		||||
			calcURL = fmt.Sprintf(
 | 
			
		||||
				"https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=%s",
 | 
			
		||||
				vinfo.CveID)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			jvn.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcBaseURL, cveInfo.CveDetail.CveID),
 | 
			
		||||
			jvn.CvssVector(),
 | 
			
		||||
			jvn.CveTitle(),
 | 
			
		||||
			linkText,
 | 
			
		||||
			cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
	case 0 < cveInfo.CveDetail.CvssScore("en"):
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			cveInfo.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
			nvd.CvssSeverity(),
 | 
			
		||||
			fmt.Sprintf(cvssV2CalcBaseURL, cveInfo.CveDetail.CveID),
 | 
			
		||||
			nvd.CvssVector(),
 | 
			
		||||
			nvd.CveSummary(),
 | 
			
		||||
			linkText,
 | 
			
		||||
			cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		return fmt.Sprintf("?\n%s\n%s\n*Confidence:* %v",
 | 
			
		||||
			nvd.CveSummary(), linkText, cveInfo.VulnInfo.Confidence)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		if cont, ok := vinfo.CveContents[cvss.Type]; ok {
 | 
			
		||||
			v := fmt.Sprintf("<%s|%s> (<%s|%s>)",
 | 
			
		||||
				calcURL,
 | 
			
		||||
				cvss.Value.Format(),
 | 
			
		||||
				cont.SourceLink,
 | 
			
		||||
				cvss.Type)
 | 
			
		||||
			vectors = append(vectors, v)
 | 
			
		||||
 | 
			
		||||
func links(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
		} else {
 | 
			
		||||
			if 0 < len(vinfo.DistroAdvisories) {
 | 
			
		||||
				links := []string{}
 | 
			
		||||
				for k, v := range vinfo.VendorLinks(osFamily) {
 | 
			
		||||
					links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
						v, k))
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
	cweID := cveInfo.CveDetail.CweID()
 | 
			
		||||
	if 0 < len(cweID) {
 | 
			
		||||
		links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
			cweURL(cweID), cweID))
 | 
			
		||||
		if config.Conf.Lang == "ja" {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s(JVN)>",
 | 
			
		||||
				cweJvnURL(cweID), cweID))
 | 
			
		||||
				v := fmt.Sprintf("<%s|%s> (%s)",
 | 
			
		||||
					calcURL,
 | 
			
		||||
					cvss.Value.Format(),
 | 
			
		||||
					strings.Join(links, ", "))
 | 
			
		||||
				vectors = append(vectors, v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
 | 
			
		||||
		jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
 | 
			
		||||
		links = append(links, jvn)
 | 
			
		||||
	severity := strings.ToUpper(maxCvss.Value.Severity)
 | 
			
		||||
	if severity == "" {
 | 
			
		||||
		severity = "?"
 | 
			
		||||
	}
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links,
 | 
			
		||||
			fmt.Sprintf("<%s|%s>", link.url, link.title))
 | 
			
		||||
	}
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|MITRE>",
 | 
			
		||||
		fmt.Sprintf("%s%s", mitreBaseURL, cveID)))
 | 
			
		||||
	links = append(links, fmt.Sprintf("<%s|CVEDetails>",
 | 
			
		||||
		fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)))
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("*%4.1f (%s)* %s\n%s\n```%s```",
 | 
			
		||||
		maxCvss.Value.Score,
 | 
			
		||||
		severity,
 | 
			
		||||
		cweIDs(vinfo, osFamily),
 | 
			
		||||
		strings.Join(vectors, "\n"),
 | 
			
		||||
		vinfo.Summaries(config.Conf.Lang, osFamily)[0].Value,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweIDs(vinfo models.VulnInfo, osFamily string) string {
 | 
			
		||||
	links := []string{}
 | 
			
		||||
	for _, cwe := range vinfo.CveContents.CweIDs(osFamily) {
 | 
			
		||||
		if config.Conf.Lang == "ja" {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweJvnURL(cwe.Value), cwe.Value))
 | 
			
		||||
		} else {
 | 
			
		||||
			links = append(links, fmt.Sprintf("<%s|%s>",
 | 
			
		||||
				cweURL(cwe.Value), cwe.Value))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(links, " / ")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										335
									
								
								report/tui.go
									
									
									
									
									
								
							
							
						
						@@ -21,36 +21,41 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"text/template"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	log "github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
	"github.com/jroimartin/gocui"
 | 
			
		||||
	cve "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanHistory models.ScanHistory
 | 
			
		||||
var scanResults models.ScanResults
 | 
			
		||||
var currentScanResult models.ScanResult
 | 
			
		||||
var currentCveInfo int
 | 
			
		||||
var vinfos []models.VulnInfo
 | 
			
		||||
var currentVinfo int
 | 
			
		||||
var currentDetailLimitY int
 | 
			
		||||
var currentChangelogLimitY int
 | 
			
		||||
 | 
			
		||||
// RunTui execute main logic
 | 
			
		||||
func RunTui(history models.ScanHistory) subcommands.ExitStatus {
 | 
			
		||||
	scanHistory = history
 | 
			
		||||
func RunTui(results models.ScanResults) subcommands.ExitStatus {
 | 
			
		||||
	scanResults = results
 | 
			
		||||
 | 
			
		||||
	g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	// g, err := gocui.NewGui(gocui.OutputNormal)
 | 
			
		||||
	g := gocui.NewGui()
 | 
			
		||||
	if err := g.Init(); err != nil {
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	defer g.Close()
 | 
			
		||||
 | 
			
		||||
	g.SetManagerFunc(layout)
 | 
			
		||||
	g.SetLayout(layout)
 | 
			
		||||
	// g.SetManagerFunc(layout)
 | 
			
		||||
	if err := keybindings(g); err != nil {
 | 
			
		||||
		log.Errorf("%s", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
@@ -175,19 +180,19 @@ func nextView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		_, err = g.SetCurrentView("changelog")
 | 
			
		||||
		err = g.SetCurrentView("changelog")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	default:
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -196,19 +201,19 @@ func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	if v == nil {
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	case "summary":
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	case "detail":
 | 
			
		||||
		_, err = g.SetCurrentView("summary")
 | 
			
		||||
		err = g.SetCurrentView("summary")
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		_, err = g.SetCurrentView("detail")
 | 
			
		||||
		err = g.SetCurrentView("detail")
 | 
			
		||||
	default:
 | 
			
		||||
		_, err = g.SetCurrentView("side")
 | 
			
		||||
		err = g.SetCurrentView("side")
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
@@ -216,27 +221,27 @@ func previousView(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
 | 
			
		||||
	switch v.Name() {
 | 
			
		||||
	case "side":
 | 
			
		||||
		yLimit = len(scanHistory.ScanResults) - 1
 | 
			
		||||
		yLimit = len(scanResults) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "summary":
 | 
			
		||||
		yLimit = len(currentScanResult.AllCves()) - 1
 | 
			
		||||
		yLimit = len(currentScanResult.ScannedCves) - 1
 | 
			
		||||
		if yLimit < nextY {
 | 
			
		||||
			return false, yLimit
 | 
			
		||||
		}
 | 
			
		||||
		return true, yLimit
 | 
			
		||||
	case "detail":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		// if currentDetailLimitY < nextY {
 | 
			
		||||
		// return false, currentDetailLimitY
 | 
			
		||||
		// }
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
	case "changelog":
 | 
			
		||||
		if currentDetailLimitY < nextY {
 | 
			
		||||
			return false, currentDetailLimitY
 | 
			
		||||
		}
 | 
			
		||||
		return true, currentDetailLimitY
 | 
			
		||||
		// if currentChangelogLimitY < nextY {
 | 
			
		||||
		// return false, currentChangelogLimitY
 | 
			
		||||
		// }
 | 
			
		||||
		return true, currentChangelogLimitY
 | 
			
		||||
	default:
 | 
			
		||||
		return true, 0
 | 
			
		||||
	}
 | 
			
		||||
@@ -280,7 +285,7 @@ func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		//  ok,  := movable(v, oy+cy+1)
 | 
			
		||||
		//  _, maxY := v.Size()
 | 
			
		||||
		ok, _ := movable(v, oy+cy+1)
 | 
			
		||||
		//  log.Info(cy, oy, maxY, yLimit)
 | 
			
		||||
		//  log.Info(cy, oy)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
@@ -291,6 +296,10 @@ func cursorDown(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
		}
 | 
			
		||||
		onMovingCursorRedrawView(g, v)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cx, cy := v.Cursor()
 | 
			
		||||
	ox, oy := v.Origin()
 | 
			
		||||
	debug(g, fmt.Sprintf("%v, %v, %v, %v", cx, cy, ox, oy))
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -387,7 +396,7 @@ func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -395,7 +404,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -405,7 +414,7 @@ func previousSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if v != nil {
 | 
			
		||||
		// cursor to summary
 | 
			
		||||
		if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// move next line
 | 
			
		||||
@@ -413,7 +422,7 @@ func nextSummary(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		// cursor to detail
 | 
			
		||||
		if _, err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("detail"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -439,9 +448,10 @@ func changeHost(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	}
 | 
			
		||||
	serverName := strings.TrimSpace(l)
 | 
			
		||||
 | 
			
		||||
	for _, r := range scanHistory.ScanResults {
 | 
			
		||||
	for _, r := range scanResults {
 | 
			
		||||
		if serverName == strings.TrimSpace(r.ServerInfoTui()) {
 | 
			
		||||
			currentScanResult = r
 | 
			
		||||
			vinfos = r.ScannedCves.ToSortedSlice()
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -495,7 +505,7 @@ func getLine(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -510,14 +520,15 @@ func showMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	//  maxX, maxY := v.Size()
 | 
			
		||||
	_, maxY := v.Size()
 | 
			
		||||
 | 
			
		||||
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
 | 
			
		||||
	l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v",
 | 
			
		||||
		cy, oy, maxY, yLimit, currentVinfo, ok)
 | 
			
		||||
	//  if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
 | 
			
		||||
	if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fmt.Fprintln(v, l)
 | 
			
		||||
		if _, err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
		if err := g.SetCurrentView("msg"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -528,7 +539,7 @@ func delMsg(g *gocui.Gui, v *gocui.View) error {
 | 
			
		||||
	if err := g.DeleteView("msg"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
	if err := g.SetCurrentView("summary"); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -551,6 +562,20 @@ func layout(g *gocui.Gui) error {
 | 
			
		||||
	if err := setChangelogLayout(g); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func debug(g *gocui.Gui, str string) error {
 | 
			
		||||
	if config.Conf.Debug {
 | 
			
		||||
		maxX, maxY := g.Size()
 | 
			
		||||
		if _, err := g.View("debug"); err != gocui.ErrUnknownView {
 | 
			
		||||
			g.DeleteView("debug")
 | 
			
		||||
		}
 | 
			
		||||
		if v, err := g.SetView("debug", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
 | 
			
		||||
			fmt.Fprintf(v, str)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -562,14 +587,15 @@ func setSideLayout(g *gocui.Gui) error {
 | 
			
		||||
		}
 | 
			
		||||
		v.Highlight = true
 | 
			
		||||
 | 
			
		||||
		for _, result := range scanHistory.ScanResults {
 | 
			
		||||
		for _, result := range scanResults {
 | 
			
		||||
			fmt.Fprintln(v, result.ServerInfoTui())
 | 
			
		||||
		}
 | 
			
		||||
		if len(scanHistory.ScanResults) == 0 {
 | 
			
		||||
		if len(scanResults) == 0 {
 | 
			
		||||
			return fmt.Errorf("No scan results")
 | 
			
		||||
		}
 | 
			
		||||
		currentScanResult = scanHistory.ScanResults[0]
 | 
			
		||||
		if _, err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
		currentScanResult = scanResults[0]
 | 
			
		||||
		vinfos = scanResults[0].ScannedCves.ToSortedSlice()
 | 
			
		||||
		if err := g.SetCurrentView("side"); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -603,50 +629,28 @@ func summaryLines() string {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	indexFormat := ""
 | 
			
		||||
	if len(currentScanResult.AllCves()) < 10 {
 | 
			
		||||
	if len(currentScanResult.ScannedCves) < 10 {
 | 
			
		||||
		indexFormat = "[%1d]"
 | 
			
		||||
	} else if len(currentScanResult.AllCves()) < 100 {
 | 
			
		||||
	} else if len(currentScanResult.ScannedCves) < 100 {
 | 
			
		||||
		indexFormat = "[%2d]"
 | 
			
		||||
	} else {
 | 
			
		||||
		indexFormat = "[%3d]"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, d := range currentScanResult.AllCves() {
 | 
			
		||||
	for i, vinfo := range vinfos {
 | 
			
		||||
		summary := vinfo.Titles(
 | 
			
		||||
			config.Conf.Lang, currentScanResult.Family)[0].Value
 | 
			
		||||
		cvssScore := fmt.Sprintf("| %4.1f",
 | 
			
		||||
			vinfo.MaxCvssScore().Value.Score)
 | 
			
		||||
 | 
			
		||||
		var cols []string
 | 
			
		||||
		//  packs := []string{}
 | 
			
		||||
		//  for _, pack := range d.Packages {
 | 
			
		||||
		//      packs = append(packs, pack.Name)
 | 
			
		||||
		//  }
 | 
			
		||||
		if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
			summary := d.CveDetail.Jvn.CveTitle()
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("| %4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang)),
 | 
			
		||||
				fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			summary := d.CveDetail.Nvd.CveSummary()
 | 
			
		||||
 | 
			
		||||
			var cvssScore string
 | 
			
		||||
			if d.CveDetail.CvssScore("en") <= 0 {
 | 
			
		||||
				cvssScore = "|   ?"
 | 
			
		||||
			} else {
 | 
			
		||||
				cvssScore = fmt.Sprintf("| %4.1f",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cols = []string{
 | 
			
		||||
				fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				cvssScore,
 | 
			
		||||
				fmt.Sprintf("| %3d |", d.VulnInfo.Confidence.Score),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		cols = []string{
 | 
			
		||||
			fmt.Sprintf(indexFormat, i+1),
 | 
			
		||||
			vinfo.CveID,
 | 
			
		||||
			cvssScore,
 | 
			
		||||
			fmt.Sprintf("| %3d |", vinfo.Confidence.Score),
 | 
			
		||||
			summary,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		icols := make([]interface{}, len(cols))
 | 
			
		||||
		for j := range cols {
 | 
			
		||||
			icols[j] = cols[j]
 | 
			
		||||
@@ -665,7 +669,7 @@ func setDetailLayout(g *gocui.Gui) error {
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
	currentVinfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("detail", -1, int(float64(maxY)*0.2), int(float64(maxX)*0.5), maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
@@ -693,22 +697,27 @@ func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
	}
 | 
			
		||||
	_, cy := summaryView.Cursor()
 | 
			
		||||
	_, oy := summaryView.Origin()
 | 
			
		||||
	currentCveInfo = cy + oy
 | 
			
		||||
	currentVinfo = cy + oy
 | 
			
		||||
 | 
			
		||||
	if v, err := g.SetView("changelog", int(float64(maxX)*0.5), int(float64(maxY)*0.2), maxX, maxY); err != nil {
 | 
			
		||||
		if err != gocui.ErrUnknownView {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if len(currentScanResult.Errors) != 0 || len(currentScanResult.AllCves()) == 0 {
 | 
			
		||||
		if len(currentScanResult.Errors) != 0 || len(currentScanResult.ScannedCves) == 0 {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		lines := []string{}
 | 
			
		||||
		cveInfo := currentScanResult.AllCves()[currentCveInfo]
 | 
			
		||||
		for _, pack := range cveInfo.Packages {
 | 
			
		||||
		vinfo := vinfos[currentVinfo]
 | 
			
		||||
		for _, adv := range vinfo.DistroAdvisories {
 | 
			
		||||
			lines = append(lines, adv.Format())
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
			pack := currentScanResult.Packages[affected.Name]
 | 
			
		||||
			for _, p := range currentScanResult.Packages {
 | 
			
		||||
				if pack.Name == p.Name {
 | 
			
		||||
					lines = append(lines, formatOneChangelog(p), "\n")
 | 
			
		||||
					lines = append(lines, p.FormatChangelog(), "\n")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -717,21 +726,19 @@ func setChangelogLayout(g *gocui.Gui) error {
 | 
			
		||||
		v.Editable = false
 | 
			
		||||
		v.Wrap = true
 | 
			
		||||
 | 
			
		||||
		currentDetailLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
		currentChangelogLimitY = len(strings.Split(text, "\n")) - 1
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dataForTmpl struct {
 | 
			
		||||
	CveID            string
 | 
			
		||||
	CvssScore        string
 | 
			
		||||
	CvssVector       string
 | 
			
		||||
	CvssSeverity     string
 | 
			
		||||
	Cvsses           string
 | 
			
		||||
	Summary          string
 | 
			
		||||
	Confidence       models.Confidence
 | 
			
		||||
	CweURL           string
 | 
			
		||||
	VulnSiteLinks    []string
 | 
			
		||||
	References       []cve.Reference
 | 
			
		||||
	Cwes             []models.CveContentStr
 | 
			
		||||
	Links            []string
 | 
			
		||||
	References       []models.Reference
 | 
			
		||||
	Packages         []string
 | 
			
		||||
	CpeNames         []string
 | 
			
		||||
	PublishedDate    time.Time
 | 
			
		||||
@@ -739,82 +746,75 @@ type dataForTmpl struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailLines() (string, error) {
 | 
			
		||||
	if len(currentScanResult.Errors) != 0 {
 | 
			
		||||
	r := currentScanResult
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(currentScanResult.AllCves()) == 0 {
 | 
			
		||||
	if len(r.ScannedCves) == 0 {
 | 
			
		||||
		return "No vulnerable packages", nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cveInfo := currentScanResult.AllCves()[currentCveInfo]
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(detailTemplate())
 | 
			
		||||
	tmpl, err := template.New("detail").Parse(mdTemplate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssSeverity, cvssVector, summary string
 | 
			
		||||
	var refs []cve.Reference
 | 
			
		||||
	switch {
 | 
			
		||||
	case config.Conf.Lang == "ja" &&
 | 
			
		||||
		0 < cveInfo.CveDetail.Jvn.CvssScore():
 | 
			
		||||
		jvn := cveInfo.CveDetail.Jvn
 | 
			
		||||
		cvssSeverity = jvn.CvssSeverity()
 | 
			
		||||
		cvssVector = jvn.CvssVector()
 | 
			
		||||
		summary = fmt.Sprintf("%s\n%s", jvn.CveTitle(), jvn.CveSummary())
 | 
			
		||||
		refs = jvn.VulnSiteReferences()
 | 
			
		||||
	default:
 | 
			
		||||
		nvd := cveInfo.CveDetail.Nvd
 | 
			
		||||
		cvssSeverity = nvd.CvssSeverity()
 | 
			
		||||
		cvssVector = nvd.CvssVector()
 | 
			
		||||
		summary = nvd.CveSummary()
 | 
			
		||||
		refs = nvd.VulnSiteReferences()
 | 
			
		||||
	vinfo := vinfos[currentVinfo]
 | 
			
		||||
 | 
			
		||||
	packsVer := []string{}
 | 
			
		||||
	vinfo.AffectedPackages.Sort()
 | 
			
		||||
	for _, affected := range vinfo.AffectedPackages {
 | 
			
		||||
		// packages detected by OVAL may not be actually installed
 | 
			
		||||
		if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
			packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Strings(vinfo.CpeNames)
 | 
			
		||||
	for _, name := range vinfo.CpeNames {
 | 
			
		||||
		packsVer = append(packsVer, name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cweURL := cweURL(cveInfo.CveDetail.CweID())
 | 
			
		||||
 | 
			
		||||
	links := []string{
 | 
			
		||||
		fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s/%s", nvdBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CVSSv2 Calc]( %s )", fmt.Sprintf(cvssV2CalcBaseURL, cveID)),
 | 
			
		||||
		fmt.Sprintf("[CVSSv3 Calc]( %s )", fmt.Sprintf(cvssV3CalcBaseURL, cveID)),
 | 
			
		||||
	}
 | 
			
		||||
	dlinks := distroLinks(cveInfo, currentScanResult.Family)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
 | 
			
		||||
	links := []string{vinfo.CveContents.SourceLinks(
 | 
			
		||||
		config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
 | 
			
		||||
		vinfo.Cvss2CalcURL(),
 | 
			
		||||
		vinfo.Cvss3CalcURL()}
 | 
			
		||||
	for _, url := range vinfo.VendorLinks(r.Family) {
 | 
			
		||||
		links = append(links, url)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var cvssScore string
 | 
			
		||||
	if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
 | 
			
		||||
		cvssScore = "?"
 | 
			
		||||
	} else {
 | 
			
		||||
		cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
 | 
			
		||||
	refs := []models.Reference{}
 | 
			
		||||
	for _, rr := range vinfo.CveContents.References(r.Family) {
 | 
			
		||||
		for _, ref := range rr.Value {
 | 
			
		||||
			refs = append(refs, ref)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packages := []string{}
 | 
			
		||||
	for _, pack := range cveInfo.Packages {
 | 
			
		||||
		packages = append(packages,
 | 
			
		||||
			fmt.Sprintf(
 | 
			
		||||
				"%s -> %s",
 | 
			
		||||
				pack.ToStringCurrentVersion(),
 | 
			
		||||
				pack.ToStringNewVersion()))
 | 
			
		||||
	summary := vinfo.Summaries(r.Lang, r.Family)[0]
 | 
			
		||||
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
 | 
			
		||||
	var cols []interface{}
 | 
			
		||||
	for _, score := range scores {
 | 
			
		||||
		cols = []interface{}{
 | 
			
		||||
			score.Value.Severity,
 | 
			
		||||
			score.Value.Format(),
 | 
			
		||||
			score.Type,
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow(cols...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	data := dataForTmpl{
 | 
			
		||||
		CveID:         cveID,
 | 
			
		||||
		CvssScore:     cvssScore,
 | 
			
		||||
		CvssSeverity:  cvssSeverity,
 | 
			
		||||
		CvssVector:    cvssVector,
 | 
			
		||||
		Summary:       summary,
 | 
			
		||||
		Confidence:    cveInfo.VulnInfo.Confidence,
 | 
			
		||||
		CweURL:        cweURL,
 | 
			
		||||
		VulnSiteLinks: links,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		Packages:      packages,
 | 
			
		||||
		CpeNames:      cveInfo.CpeNames,
 | 
			
		||||
		CveID:      vinfo.CveID,
 | 
			
		||||
		Cvsses:     fmt.Sprintf("%s\n", table),
 | 
			
		||||
		Summary:    fmt.Sprintf("%s (%s)", summary.Value, summary.Type),
 | 
			
		||||
		Confidence: vinfo.Confidence,
 | 
			
		||||
		Cwes:       vinfo.CveContents.CweIDs(r.Family),
 | 
			
		||||
		Links:      util.Distinct(links),
 | 
			
		||||
		Packages:   packsVer,
 | 
			
		||||
		References: refs,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := bytes.NewBuffer(nil) // create empty buffer
 | 
			
		||||
@@ -825,52 +825,49 @@ func detailLines() (string, error) {
 | 
			
		||||
	return string(buf.Bytes()), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detailTemplate() string {
 | 
			
		||||
	return `
 | 
			
		||||
const mdTemplate = `
 | 
			
		||||
{{.CveID}}
 | 
			
		||||
==============
 | 
			
		||||
 | 
			
		||||
CVSS Score
 | 
			
		||||
CVSS Scores
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
 | 
			
		||||
{{.Cvsses }}
 | 
			
		||||
 | 
			
		||||
Summary
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Summary }}
 | 
			
		||||
 | 
			
		||||
Confidence
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.Confidence }}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
{{range $link := .Links -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
CWE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
 {{.CweURL }}
 | 
			
		||||
{{range .Cwes -}}
 | 
			
		||||
* {{.Value}} ({{.Type}})
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
Package/CPE
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $pack := .Packages -}}
 | 
			
		||||
* {{$pack}}
 | 
			
		||||
{{end -}}
 | 
			
		||||
{{range $name := .CpeNames -}}
 | 
			
		||||
* {{$name}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Links
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range $link := .VulnSiteLinks -}}
 | 
			
		||||
* {{$link}}
 | 
			
		||||
{{end}}
 | 
			
		||||
Confidence
 | 
			
		||||
--------------
 | 
			
		||||
 {{.Confidence }}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
References
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
{{range .References -}}
 | 
			
		||||
* [{{.Source}}]( {{.Link}} )
 | 
			
		||||
{{end}}
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										729
									
								
								report/util.go
									
									
									
									
									
								
							
							
						
						@@ -18,12 +18,19 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/gosuri/uitable"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -39,7 +46,6 @@ func formatScanSummary(rs ...models.ScanResult) string {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				fmt.Sprintf("%s%s", r.Family, r.Release),
 | 
			
		||||
				fmt.Sprintf("%d CVEs", len(r.ScannedCves)),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -64,7 +70,7 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			cols = []interface{}{
 | 
			
		||||
				r.FormatServerName(),
 | 
			
		||||
				r.CveSummary(),
 | 
			
		||||
				r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
				r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
@@ -80,97 +86,69 @@ func formatOneLineSummary(rs ...models.ScanResult) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatShortPlainText(r models.ScanResult) string {
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
 | 
			
		||||
	cves := r.KnownCves
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		cves = append(cves, r.UnknownCves...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(r.ServerInfo()); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(cves) == 0 {
 | 
			
		||||
	vulns := r.ScannedCves
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vulns = vulns.FindScoredVulns()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(vulns) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range cves {
 | 
			
		||||
		var packsVer string
 | 
			
		||||
		for _, p := range d.Packages {
 | 
			
		||||
			packsVer += fmt.Sprintf(
 | 
			
		||||
				"%s -> %s\n", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		}
 | 
			
		||||
		for _, n := range d.CpeNames {
 | 
			
		||||
			packsVer += n
 | 
			
		||||
	stable := uitable.New()
 | 
			
		||||
	stable.MaxColWidth = maxColWidth
 | 
			
		||||
	stable.Wrap = true
 | 
			
		||||
	for _, vuln := range vulns.ToSortedSlice() {
 | 
			
		||||
		summaries := vuln.Summaries(config.Conf.Lang, r.Family)
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
 | 
			
		||||
		vlinks := []string{}
 | 
			
		||||
		for name, url := range vuln.VendorLinks(r.Family) {
 | 
			
		||||
			vlinks = append(vlinks, fmt.Sprintf("%s (%s)", url, name))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var scols []string
 | 
			
		||||
		switch {
 | 
			
		||||
		case config.Conf.Lang == "ja" &&
 | 
			
		||||
			0 < d.CveDetail.Jvn.CvssScore():
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s\n%s\n%sConfidence: %v",
 | 
			
		||||
				d.CveDetail.Jvn.CveTitle(),
 | 
			
		||||
				d.CveDetail.Jvn.Link(),
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
				d.VulnInfo.Confidence,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Jvn.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		case 0 < d.CveDetail.CvssScore("en"):
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%s/%s\n%s\n%sConfidence: %v",
 | 
			
		||||
				d.CveDetail.Nvd.CveSummary(),
 | 
			
		||||
				cveDetailsBaseURL,
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				distroLinks(d, r.Family)[0].url,
 | 
			
		||||
				packsVer,
 | 
			
		||||
				d.VulnInfo.Confidence,
 | 
			
		||||
			)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				fmt.Sprintf("%-4.1f (%s)",
 | 
			
		||||
					d.CveDetail.CvssScore(config.Conf.Lang),
 | 
			
		||||
					d.CveDetail.Nvd.CvssSeverity(),
 | 
			
		||||
				),
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			summary := fmt.Sprintf("%s\n%sConfidence: %v",
 | 
			
		||||
				distroLinks(d, r.Family)[0].url, packsVer, d.VulnInfo.Confidence)
 | 
			
		||||
			scols = []string{
 | 
			
		||||
				d.CveDetail.CveID,
 | 
			
		||||
				"?",
 | 
			
		||||
				summary,
 | 
			
		||||
			}
 | 
			
		||||
		cvsses := ""
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		cvsses += vuln.Cvss2CalcURL() + "\n"
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			cvsses += fmt.Sprintf("%s (%s)\n", cvss.Value.Format(), cvss.Type)
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			cvsses += vuln.Cvss3CalcURL() + "\n"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		maxCvss := vuln.FormatMaxCvssScore()
 | 
			
		||||
		rightCol := fmt.Sprintf(`%s
 | 
			
		||||
%s
 | 
			
		||||
---
 | 
			
		||||
%s
 | 
			
		||||
%s
 | 
			
		||||
%sConfidence: %v`,
 | 
			
		||||
			maxCvss,
 | 
			
		||||
			summaries[0].Value,
 | 
			
		||||
			links[0].Value,
 | 
			
		||||
			strings.Join(vlinks, "\n"),
 | 
			
		||||
			cvsses,
 | 
			
		||||
			//  packsVer,
 | 
			
		||||
			vuln.Confidence,
 | 
			
		||||
		)
 | 
			
		||||
 | 
			
		||||
		leftCol := fmt.Sprintf("%s", vuln.CveID)
 | 
			
		||||
		scols := []string{leftCol, rightCol}
 | 
			
		||||
		cols := make([]interface{}, len(scols))
 | 
			
		||||
		for i := range cols {
 | 
			
		||||
			cols[i] = scols[i]
 | 
			
		||||
@@ -182,295 +160,79 @@ No CVE-IDs are found in updatable packages.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatFullPlainText(r models.ScanResult) string {
 | 
			
		||||
	serverInfo := r.ServerInfo()
 | 
			
		||||
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(serverInfo); i++ {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
	header := fmt.Sprintf("%s\n%s\n%s\t%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.CveSummary(),
 | 
			
		||||
		r.Packages.FormatUpdatablePacksSummary(),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	header := r.FormatTextReportHeadedr()
 | 
			
		||||
	if len(r.Errors) != 0 {
 | 
			
		||||
		return fmt.Sprintf(
 | 
			
		||||
			"%s\nError: Scan with --debug to view the details\n%s\n\n",
 | 
			
		||||
			header, r.Errors)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(r.KnownCves) == 0 && len(r.UnknownCves) == 0 {
 | 
			
		||||
	vulns := r.ScannedCves
 | 
			
		||||
	if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		vulns = vulns.FindScoredVulns()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(vulns) == 0 {
 | 
			
		||||
		return fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
No CVE-IDs are found in updatable packages.
 | 
			
		||||
%s
 | 
			
		||||
`, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	 `, header, r.Packages.FormatUpdatablePacksSummary())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scoredReport, unscoredReport := []string{}, []string{}
 | 
			
		||||
	scoredReport, unscoredReport = formatPlainTextDetails(r, r.Family)
 | 
			
		||||
	table := uitable.New()
 | 
			
		||||
	table.MaxColWidth = maxColWidth
 | 
			
		||||
	table.Wrap = true
 | 
			
		||||
	for _, vuln := range vulns.ToSortedSlice() {
 | 
			
		||||
		table.AddRow(vuln.CveID)
 | 
			
		||||
		table.AddRow("----------------")
 | 
			
		||||
		table.AddRow("Max Score", vuln.FormatMaxCvssScore())
 | 
			
		||||
		for _, cvss := range vuln.Cvss2Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
		for _, cvss := range vuln.Cvss3Scores() {
 | 
			
		||||
			table.AddRow(cvss.Type, cvss.Value.Format())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss2Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv2 Calc", vuln.Cvss2CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		if 0 < len(vuln.Cvss3Scores()) {
 | 
			
		||||
			table.AddRow("CVSSv3 Calc", vuln.Cvss3CalcURL())
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Summary", vuln.Summaries(
 | 
			
		||||
			config.Conf.Lang, r.Family)[0].Value)
 | 
			
		||||
 | 
			
		||||
	unscored := ""
 | 
			
		||||
	if !config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
		unscored = strings.Join(unscoredReport, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
		links := vuln.CveContents.SourceLinks(
 | 
			
		||||
			config.Conf.Lang, r.Family, vuln.CveID)
 | 
			
		||||
		table.AddRow("Source", links[0].Value)
 | 
			
		||||
 | 
			
		||||
	scored := strings.Join(scoredReport, "\n\n")
 | 
			
		||||
	detail := fmt.Sprintf(`
 | 
			
		||||
%s
 | 
			
		||||
		vlinks := vuln.VendorLinks(r.Family)
 | 
			
		||||
		for name, url := range vlinks {
 | 
			
		||||
			table.AddRow(name, url)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
%s
 | 
			
		||||
`,
 | 
			
		||||
		scored,
 | 
			
		||||
		unscored,
 | 
			
		||||
	)
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s", header, detail, formatChangelogs(r))
 | 
			
		||||
}
 | 
			
		||||
		for _, v := range vuln.CveContents.CweIDs(r.Family) {
 | 
			
		||||
			table.AddRow(fmt.Sprintf("%s (%s)", v.Value, v.Type), cweURL(v.Value))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
func formatPlainTextDetails(r models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
 | 
			
		||||
	for _, cve := range r.KnownCves {
 | 
			
		||||
		switch config.Conf.Lang {
 | 
			
		||||
		case "en":
 | 
			
		||||
			if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
			}
 | 
			
		||||
		case "ja":
 | 
			
		||||
			if 0 < cve.CveDetail.Jvn.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangJa(cve, osFamily))
 | 
			
		||||
			} else if 0 < cve.CveDetail.Nvd.CvssScore() {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, formatPlainTextDetailsLangEn(cve, osFamily))
 | 
			
		||||
			} else {
 | 
			
		||||
				scoredReport = append(
 | 
			
		||||
					scoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
		packsVer := []string{}
 | 
			
		||||
		vuln.AffectedPackages.Sort()
 | 
			
		||||
		for _, affected := range vuln.AffectedPackages {
 | 
			
		||||
			if pack, ok := r.Packages[affected.Name]; ok {
 | 
			
		||||
				packsVer = append(packsVer, pack.FormatVersionFromTo(affected.NotFixedYet))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cve := range r.UnknownCves {
 | 
			
		||||
		unscoredReport = append(
 | 
			
		||||
			unscoredReport, formatPlainTextUnknownCve(cve, osFamily))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
		sort.Strings(vuln.CpeNames)
 | 
			
		||||
		for _, name := range vuln.CpeNames {
 | 
			
		||||
			packsVer = append(packsVer, name)
 | 
			
		||||
		}
 | 
			
		||||
		table.AddRow("Package/CPE", strings.Join(packsVer, "\n"))
 | 
			
		||||
		table.AddRow("Confidence", vuln.Confidence)
 | 
			
		||||
 | 
			
		||||
func formatPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	dtable.AddRow("Score", "?")
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s/%s", nvdBaseURL, cveID))
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := cveInfo.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	jvn := cveDetail.Jvn
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
	if score := cveDetail.Jvn.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Jvn.CvssScore(),
 | 
			
		||||
				jvn.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
	dtable.AddRow("Vector", jvn.CvssVector())
 | 
			
		||||
	dtable.AddRow("Title", jvn.CveTitle())
 | 
			
		||||
	dtable.AddRow("Description", jvn.CveSummary())
 | 
			
		||||
	dtable.AddRow(cveDetail.CweID(), cweURL(cveDetail.CweID()))
 | 
			
		||||
	dtable.AddRow(cveDetail.CweID()+"(JVN)", cweJvnURL(cveDetail.CweID()))
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("JVN", jvn.Link())
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s/%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSSv2 Clac", fmt.Sprintf(cvssV2CalcBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSSv3 Clac", fmt.Sprintf(cvssV3CalcBaseURL, cveID))
 | 
			
		||||
 | 
			
		||||
	dlinks := distroLinks(cveInfo, osFamily)
 | 
			
		||||
	for _, link := range dlinks {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
		table.AddRow("\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable = addPackageInfos(dtable, cveInfo.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, cveInfo.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", cveInfo.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
 | 
			
		||||
	cveDetail := d.CveDetail
 | 
			
		||||
	cveID := cveDetail.CveID
 | 
			
		||||
	nvd := cveDetail.Nvd
 | 
			
		||||
 | 
			
		||||
	dtable := uitable.New()
 | 
			
		||||
	dtable.MaxColWidth = maxColWidth
 | 
			
		||||
	dtable.Wrap = true
 | 
			
		||||
	dtable.AddRow(cveID)
 | 
			
		||||
	dtable.AddRow("-------------")
 | 
			
		||||
 | 
			
		||||
	if score := cveDetail.Nvd.CvssScore(); 0 < score {
 | 
			
		||||
		dtable.AddRow("Score",
 | 
			
		||||
			fmt.Sprintf("%4.1f (%s)",
 | 
			
		||||
				cveDetail.Nvd.CvssScore(),
 | 
			
		||||
				nvd.CvssSeverity(),
 | 
			
		||||
			))
 | 
			
		||||
	} else {
 | 
			
		||||
		dtable.AddRow("Score", "?")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("Vector", nvd.CvssVector())
 | 
			
		||||
	dtable.AddRow("Summary", nvd.CveSummary())
 | 
			
		||||
	dtable.AddRow("CWE", cweURL(cveDetail.CweID()))
 | 
			
		||||
 | 
			
		||||
	dtable.AddRow("NVD", fmt.Sprintf("%s/%s", nvdBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSSv2 Clac", fmt.Sprintf(cvssV2CalcBaseURL, cveID))
 | 
			
		||||
	dtable.AddRow("CVSSv3 Clac", fmt.Sprintf(cvssV3CalcBaseURL, cveID))
 | 
			
		||||
 | 
			
		||||
	links := distroLinks(d, osFamily)
 | 
			
		||||
	for _, link := range links {
 | 
			
		||||
		dtable.AddRow(link.title, link.url)
 | 
			
		||||
	}
 | 
			
		||||
	dtable = addPackageInfos(dtable, d.Packages)
 | 
			
		||||
	dtable = addCpeNames(dtable, d.CpeNames)
 | 
			
		||||
	dtable.AddRow("Confidence", d.VulnInfo.Confidence)
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n", dtable)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type distroLink struct {
 | 
			
		||||
	title string
 | 
			
		||||
	url   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// distroLinks add Vendor URL of the CVE to table
 | 
			
		||||
func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
 | 
			
		||||
	cveID := cveInfo.CveDetail.CveID
 | 
			
		||||
	switch osFamily {
 | 
			
		||||
	case "rhel", "centos":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "RHEL-errata",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(redhatRHSABaseBaseURL, aidURL),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "oraclelinux":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Oracle-CVE",
 | 
			
		||||
				fmt.Sprintf(oracleSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				// "Oracle-ELSA"
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(oracleELSABaseBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "amazon":
 | 
			
		||||
		links := []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"RHEL-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				//  "Amazon-ALAS",
 | 
			
		||||
				advisory.AdvisoryID,
 | 
			
		||||
				fmt.Sprintf(amazonSecurityBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Ubuntu-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", ubuntuSecurityBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//TODO Ubuntu USN
 | 
			
		||||
		}
 | 
			
		||||
	case "debian":
 | 
			
		||||
		return []distroLink{
 | 
			
		||||
			{
 | 
			
		||||
				"Debian-CVE",
 | 
			
		||||
				fmt.Sprintf("%s/%s", debianTrackerBaseURL, cveID),
 | 
			
		||||
			},
 | 
			
		||||
			//  TODO Debian dsa
 | 
			
		||||
		}
 | 
			
		||||
	case "FreeBSD":
 | 
			
		||||
		links := []distroLink{}
 | 
			
		||||
		for _, advisory := range cveInfo.DistroAdvisories {
 | 
			
		||||
			links = append(links, distroLink{
 | 
			
		||||
				"FreeBSD-VuXML",
 | 
			
		||||
				fmt.Sprintf(freeBSDVuXMLBaseURL, advisory.AdvisoryID),
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		return links
 | 
			
		||||
	default:
 | 
			
		||||
		return []distroLink{}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// addPackageInfos add package information related the CVE to table
 | 
			
		||||
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
 | 
			
		||||
	for i, p := range packs {
 | 
			
		||||
		var title string
 | 
			
		||||
		if i == 0 {
 | 
			
		||||
			title = "Package"
 | 
			
		||||
		}
 | 
			
		||||
		ver := fmt.Sprintf(
 | 
			
		||||
			"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
		table.AddRow(title, ver)
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addCpeNames(table *uitable.Table, names []string) *uitable.Table {
 | 
			
		||||
	for _, n := range names {
 | 
			
		||||
		table.AddRow("CPE", fmt.Sprintf("%s", n))
 | 
			
		||||
	}
 | 
			
		||||
	return table
 | 
			
		||||
	return fmt.Sprintf("%s\n%s", header, table)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cweURL(cweID string) string {
 | 
			
		||||
@@ -488,36 +250,263 @@ func formatChangelogs(r models.ScanResult) string {
 | 
			
		||||
		if p.NewVersion == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		clog := formatOneChangelog(p)
 | 
			
		||||
		clog := p.FormatChangelog()
 | 
			
		||||
		buf = append(buf, clog, "\n\n")
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func formatOneChangelog(p models.PackageInfo) string {
 | 
			
		||||
	buf := []string{}
 | 
			
		||||
	if p.NewVersion == "" {
 | 
			
		||||
		return ""
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	if r.Lang != config.Conf.Lang {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packVer := fmt.Sprintf("%s -> %s",
 | 
			
		||||
		p.ToStringCurrentVersion(), p.ToStringNewVersion())
 | 
			
		||||
	var delim bytes.Buffer
 | 
			
		||||
	for i := 0; i < len(packVer); i++ {
 | 
			
		||||
		delim.WriteString("-")
 | 
			
		||||
	for _, cve := range r.ScannedCves {
 | 
			
		||||
		if 0 < len(cve.CveContents) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := p.Changelog.Contents
 | 
			
		||||
	if lines := strings.Split(clog, "\n"); len(lines) != 0 {
 | 
			
		||||
		clog = strings.Join(lines[0:len(lines)-1], "\n")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch p.Changelog.Method {
 | 
			
		||||
	case models.FailedToGetChangelog:
 | 
			
		||||
		clog = "No changelogs"
 | 
			
		||||
	case models.FailedToFindVersionInChangelog:
 | 
			
		||||
		clog = "Failed to parse changelogs. For detials, check yourself"
 | 
			
		||||
	}
 | 
			
		||||
	buf = append(buf, packVer, delim.String(), clog)
 | 
			
		||||
	return strings.Join(buf, "\n")
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overwriteJSONFile(dir string, r models.ScanResult) error {
 | 
			
		||||
	before := config.Conf.FormatJSON
 | 
			
		||||
	beforeDiff := config.Conf.Diff
 | 
			
		||||
	config.Conf.FormatJSON = true
 | 
			
		||||
	config.Conf.Diff = false
 | 
			
		||||
	w := LocalFileWriter{CurrentDir: dir}
 | 
			
		||||
	if err := w.Write(r); err != nil {
 | 
			
		||||
		return fmt.Errorf("Failed to write summary report: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	config.Conf.FormatJSON = before
 | 
			
		||||
	config.Conf.Diff = beforeDiff
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPrevious(current models.ScanResults) (previous models.ScanResults, err error) {
 | 
			
		||||
	dirs, err := ListValidJSONDirs()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range current {
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			var r *models.ScanResult
 | 
			
		||||
			path := filepath.Join(dir, result.ServerName+".json")
 | 
			
		||||
			if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				previous = append(previous, *r)
 | 
			
		||||
				util.Log.Infof("Privious json found: %s", path)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return previous, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(curResults, preResults models.ScanResults) (diffed models.ScanResults, err error) {
 | 
			
		||||
	for _, current := range curResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previous models.ScanResult
 | 
			
		||||
		for _, r := range preResults {
 | 
			
		||||
			if current.ServerName == r.ServerName {
 | 
			
		||||
				found = true
 | 
			
		||||
				previous = r
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if found {
 | 
			
		||||
			current.ScannedCves = getDiffCves(previous, current)
 | 
			
		||||
			packages := models.Packages{}
 | 
			
		||||
			for _, s := range current.ScannedCves {
 | 
			
		||||
				for _, affected := range s.AffectedPackages {
 | 
			
		||||
					p := current.Packages[affected.Name]
 | 
			
		||||
					packages[affected.Name] = p
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			current.Packages = packages
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		diffed = append(diffed, current)
 | 
			
		||||
	}
 | 
			
		||||
	return diffed, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	previousCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, previousVulnInfo := range previous.ScannedCves {
 | 
			
		||||
		previousCveIDsSet[previousVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	new := models.VulnInfos{}
 | 
			
		||||
	updated := models.VulnInfos{}
 | 
			
		||||
	for _, v := range current.ScannedCves {
 | 
			
		||||
		if previousCveIDsSet[v.CveID] {
 | 
			
		||||
			if isCveInfoUpdated(v.CveID, previous, current) {
 | 
			
		||||
				updated[v.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			new[v.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID, vuln := range new {
 | 
			
		||||
		updated[cveID] = vuln
 | 
			
		||||
	}
 | 
			
		||||
	return updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
 | 
			
		||||
	cTypes := []models.CveContentType{
 | 
			
		||||
		models.NVD,
 | 
			
		||||
		models.JVN,
 | 
			
		||||
		models.NewCveContentType(current.Family),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prevLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range previous.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				prevLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	curLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	for _, c := range current.ScannedCves {
 | 
			
		||||
		if cveID == c.CveID {
 | 
			
		||||
			for _, cType := range cTypes {
 | 
			
		||||
				content, _ := c.CveContents[cType]
 | 
			
		||||
				curLastModified[cType] = content.LastModified
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if equal := prevLastModified[cType].Equal(curLastModified[cType]); !equal {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// jsonDirPattern is file name pattern of JSON directory
 | 
			
		||||
// 2016-11-16T10:43:28+09:00
 | 
			
		||||
// 2016-11-16T10:43:28Z
 | 
			
		||||
var jsonDirPattern = regexp.MustCompile(
 | 
			
		||||
	`^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+-]\d{2}:\d{2})$`)
 | 
			
		||||
 | 
			
		||||
// ListValidJSONDirs returns valid json directory as array
 | 
			
		||||
// Returned array is sorted so that recent directories are at the head
 | 
			
		||||
func ListValidJSONDirs() (dirs []string, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(config.Conf.ResultsDir); err != nil {
 | 
			
		||||
		err = fmt.Errorf("Failed to read %s: %s",
 | 
			
		||||
			config.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(config.Conf.ResultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(dirs, func(i, j int) bool {
 | 
			
		||||
		return dirs[j] < dirs[i]
 | 
			
		||||
	})
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// JSONDir returns
 | 
			
		||||
// If there is an arg, check if it is a valid format and return the corresponding path under results.
 | 
			
		||||
// If arg passed via PIPE (such as history subcommand), return that path.
 | 
			
		||||
// Otherwise, returns the path of the latest directory
 | 
			
		||||
func JSONDir(args []string) (string, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	dirs := []string{}
 | 
			
		||||
 | 
			
		||||
	if 0 < len(args) {
 | 
			
		||||
		if dirs, err = ListValidJSONDirs(); err != nil {
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		path := filepath.Join(config.Conf.ResultsDir, args[0])
 | 
			
		||||
		for _, d := range dirs {
 | 
			
		||||
			ss := strings.Split(d, string(os.PathSeparator))
 | 
			
		||||
			timedir := ss[len(ss)-1]
 | 
			
		||||
			if timedir == args[0] {
 | 
			
		||||
				return path, nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return "", fmt.Errorf("Invalid path: %s", path)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// PIPE
 | 
			
		||||
	if config.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return "", fmt.Errorf("Failed to read stdin: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			return filepath.Join(config.Conf.ResultsDir, fields[0]), nil
 | 
			
		||||
		}
 | 
			
		||||
		return "", fmt.Errorf("Stdin is invalid: %s", string(bytes))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// returns latest dir when no args or no PIPE
 | 
			
		||||
	if dirs, err = ListValidJSONDirs(); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	if len(dirs) == 0 {
 | 
			
		||||
		return "", fmt.Errorf("No results under %s",
 | 
			
		||||
			config.Conf.ResultsDir)
 | 
			
		||||
	}
 | 
			
		||||
	return dirs[0], nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadScanResults read JSON data
 | 
			
		||||
func LoadScanResults(jsonDir string) (results models.ScanResults, err error) {
 | 
			
		||||
	var files []os.FileInfo
 | 
			
		||||
	if files, err = ioutil.ReadDir(jsonDir); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read %s: %s", jsonDir, err)
 | 
			
		||||
	}
 | 
			
		||||
	for _, f := range files {
 | 
			
		||||
		if filepath.Ext(f.Name()) != ".json" || strings.HasSuffix(f.Name(), "_diff.json") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var r *models.ScanResult
 | 
			
		||||
		path := filepath.Join(jsonDir, f.Name())
 | 
			
		||||
		if r, err = loadOneServerScanResult(path); err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		results = append(results, *r)
 | 
			
		||||
	}
 | 
			
		||||
	if len(results) == 0 {
 | 
			
		||||
		return nil, fmt.Errorf("There is no json file under %s", jsonDir)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// loadOneServerScanResult read JSON data of one server
 | 
			
		||||
func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) {
 | 
			
		||||
	var (
 | 
			
		||||
		data []byte
 | 
			
		||||
		err  error
 | 
			
		||||
	)
 | 
			
		||||
	if data, err = ioutil.ReadFile(jsonFile); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to read %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	result := &models.ScanResult{}
 | 
			
		||||
	if err := json.Unmarshal(data, result); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to parse %s: %s", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										327
									
								
								report/util_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,327 @@
 | 
			
		||||
package report
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestIsCveInfoUpdated(t *testing.T) {
 | 
			
		||||
	f := "2006-01-02"
 | 
			
		||||
	old, _ := time.Parse(f, "2015-12-15")
 | 
			
		||||
	new, _ := time.Parse(f, "2015-12-16")
 | 
			
		||||
 | 
			
		||||
	type In struct {
 | 
			
		||||
		cveID string
 | 
			
		||||
		cur   models.ScanResult
 | 
			
		||||
		prev  models.ScanResult
 | 
			
		||||
	}
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       In
 | 
			
		||||
		expected bool
 | 
			
		||||
	}{
 | 
			
		||||
		// NVD compare non-initialized times
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0001",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0001": {
 | 
			
		||||
							CveID: "CVE-2017-0001",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0001",
 | 
			
		||||
									LastModified: time.Time{},
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		// JVN not updated
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0002",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0002": {
 | 
			
		||||
							CveID: "CVE-2017-0002",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: false,
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL updated
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0003",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					Family: "ubuntu",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: new,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					Family: "ubuntu",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0003": {
 | 
			
		||||
							CveID: "CVE-2017-0003",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
		// OVAL newly detected
 | 
			
		||||
		{
 | 
			
		||||
			in: In{
 | 
			
		||||
				cveID: "CVE-2017-0004",
 | 
			
		||||
				cur: models.ScanResult{
 | 
			
		||||
					Family: "redhat",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2017-0004": {
 | 
			
		||||
							CveID: "CVE-2017-0004",
 | 
			
		||||
							CveContents: models.NewCveContents(
 | 
			
		||||
								models.CveContent{
 | 
			
		||||
									Type:         models.NVD,
 | 
			
		||||
									CveID:        "CVE-2017-0002",
 | 
			
		||||
									LastModified: old,
 | 
			
		||||
								},
 | 
			
		||||
							),
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				prev: models.ScanResult{
 | 
			
		||||
					Family:      "redhat",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := isCveInfoUpdated(tt.in.cveID, tt.in.prev, tt.in.cur)
 | 
			
		||||
		if actual != tt.expected {
 | 
			
		||||
			t.Errorf("[%d] actual: %t, expected: %t", i, actual, tt.expected)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestDiff(t *testing.T) {
 | 
			
		||||
	atCurrent, _ := time.Parse("2006-01-02", "2014-12-31")
 | 
			
		||||
	atPrevious, _ := time.Parse("2006-01-02", "2014-11-31")
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		inCurrent  models.ScanResults
 | 
			
		||||
		inPrevious models.ScanResults
 | 
			
		||||
		out        models.ScanResult
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atPrevious,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2012-6702": {
 | 
			
		||||
							CveID:            "CVE-2012-6702",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
						"CVE-2014-9761": {
 | 
			
		||||
							CveID:            "CVE-2014-9761",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{},
 | 
			
		||||
					Errors:   []string{},
 | 
			
		||||
					Optional: [][]interface{}{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:   atCurrent,
 | 
			
		||||
				ServerName:  "u16",
 | 
			
		||||
				Family:      "ubuntu",
 | 
			
		||||
				Release:     "16.04",
 | 
			
		||||
				Packages:    models.Packages{},
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
				Errors:      []string{},
 | 
			
		||||
				Optional:    [][]interface{}{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			inCurrent: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:  atCurrent,
 | 
			
		||||
					ServerName: "u16",
 | 
			
		||||
					Family:     "ubuntu",
 | 
			
		||||
					Release:    "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{
 | 
			
		||||
						"CVE-2016-6662": {
 | 
			
		||||
							CveID:            "CVE-2016-6662",
 | 
			
		||||
							AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
							DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
							CpeNames:         []string{},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Packages: models.Packages{
 | 
			
		||||
						"mysql-libs": {
 | 
			
		||||
							Name:       "mysql-libs",
 | 
			
		||||
							Version:    "5.1.73",
 | 
			
		||||
							Release:    "7.el6",
 | 
			
		||||
							NewVersion: "5.1.73",
 | 
			
		||||
							NewRelease: "8.el6_8",
 | 
			
		||||
							Repository: "",
 | 
			
		||||
							Changelog: models.Changelog{
 | 
			
		||||
								Contents: "",
 | 
			
		||||
								Method:   "",
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			inPrevious: models.ScanResults{
 | 
			
		||||
				{
 | 
			
		||||
					ScannedAt:   atPrevious,
 | 
			
		||||
					ServerName:  "u16",
 | 
			
		||||
					Family:      "ubuntu",
 | 
			
		||||
					Release:     "16.04",
 | 
			
		||||
					ScannedCves: models.VulnInfos{},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			out: models.ScanResult{
 | 
			
		||||
				ScannedAt:  atCurrent,
 | 
			
		||||
				ServerName: "u16",
 | 
			
		||||
				Family:     "ubuntu",
 | 
			
		||||
				Release:    "16.04",
 | 
			
		||||
				ScannedCves: models.VulnInfos{
 | 
			
		||||
					"CVE-2016-6662": {
 | 
			
		||||
						CveID:            "CVE-2016-6662",
 | 
			
		||||
						AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}},
 | 
			
		||||
						DistroAdvisories: []models.DistroAdvisory{},
 | 
			
		||||
						CpeNames:         []string{},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Packages: models.Packages{
 | 
			
		||||
					"mysql-libs": {
 | 
			
		||||
						Name:       "mysql-libs",
 | 
			
		||||
						Version:    "5.1.73",
 | 
			
		||||
						Release:    "7.el6",
 | 
			
		||||
						NewVersion: "5.1.73",
 | 
			
		||||
						NewRelease: "8.el6_8",
 | 
			
		||||
						Repository: "",
 | 
			
		||||
						Changelog: models.Changelog{
 | 
			
		||||
							Contents: "",
 | 
			
		||||
							Method:   "",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		diff, _ := diff(tt.inCurrent, tt.inPrevious)
 | 
			
		||||
		for _, actual := range diff {
 | 
			
		||||
			if !reflect.DeepEqual(actual.ScannedCves, tt.out.ScannedCves) {
 | 
			
		||||
				h := pp.Sprint(actual.ScannedCves)
 | 
			
		||||
				x := pp.Sprint(tt.out.ScannedCves)
 | 
			
		||||
				t.Errorf("[%d] cves actual: \n %s \n expected: \n %s", i, h, x)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for j := range tt.out.Packages {
 | 
			
		||||
				if !reflect.DeepEqual(tt.out.Packages[j], actual.Packages[j]) {
 | 
			
		||||
					h := pp.Sprint(tt.out.Packages[j])
 | 
			
		||||
					x := pp.Sprint(actual.Packages[j])
 | 
			
		||||
					t.Errorf("[%d] packages actual: \n %s \n expected: \n %s", i, x, h)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -24,28 +24,6 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	nvdBaseURL        = "https://nvd.nist.gov/vuln/detail"
 | 
			
		||||
	mitreBaseURL      = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
 | 
			
		||||
	cveDetailsBaseURL = "http://www.cvedetails.com/cve"
 | 
			
		||||
	cvssV2CalcBaseURL = "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=%s"
 | 
			
		||||
	cvssV3CalcBaseURL = "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=%s"
 | 
			
		||||
 | 
			
		||||
	redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
 | 
			
		||||
	redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
 | 
			
		||||
	amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
 | 
			
		||||
	oracleSecurityBaseURL = "https://linux.oracle.com/cve/%s.html"
 | 
			
		||||
	oracleELSABaseBaseURL = "https://linux.oracle.com/errata/%s.html"
 | 
			
		||||
 | 
			
		||||
	ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
 | 
			
		||||
	debianTrackerBaseURL  = "https://security-tracker.debian.org/tracker"
 | 
			
		||||
 | 
			
		||||
	freeBSDVuXMLBaseURL = "https://vuxml.freebsd.org/freebsd/%s.html"
 | 
			
		||||
 | 
			
		||||
	vulsOpenTag  = "<vulsreport>"
 | 
			
		||||
	vulsCloseTag = "</vulsreport>"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ResultWriter Interface
 | 
			
		||||
type ResultWriter interface {
 | 
			
		||||
	Write(...models.ScanResult) error
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										70
									
								
								scan/base.go
									
									
									
									
									
								
							
							
						
						@@ -20,13 +20,12 @@ package scan
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type base struct {
 | 
			
		||||
@@ -48,7 +47,7 @@ func (l *base) setServerInfo(c config.ServerInfo) {
 | 
			
		||||
	l.ServerInfo = c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getServerInfo() config.ServerInfo {
 | 
			
		||||
func (l *base) getServerInfo() config.ServerInfo {
 | 
			
		||||
	return l.ServerInfo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -64,7 +63,7 @@ func (l *base) setDistro(fam, rel string) {
 | 
			
		||||
	l.setServerInfo(s)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getDistro() config.Distro {
 | 
			
		||||
func (l *base) getDistro() config.Distro {
 | 
			
		||||
	return l.Distro
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -72,11 +71,32 @@ func (l *base) setPlatform(p models.Platform) {
 | 
			
		||||
	l.Platform = p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getPlatform() models.Platform {
 | 
			
		||||
func (l *base) getPlatform() models.Platform {
 | 
			
		||||
	return l.Platform
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
func (l *base) runningKernel() (release, version string, err error) {
 | 
			
		||||
	r := l.exec("uname -r", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return "", "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	release = strings.TrimSpace(r.Stdout)
 | 
			
		||||
 | 
			
		||||
	switch l.Distro.Family {
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		r := l.exec("uname -a", noSudo)
 | 
			
		||||
		if !r.isSuccess() {
 | 
			
		||||
			return "", "", fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		ss := strings.Fields(r.Stdout)
 | 
			
		||||
		if 6 < len(ss) {
 | 
			
		||||
			version = ss[6]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) allContainers() (containers []config.Container, err error) {
 | 
			
		||||
	switch l.ServerInfo.Containers.Type {
 | 
			
		||||
	case "", "docker":
 | 
			
		||||
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
 | 
			
		||||
@@ -213,7 +233,7 @@ func (l *base) detectPlatform() {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
	if r := l.exec("type curl", noSudo); r.isSuccess() {
 | 
			
		||||
		cmd := "curl --max-time 1 --retry 3 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
 | 
			
		||||
		r := l.exec(cmd, noSudo)
 | 
			
		||||
@@ -261,16 +281,11 @@ func (l base) detectRunningOnAws() (ok bool, instanceID string, err error) {
 | 
			
		||||
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
 | 
			
		||||
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
 | 
			
		||||
 | 
			
		||||
func (l base) isAwsInstanceID(str string) bool {
 | 
			
		||||
func (l *base) isAwsInstanceID(str string) bool {
 | 
			
		||||
	return awsInstanceIDPattern.MatchString(str)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l *base) convertToModel() models.ScanResult {
 | 
			
		||||
	for _, p := range l.VulnInfos {
 | 
			
		||||
		sort.Sort(models.PackageInfosByName(p.Packages))
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(l.VulnInfos)
 | 
			
		||||
 | 
			
		||||
	ctype := l.ServerInfo.Containers.Type
 | 
			
		||||
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
 | 
			
		||||
		ctype = "docker"
 | 
			
		||||
@@ -287,22 +302,19 @@ func (l *base) convertToModel() models.ScanResult {
 | 
			
		||||
		errs = append(errs, fmt.Sprintf("%s", e))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Avoid null slice being null in JSON
 | 
			
		||||
	for i := range l.VulnInfos {
 | 
			
		||||
		l.VulnInfos[i].NilSliceToEmpty()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return models.ScanResult{
 | 
			
		||||
		ServerName:  l.ServerInfo.ServerName,
 | 
			
		||||
		ScannedAt:   time.Now(),
 | 
			
		||||
		Family:      l.Distro.Family,
 | 
			
		||||
		Release:     l.Distro.Release,
 | 
			
		||||
		Container:   container,
 | 
			
		||||
		Platform:    l.Platform,
 | 
			
		||||
		ScannedCves: l.VulnInfos,
 | 
			
		||||
		Packages:    l.Packages,
 | 
			
		||||
		Optional:    l.ServerInfo.Optional,
 | 
			
		||||
		Errors:      errs,
 | 
			
		||||
		JSONVersion:   models.JSONVersion,
 | 
			
		||||
		ServerName:    l.ServerInfo.ServerName,
 | 
			
		||||
		ScannedAt:     time.Now(),
 | 
			
		||||
		Family:        l.Distro.Family,
 | 
			
		||||
		Release:       l.Distro.Release,
 | 
			
		||||
		Container:     container,
 | 
			
		||||
		Platform:      l.Platform,
 | 
			
		||||
		ScannedCves:   l.VulnInfos,
 | 
			
		||||
		RunningKernel: l.Kernel,
 | 
			
		||||
		Packages:      l.Packages,
 | 
			
		||||
		Optional:      l.ServerInfo.Optional,
 | 
			
		||||
		Errors:        errs,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -310,6 +322,6 @@ func (l *base) setErrs(errs []error) {
 | 
			
		||||
	l.errs = errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l base) getErrs() []error {
 | 
			
		||||
func (l *base) getErrs() []error {
 | 
			
		||||
	return l.errs
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										497
									
								
								scan/debian.go
									
									
									
									
									
								
							
							
						
						@@ -18,6 +18,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
package scan
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
@@ -28,6 +29,8 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/knqyf263/go-deb-version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// inherit OsTypeInterface
 | 
			
		||||
@@ -37,7 +40,14 @@ type debian struct {
 | 
			
		||||
 | 
			
		||||
// NewDebian is constructor
 | 
			
		||||
func newDebian(c config.ServerInfo) *debian {
 | 
			
		||||
	d := &debian{}
 | 
			
		||||
	d := &debian{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
@@ -53,8 +63,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
			return false, deb, nil
 | 
			
		||||
		}
 | 
			
		||||
		if r.ExitStatus == 255 {
 | 
			
		||||
			return false, deb, fmt.Errorf(
 | 
			
		||||
				"Unable to connect via SSH. Check SSH settings. %s", r)
 | 
			
		||||
			return false, deb, fmt.Errorf("Unable to connect via SSH. Check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r)
 | 
			
		||||
		}
 | 
			
		||||
		util.Log.Debugf("Not Debian like Linux. %s", r)
 | 
			
		||||
		return false, deb, nil
 | 
			
		||||
@@ -67,7 +76,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
		//  e.g.
 | 
			
		||||
		//  Raspbian GNU/Linux 7 \n \l
 | 
			
		||||
		result := strings.Fields(r.Stdout)
 | 
			
		||||
		if len(result) > 2 && result[0] == "Raspbian" {
 | 
			
		||||
		if len(result) > 2 && result[0] == config.Raspbian {
 | 
			
		||||
			distro := strings.ToLower(trim(result[0]))
 | 
			
		||||
			deb.setDistro(distro, trim(result[2]))
 | 
			
		||||
			return true, deb, nil
 | 
			
		||||
@@ -115,7 +124,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err
 | 
			
		||||
	// Debian
 | 
			
		||||
	cmd := "cat /etc/debian_version"
 | 
			
		||||
	if r := exec(c, cmd, noSudo); r.isSuccess() {
 | 
			
		||||
		deb.setDistro("debian", trim(r.Stdout))
 | 
			
		||||
		deb.setDistro(config.Debian, trim(r.Stdout))
 | 
			
		||||
		return true, deb, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -128,60 +137,112 @@ func trim(str string) string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkIfSudoNoPasswd() error {
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	o.log.Infof("Checking... sudo %s", cmd)
 | 
			
		||||
	r := o.exec(cmd, sudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
	if config.Conf.Deep || o.Distro.Family == config.Raspbian {
 | 
			
		||||
		cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
		o.log.Infof("Checking... sudo %s", cmd)
 | 
			
		||||
		r := o.exec(cmd, sudo)
 | 
			
		||||
		if !r.isSuccess() {
 | 
			
		||||
			o.log.Errorf("sudo error on %s", r)
 | 
			
		||||
			return fmt.Errorf("Failed to sudo: %s", r)
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Sudo... Pass")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Infof("Sudo... Pass")
 | 
			
		||||
 | 
			
		||||
	o.log.Infof("sudo ... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) checkDependencies() error {
 | 
			
		||||
	packNames := []string{}
 | 
			
		||||
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu", "raspbian":
 | 
			
		||||
	case config.Ubuntu, config.Raspbian:
 | 
			
		||||
		o.log.Infof("Dependencies... No need")
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	case "debian":
 | 
			
		||||
		// Debian needs aptitude to get changelogs.
 | 
			
		||||
		// Because unable to get changelogs via apt-get changelog on Debian.
 | 
			
		||||
		if r := o.exec("test -f /usr/bin/aptitude", noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("aptitude is not installed: %s", r)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		// https://askubuntu.com/a/742844
 | 
			
		||||
		packNames = append(packNames, "reboot-notifier")
 | 
			
		||||
 | 
			
		||||
		if !config.Conf.Deep {
 | 
			
		||||
			// Debian needs aptitude to get changelogs.
 | 
			
		||||
			// Because unable to get changelogs via apt-get changelog on Debian.
 | 
			
		||||
			packNames = append(packNames, "aptitude")
 | 
			
		||||
		}
 | 
			
		||||
		o.log.Infof("Dependencies... Pass")
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("Not implemented yet: %s", o.Distro)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
		return err
 | 
			
		||||
	for _, name := range packNames {
 | 
			
		||||
		//TODO --show-format
 | 
			
		||||
		cmd := "dpkg-query -W " + name
 | 
			
		||||
		if r := o.exec(cmd, noSudo); !r.isSuccess() {
 | 
			
		||||
			msg := fmt.Sprintf("%s is not installed", name)
 | 
			
		||||
			o.log.Errorf(msg)
 | 
			
		||||
			return fmt.Errorf(msg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
 | 
			
		||||
	var unsecurePacks []models.VulnInfo
 | 
			
		||||
	if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setVulnInfos(unsecurePacks)
 | 
			
		||||
	o.log.Infof("Dependencies... Pass")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
 | 
			
		||||
func (o *debian) scanPackages() error {
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	rebootRequired, err := o.rebootRequired()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Version:        version,
 | 
			
		||||
		Release:        release,
 | 
			
		||||
		RebootRequired: rebootRequired,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	installed, updatable, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Packages = installed
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Deep || o.Distro.Family == config.Raspbian {
 | 
			
		||||
		unsecures, err := o.scanUnsecurePackages(updatable)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		o.VulnInfos = unsecures
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://askubuntu.com/a/742844
 | 
			
		||||
func (o *debian) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("test -f /var/run/reboot-required", noSudo)
 | 
			
		||||
	switch r.ExitStatus {
 | 
			
		||||
	case 0:
 | 
			
		||||
		return true, nil
 | 
			
		||||
	case 1:
 | 
			
		||||
		return false, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return false, fmt.Errorf("Failed to check reboot reauired: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, error) {
 | 
			
		||||
	installed, updatable := models.Packages{}, models.Packages{}
 | 
			
		||||
	r := o.exec("dpkg-query -W", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return packs, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		return nil, nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//  e.g.
 | 
			
		||||
@@ -192,16 +253,37 @@ func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error)
 | 
			
		||||
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
			
		||||
			name, version, err := o.parseScannedPackagesLine(trimmed)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
				return nil, nil, fmt.Errorf(
 | 
			
		||||
					"Debian: Failed to parse package line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
			installed[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: version,
 | 
			
		||||
			})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 | 
			
		||||
	updatableNames, err := o.getUpdatablePackNames()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, name := range updatableNames {
 | 
			
		||||
		for _, pack := range installed {
 | 
			
		||||
			if pack.Name == name {
 | 
			
		||||
				updatable[name] = pack
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fill the candidate versions of upgradable packages
 | 
			
		||||
	err = o.fillCandidateVersion(updatable)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	installed.MergeNewVersion(updatable)
 | 
			
		||||
 | 
			
		||||
	return installed, updatable, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var packageLinePattern = regexp.MustCompile(`^([^\t']+)\t(.+)$`)
 | 
			
		||||
@@ -221,51 +303,33 @@ func (o *debian) parseScannedPackagesLine(line string) (name, version string, er
 | 
			
		||||
	return "", "", fmt.Errorf("Unknown format: %s", line)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanUnsecurePackages(installed []models.PackageInfo) ([]models.VulnInfo, error) {
 | 
			
		||||
func (o *debian) aptGetUpdate() error {
 | 
			
		||||
	o.log.Infof("apt-get update...")
 | 
			
		||||
	cmd := util.PrependProxyEnv("apt-get update")
 | 
			
		||||
	if r := o.exec(cmd, sudo); !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		return fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// Convert the name of upgradable packages to PackageInfo struct
 | 
			
		||||
	upgradableNames, err := o.GetUpgradablePackNames()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var upgradablePacks []models.PackageInfo
 | 
			
		||||
	for _, name := range upgradableNames {
 | 
			
		||||
		for _, pack := range installed {
 | 
			
		||||
			if pack.Name == name {
 | 
			
		||||
				upgradablePacks = append(upgradablePacks, pack)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Fill the candidate versions of upgradable packages
 | 
			
		||||
	upgradablePacks, err = o.fillCandidateVersion(upgradablePacks)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.Packages.MergeNewVersion(upgradablePacks)
 | 
			
		||||
func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
 | 
			
		||||
	o.aptGetUpdate()
 | 
			
		||||
 | 
			
		||||
	// Setup changelog cache
 | 
			
		||||
	current := cache.Meta{
 | 
			
		||||
		Name:   o.getServerInfo().GetServerName(),
 | 
			
		||||
		Distro: o.getServerInfo().Distro,
 | 
			
		||||
		Packs:  upgradablePacks,
 | 
			
		||||
		Packs:  updatable,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	o.log.Debugf("Ensure changelog cache: %s", current.Name)
 | 
			
		||||
	var meta *cache.Meta
 | 
			
		||||
	if meta, err = o.ensureChangelogCache(current); err != nil {
 | 
			
		||||
	meta, err := o.ensureChangelogCache(current)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Collect CVE information of upgradable packages
 | 
			
		||||
	vulnInfos, err := o.scanVulnInfos(upgradablePacks, meta)
 | 
			
		||||
	vulnInfos, err := o.scanVulnInfos(updatable, meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -308,34 +372,34 @@ func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) {
 | 
			
		||||
	return &cached, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) fillCandidateVersion(before models.PackageInfoList) (filled []models.PackageInfo, err error) {
 | 
			
		||||
func (o *debian) fillCandidateVersion(updatables models.Packages) (err error) {
 | 
			
		||||
	names := []string{}
 | 
			
		||||
	for _, p := range before {
 | 
			
		||||
		names = append(names, p.Name)
 | 
			
		||||
	for name := range updatables {
 | 
			
		||||
		names = append(names, name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return nil, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
		return 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)
 | 
			
		||||
			return fmt.Errorf("Failed to parse %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		p, found := before.FindByName(k)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Not found: %s", k)
 | 
			
		||||
		pack, ok := updatables[k]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return fmt.Errorf("Not found: %s", k)
 | 
			
		||||
		}
 | 
			
		||||
		p.NewVersion = ver.Candidate
 | 
			
		||||
		filled = append(filled, p)
 | 
			
		||||
		pack.NewVersion = ver.Candidate
 | 
			
		||||
		updatables[k] = pack
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get upgrade --dry-run")
 | 
			
		||||
func (o *debian) getUpdatablePackNames() (packNames []string, err error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get dist-upgrade --dry-run")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if r.isSuccess(0, 1) {
 | 
			
		||||
		return o.parseAptGetUpgrade(r.Stdout)
 | 
			
		||||
@@ -345,7 +409,7 @@ func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
 | 
			
		||||
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
 | 
			
		||||
func (o *debian) parseAptGetUpgrade(stdout string) (updatableNames []string, err error) {
 | 
			
		||||
	startRe := regexp.MustCompile(`The following packages will be upgraded:`)
 | 
			
		||||
	stopRe := regexp.MustCompile(`^(\d+) upgraded.*`)
 | 
			
		||||
	startLineFound, stopLineFound := false, false
 | 
			
		||||
@@ -360,21 +424,21 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
 | 
			
		||||
		}
 | 
			
		||||
		result := stopRe.FindStringSubmatch(line)
 | 
			
		||||
		if len(result) == 2 {
 | 
			
		||||
			numUpgradablePacks, err := strconv.Atoi(result[1])
 | 
			
		||||
			nUpdatable, err := strconv.Atoi(result[1])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages number. line: %s", line)
 | 
			
		||||
			}
 | 
			
		||||
			if numUpgradablePacks != len(upgradableNames) {
 | 
			
		||||
			if nUpdatable != len(updatableNames) {
 | 
			
		||||
				return nil, fmt.Errorf(
 | 
			
		||||
					"Failed to scan upgradable packages, expected: %s, detected: %d",
 | 
			
		||||
					result[1], len(upgradableNames))
 | 
			
		||||
					result[1], len(updatableNames))
 | 
			
		||||
			}
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			o.log.Debugf("Found the stop line. line: %s", line)
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		upgradableNames = append(upgradableNames, strings.Fields(line)...)
 | 
			
		||||
		updatableNames = append(updatableNames, strings.Fields(line)...)
 | 
			
		||||
	}
 | 
			
		||||
	if !startLineFound {
 | 
			
		||||
		// no upgrades
 | 
			
		||||
@@ -387,19 +451,28 @@ func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, er
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache.Meta) (models.VulnInfos, error) {
 | 
			
		||||
	resChan := make(chan struct {
 | 
			
		||||
		models.PackageInfo
 | 
			
		||||
		DetectedCveIDs
 | 
			
		||||
	}, len(upgradablePacks))
 | 
			
		||||
	errChan := make(chan error, len(upgradablePacks))
 | 
			
		||||
	reqChan := make(chan models.PackageInfo, len(upgradablePacks))
 | 
			
		||||
// DetectedCveID has CveID, Confidence and DetectionMethod fields
 | 
			
		||||
// LenientMatching will be true if this vulnerability is not detected by accurate version matching.
 | 
			
		||||
// see https://github.com/future-architect/vuls/pull/328
 | 
			
		||||
type DetectedCveID struct {
 | 
			
		||||
	CveID      string
 | 
			
		||||
	Confidence models.Confidence
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanVulnInfos(updatablePacks models.Packages, meta *cache.Meta) (models.VulnInfos, error) {
 | 
			
		||||
	type response struct {
 | 
			
		||||
		pack           *models.Package
 | 
			
		||||
		DetectedCveIDs []DetectedCveID
 | 
			
		||||
	}
 | 
			
		||||
	resChan := make(chan response, len(updatablePacks))
 | 
			
		||||
	errChan := make(chan error, len(updatablePacks))
 | 
			
		||||
	reqChan := make(chan models.Package, len(updatablePacks))
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, pack := range upgradablePacks {
 | 
			
		||||
		for _, pack := range updatablePacks {
 | 
			
		||||
			reqChan <- pack
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
@@ -407,50 +480,53 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache
 | 
			
		||||
	timeout := time.After(30 * 60 * time.Second)
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for range upgradablePacks {
 | 
			
		||||
	for range updatablePacks {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case pack := <-reqChan:
 | 
			
		||||
				func(p models.PackageInfo) {
 | 
			
		||||
				func(p models.Package) {
 | 
			
		||||
					changelog := o.getChangelogCache(meta, p)
 | 
			
		||||
					if 0 < len(changelog) {
 | 
			
		||||
						cveIDs, _ := o.getCveIDsFromChangelog(changelog, p.Name, p.Version)
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							DetectedCveIDs
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
						cveIDs, pack := o.getCveIDsFromChangelog(changelog, p.Name, p.Version)
 | 
			
		||||
						resChan <- response{pack, cveIDs}
 | 
			
		||||
						return
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// if the changelog is not in cache or failed to get from local cache,
 | 
			
		||||
					// get the changelog of the package via internet.
 | 
			
		||||
					// After that, store it in the cache.
 | 
			
		||||
					if cveIDs, err := o.scanPackageCveIDs(p); err != nil {
 | 
			
		||||
					if cveIDs, pack, err := o.scanPackageCveIDs(p); err != nil {
 | 
			
		||||
						errChan <- err
 | 
			
		||||
					} else {
 | 
			
		||||
						resChan <- struct {
 | 
			
		||||
							models.PackageInfo
 | 
			
		||||
							DetectedCveIDs
 | 
			
		||||
						}{p, cveIDs}
 | 
			
		||||
						resChan <- response{pack, cveIDs}
 | 
			
		||||
					}
 | 
			
		||||
				}(pack)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// { DetectedCveID{} : [packageInfo] }
 | 
			
		||||
	cvePackages := make(map[DetectedCveID][]models.PackageInfo)
 | 
			
		||||
	// { DetectedCveID{} : [package] }
 | 
			
		||||
	cvePackages := make(map[DetectedCveID][]string)
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
	for i := 0; i < len(upgradablePacks); i++ {
 | 
			
		||||
	for i := 0; i < len(updatablePacks); i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case pair := <-resChan:
 | 
			
		||||
			pack := pair.PackageInfo
 | 
			
		||||
			cveIDs := pair.DetectedCveIDs
 | 
			
		||||
			for _, cveID := range cveIDs {
 | 
			
		||||
				cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
 | 
			
		||||
		case response := <-resChan:
 | 
			
		||||
			if response.pack == nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s-%s : %s",
 | 
			
		||||
				i+1, len(upgradablePacks), pair.Name, pair.PackageInfo.Version, cveIDs)
 | 
			
		||||
			o.Packages[response.pack.Name] = *response.pack
 | 
			
		||||
			cves := response.DetectedCveIDs
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				packNames, ok := cvePackages[cve]
 | 
			
		||||
				if ok {
 | 
			
		||||
					packNames = append(packNames, response.pack.Name)
 | 
			
		||||
				} else {
 | 
			
		||||
					packNames = []string{response.pack.Name}
 | 
			
		||||
				}
 | 
			
		||||
				cvePackages[cve] = packNames
 | 
			
		||||
			}
 | 
			
		||||
			o.log.Infof("(%d/%d) Scanned %s: %s",
 | 
			
		||||
				i+1, len(updatablePacks), response.pack.Name, cves)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
@@ -466,17 +542,22 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache
 | 
			
		||||
		cveIDs = append(cveIDs, k)
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
 | 
			
		||||
	var vinfos models.VulnInfos
 | 
			
		||||
	for k, v := range cvePackages {
 | 
			
		||||
		vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
			CveID:      k.CveID,
 | 
			
		||||
			Confidence: k.Confidence,
 | 
			
		||||
			Packages:   v,
 | 
			
		||||
		})
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for cveID, names := range cvePackages {
 | 
			
		||||
		affected := models.PackageStatuses{}
 | 
			
		||||
		for _, n := range names {
 | 
			
		||||
			affected = append(affected, models.PackageStatus{Name: n})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vinfos[cveID.CveID] = models.VulnInfo{
 | 
			
		||||
			CveID:            cveID.CveID,
 | 
			
		||||
			Confidence:       cveID.Confidence,
 | 
			
		||||
			AffectedPackages: affected,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update meta package information of changelog cache to the latest one.
 | 
			
		||||
	meta.Packs = upgradablePacks
 | 
			
		||||
	meta.Packs = updatablePacks
 | 
			
		||||
	if err := cache.DB.RefreshMeta(*meta); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -484,10 +565,10 @@ func (o *debian) scanVulnInfos(upgradablePacks []models.PackageInfo, meta *cache
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) getChangelogCache(meta *cache.Meta, pack models.PackageInfo) string {
 | 
			
		||||
	cachedPack, found := meta.FindPack(pack.Name)
 | 
			
		||||
func (o *debian) getChangelogCache(meta *cache.Meta, pack models.Package) string {
 | 
			
		||||
	cachedPack, found := meta.Packs[pack.Name]
 | 
			
		||||
	if !found {
 | 
			
		||||
		o.log.Debugf("Not found: %s", pack.Name)
 | 
			
		||||
		o.log.Debugf("Not found in cache: %s", pack.Name)
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -512,12 +593,12 @@ func (o *debian) getChangelogCache(meta *cache.Meta, pack models.PackageInfo) st
 | 
			
		||||
	return changelog
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]DetectedCveID, error) {
 | 
			
		||||
func (o *debian) scanPackageCveIDs(pack models.Package) ([]DetectedCveID, *models.Package, error) {
 | 
			
		||||
	cmd := ""
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu", "raspbian":
 | 
			
		||||
	case config.Ubuntu, config.Raspbian:
 | 
			
		||||
		cmd = fmt.Sprintf(`PAGER=cat apt-get -q=2 changelog %s`, pack.Name)
 | 
			
		||||
	case "debian":
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
		cmd = fmt.Sprintf(`PAGER=cat aptitude -q=2 changelog %s`, pack.Name)
 | 
			
		||||
	}
 | 
			
		||||
	cmd = util.PrependProxyEnv(cmd)
 | 
			
		||||
@@ -526,151 +607,146 @@ func (o *debian) scanPackageCveIDs(pack models.PackageInfo) ([]DetectedCveID, er
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		o.log.Warnf("Failed to SSH: %s", r)
 | 
			
		||||
		// Ignore this Error.
 | 
			
		||||
		return nil, nil
 | 
			
		||||
		return nil, nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stdout := strings.Replace(r.Stdout, "\r", "", -1)
 | 
			
		||||
	cveIDs, clog := o.getCveIDsFromChangelog(
 | 
			
		||||
		stdout, pack.Name, pack.Version)
 | 
			
		||||
	cveIDs, clogFilledPack := o.getCveIDsFromChangelog(stdout, pack.Name, pack.Version)
 | 
			
		||||
 | 
			
		||||
	if clog.Method != models.FailedToGetChangelog {
 | 
			
		||||
		err := cache.DB.PutChangelog(o.getServerInfo().GetServerName(), pack.Name, clog.Contents)
 | 
			
		||||
	if clogFilledPack.Changelog.Method != models.FailedToGetChangelog {
 | 
			
		||||
		err := cache.DB.PutChangelog(
 | 
			
		||||
			o.getServerInfo().GetServerName(), pack.Name, pack.Changelog.Contents)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("Failed to put changelog into cache")
 | 
			
		||||
			return nil, nil, fmt.Errorf("Failed to put changelog into cache")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// No error will be returned. Only logging.
 | 
			
		||||
	return cveIDs, nil
 | 
			
		||||
	return cveIDs, clogFilledPack, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Debian Version Numbers
 | 
			
		||||
// https://readme.phys.ethz.ch/documentation/debian_version_numbers/
 | 
			
		||||
func (o *debian) getCveIDsFromChangelog(
 | 
			
		||||
	changelog, name, ver string) ([]DetectedCveID, models.Changelog) {
 | 
			
		||||
	changelog, name, ver string) ([]DetectedCveID, *models.Package) {
 | 
			
		||||
 | 
			
		||||
	if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
	if cveIDs, pack, err := o.parseChangelog(
 | 
			
		||||
		changelog, name, ver, models.ChangelogExactMatch); err == nil {
 | 
			
		||||
		return cveIDs, relevant
 | 
			
		||||
		return cveIDs, pack
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var verAfterColon string
 | 
			
		||||
	var err error
 | 
			
		||||
 | 
			
		||||
	splittedByColon := strings.Split(ver, ":")
 | 
			
		||||
	if 1 < len(splittedByColon) {
 | 
			
		||||
		verAfterColon = splittedByColon[1]
 | 
			
		||||
		if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
		if cveIDs, pack, err := o.parseChangelog(
 | 
			
		||||
			changelog, name, verAfterColon, models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
			return cveIDs, relevant
 | 
			
		||||
			return cveIDs, pack
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delim := []string{"+", "~", "build"}
 | 
			
		||||
	switch o.Distro.Family {
 | 
			
		||||
	case "ubuntu":
 | 
			
		||||
		delim = append(delim, "ubuntu")
 | 
			
		||||
	case "debian":
 | 
			
		||||
	case "Raspbian":
 | 
			
		||||
	case config.Ubuntu:
 | 
			
		||||
		delim = append(delim, config.Ubuntu)
 | 
			
		||||
	case config.Debian:
 | 
			
		||||
	case config.Raspbian:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range delim {
 | 
			
		||||
		ss := strings.Split(ver, d)
 | 
			
		||||
		if 1 < len(ss) {
 | 
			
		||||
			if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
			if cveIDs, pack, err := o.parseChangelog(
 | 
			
		||||
				changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
				return cveIDs, relevant
 | 
			
		||||
				return cveIDs, pack
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ss = strings.Split(verAfterColon, d)
 | 
			
		||||
		if 1 < len(ss) {
 | 
			
		||||
			if cveIDs, relevant, err := o.parseChangelog(
 | 
			
		||||
			if cveIDs, pack, err := o.parseChangelog(
 | 
			
		||||
				changelog, name, ss[0], models.ChangelogLenientMatch); err == nil {
 | 
			
		||||
				return cveIDs, relevant
 | 
			
		||||
				return cveIDs, pack
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only logging the error.
 | 
			
		||||
	o.log.Error(err)
 | 
			
		||||
 | 
			
		||||
	for i, p := range o.Packages {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			o.Packages[i].Changelog = models.Changelog{
 | 
			
		||||
				Contents: "",
 | 
			
		||||
				Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	o.log.Warnf("Failed to find the version in changelog: %s-%s", name, ver)
 | 
			
		||||
	o.log.Debugf("Changelog of : %s-%s", name, ver, changelog)
 | 
			
		||||
 | 
			
		||||
	// If the version is not in changelog, return entire changelog to put into cache
 | 
			
		||||
	return []DetectedCveID{}, models.Changelog{
 | 
			
		||||
	pack := o.Packages[name]
 | 
			
		||||
	pack.Changelog = models.Changelog{
 | 
			
		||||
		Contents: changelog,
 | 
			
		||||
		Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectedCveID has CveID, Confidence and DetectionMethod fields
 | 
			
		||||
// LenientMatching will be true if this vulnerability is not detected by accurate version matching.
 | 
			
		||||
// see https://github.com/future-architect/vuls/pull/328
 | 
			
		||||
type DetectedCveID struct {
 | 
			
		||||
	CveID      string
 | 
			
		||||
	Confidence models.Confidence
 | 
			
		||||
	return []DetectedCveID{}, &pack
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectedCveIDs is a slice of DetectedCveID
 | 
			
		||||
type DetectedCveIDs []DetectedCveID
 | 
			
		||||
 | 
			
		||||
var cveRe = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
 | 
			
		||||
 | 
			
		||||
// Collect CVE-IDs included in the changelog.
 | 
			
		||||
// The version which specified in argument(versionOrLater) is excluded.
 | 
			
		||||
func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, models.Changelog, error) {
 | 
			
		||||
// The version specified in argument(versionOrLater) is used to compare.
 | 
			
		||||
func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, *models.Package, error) {
 | 
			
		||||
	installedVer, err := version.NewVersion(ver)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, fmt.Errorf("Failed to parse installed version: %s, %s", ver, err)
 | 
			
		||||
	}
 | 
			
		||||
	buf, cveIDs := []string{}, []string{}
 | 
			
		||||
	stopRe := regexp.MustCompile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(ver)))
 | 
			
		||||
	stopLineFound := false
 | 
			
		||||
	lines := strings.Split(changelog, "\n")
 | 
			
		||||
	for _, line := range lines {
 | 
			
		||||
	scanner := bufio.NewScanner(strings.NewReader(changelog))
 | 
			
		||||
	found := false
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		line := scanner.Text()
 | 
			
		||||
		buf = append(buf, line)
 | 
			
		||||
		if match := stopRe.MatchString(line); match {
 | 
			
		||||
			//  o.log.Debugf("Found the stop line: %s", line)
 | 
			
		||||
			stopLineFound = true
 | 
			
		||||
			break
 | 
			
		||||
		} else if matches := cveRe.FindAllString(line, -1); 0 < len(matches) {
 | 
			
		||||
		if matches := cveRe.FindAllString(line, -1); 0 < len(matches) {
 | 
			
		||||
			for _, m := range matches {
 | 
			
		||||
				cveIDs = util.AppendIfMissing(cveIDs, m)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ss := strings.Fields(line)
 | 
			
		||||
		if len(ss) < 2 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !strings.HasPrefix(ss[1], "(") || !strings.HasSuffix(ss[1], ")") {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		clogVer, err := version.NewVersion(ss[1][1 : len(ss[1])-1])
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if installedVer.Equal(clogVer) || installedVer.GreaterThan(clogVer) {
 | 
			
		||||
			found = true
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if !stopLineFound {
 | 
			
		||||
		return nil, models.Changelog{
 | 
			
		||||
				Contents: "",
 | 
			
		||||
				Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
			}, fmt.Errorf(
 | 
			
		||||
				"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
				name,
 | 
			
		||||
				ver,
 | 
			
		||||
			)
 | 
			
		||||
 | 
			
		||||
	if !found {
 | 
			
		||||
		pack := o.Packages[name]
 | 
			
		||||
		pack.Changelog = models.Changelog{
 | 
			
		||||
			Contents: "",
 | 
			
		||||
			Method:   models.FailedToFindVersionInChangelog,
 | 
			
		||||
		}
 | 
			
		||||
		return nil, &pack, fmt.Errorf(
 | 
			
		||||
			"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
			
		||||
			name, ver)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clog := models.Changelog{
 | 
			
		||||
		Contents: strings.Join(buf, "\n"),
 | 
			
		||||
		Method:   string(confidence.DetectionMethod),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, p := range o.Packages {
 | 
			
		||||
		if p.Name == name {
 | 
			
		||||
			o.Packages[i].Changelog = clog
 | 
			
		||||
		}
 | 
			
		||||
		Contents: strings.Join(buf[0:len(buf)-1], "\n"),
 | 
			
		||||
		Method:   confidence.DetectionMethod,
 | 
			
		||||
	}
 | 
			
		||||
	pack := o.Packages[name]
 | 
			
		||||
	pack.Changelog = clog
 | 
			
		||||
 | 
			
		||||
	cves := []DetectedCveID{}
 | 
			
		||||
	for _, id := range cveIDs {
 | 
			
		||||
		cves = append(cves, DetectedCveID{id, confidence})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cves, clog, nil
 | 
			
		||||
	return cves, &pack, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
 | 
			
		||||
@@ -722,14 +798,3 @@ func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, err
 | 
			
		||||
	}
 | 
			
		||||
	return ver, fmt.Errorf("Unknown Format: %s", stdout)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
 | 
			
		||||
	for _, ele := range slice {
 | 
			
		||||
		if ele.Name == s.Name &&
 | 
			
		||||
			ele.Version == s.Version &&
 | 
			
		||||
			ele.Release == s.Release {
 | 
			
		||||
			return slice
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return append(slice, s)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,11 +22,11 @@ import (
 | 
			
		||||
	"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"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseScannedPackagesLineDebian(t *testing.T) {
 | 
			
		||||
@@ -66,7 +66,7 @@ func TestGetCveIDsFromChangelog(t *testing.T) {
 | 
			
		||||
		changelog models.Changelog
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			// verubuntu1
 | 
			
		||||
			//0 verubuntu1
 | 
			
		||||
			[]string{
 | 
			
		||||
				"systemd",
 | 
			
		||||
				"228-4ubuntu1",
 | 
			
		||||
@@ -81,9 +81,9 @@ systemd (228-4) unstable; urgency=medium
 | 
			
		||||
systemd (228-3) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `systemd (229-2) unstable; urgency=medium
 | 
			
		||||
@@ -92,13 +92,12 @@ systemd (228-6) unstable; urgency=medium
 | 
			
		||||
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
systemd (228-5) unstable; urgency=medium
 | 
			
		||||
systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
systemd (228-5) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// ver
 | 
			
		||||
			//1 ver
 | 
			
		||||
			[]string{
 | 
			
		||||
				"libpcre3",
 | 
			
		||||
				"2:8.35-7.1ubuntu1",
 | 
			
		||||
@@ -115,9 +114,9 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 pcre3 (2:8.35-7) unstable; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogExactMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `pcre3 (2:8.38-2) unstable; urgency=low
 | 
			
		||||
@@ -128,13 +127,12 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 pcre3 (2:8.35-7.2) unstable; urgency=low
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 pcre3 (2:8.35-7.1) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// ver-ubuntu3
 | 
			
		||||
			//2 ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"sysvinit",
 | 
			
		||||
				"2.88dsf-59.2ubuntu3",
 | 
			
		||||
@@ -168,13 +166,12 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 sysvinit (2.88dsf-59.3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
		 CVE-2015-3210: heap buffer overflow in pcre_compile2() /`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 1:ver-ubuntu3
 | 
			
		||||
			//3  1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27.1-1ubuntu3",
 | 
			
		||||
@@ -192,25 +189,25 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 util-linux (2.27-3ubuntu1) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// 1:ver-ubuntu3
 | 
			
		||||
			//4 1:ver-ubuntu3
 | 
			
		||||
			[]string{
 | 
			
		||||
				"bsdutils",
 | 
			
		||||
				"1:2.27-3ubuntu3",
 | 
			
		||||
@@ -228,29 +225,28 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2325", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-2326", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2015-3210", models.ChangelogLenientMatch},
 | 
			
		||||
			// {"CVE-2016-1000000", models.ChangelogLenientMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
		 CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
		 CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
		 CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
		 util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
		 util-linux (2.27.1-1) unstable; urgency=medium
 | 
			
		||||
		 util-linux (2.27-3) xenial; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
				// Contents: `util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-3) unstable; urgency=medium
 | 
			
		||||
				// CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
 | 
			
		||||
				// CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
 | 
			
		||||
				// CVE-2015-3210: CVE-2016-1000000heap buffer overflow in pcre_compile2() /
 | 
			
		||||
				// util-linux (2.27.1-2) unstable; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
 | 
			
		||||
				// util-linux (2.27.1-1) unstable; urgency=medium`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			// https://github.com/future-architect/vuls/pull/350
 | 
			
		||||
			//5 https://github.com/future-architect/vuls/pull/350
 | 
			
		||||
			[]string{
 | 
			
		||||
				"tar",
 | 
			
		||||
				"1.27.1-2+b1",
 | 
			
		||||
@@ -259,13 +255,12 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
			},
 | 
			
		||||
			[]DetectedCveID{
 | 
			
		||||
				{"CVE-2016-6321", models.ChangelogLenientMatch},
 | 
			
		||||
				{"CVE-2016-6321", models.ChangelogExactMatch},
 | 
			
		||||
			},
 | 
			
		||||
			models.Changelog{
 | 
			
		||||
				Contents: `tar (1.27.1-2+deb8u1) jessie-security; urgency=high
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.
 | 
			
		||||
		 tar (1.27.1-2) unstable; urgency=low`,
 | 
			
		||||
				Method: models.ChangelogLenientMatchStr,
 | 
			
		||||
		   * CVE-2016-6321: Bypassing the extract path name.`,
 | 
			
		||||
				Method: models.ChangelogExactMatchStr,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -273,7 +268,7 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
	d := newDebian(config.ServerInfo{})
 | 
			
		||||
	d.Distro.Family = "ubuntu"
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		aCveIDs, aClog := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		aCveIDs, aPack := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
 | 
			
		||||
		if len(aCveIDs) != len(tt.cveIDs) {
 | 
			
		||||
			t.Errorf("[%d] Len of return array are'nt same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
 | 
			
		||||
			t.Errorf(pp.Sprintf("%s", tt.in))
 | 
			
		||||
@@ -285,12 +280,12 @@ systemd (228-4) unstable; urgency=medium`,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aClog.Contents != tt.changelog.Contents {
 | 
			
		||||
			t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Contents, aClog.Contents))
 | 
			
		||||
		if aPack.Changelog.Contents != tt.changelog.Contents {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Contents, aPack.Changelog.Contents))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if aClog.Method != tt.changelog.Method {
 | 
			
		||||
			t.Errorf(pp.Sprintf("expected: %s, actual: %s", tt.changelog.Method, aClog.Method))
 | 
			
		||||
		if aPack.Changelog.Method != tt.changelog.Method {
 | 
			
		||||
			t.Error(pp.Sprintf("[%d] expected: %s, actual: %s", i, tt.changelog.Method, aPack.Changelog.Method))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -308,49 +303,7 @@ Reading state information... Done
 | 
			
		||||
The following packages will be upgraded:
 | 
			
		||||
  apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
 | 
			
		||||
  libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
 | 
			
		||||
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
 | 
			
		||||
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
 | 
			
		||||
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
 | 
			
		||||
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
				"ca-certificates",
 | 
			
		||||
@@ -391,124 +344,6 @@ The following packages will be upgraded:
 | 
			
		||||
  ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
 | 
			
		||||
  sysvinit-utils tzdata udev util-linux
 | 
			
		||||
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
 | 
			
		||||
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
 | 
			
		||||
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
 | 
			
		||||
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
 | 
			
		||||
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
 | 
			
		||||
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
 | 
			
		||||
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
 | 
			
		||||
`,
 | 
			
		||||
			[]string{
 | 
			
		||||
				"apt",
 | 
			
		||||
@@ -613,7 +448,7 @@ Calculating upgrade... Done
 | 
			
		||||
 | 
			
		||||
func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
	const servername = "server1"
 | 
			
		||||
	pack := models.PackageInfo{
 | 
			
		||||
	pack := models.Package{
 | 
			
		||||
		Name:       "apt",
 | 
			
		||||
		Version:    "1.0.0",
 | 
			
		||||
		NewVersion: "1.0.1",
 | 
			
		||||
@@ -624,7 +459,9 @@ func TestGetChangelogCache(t *testing.T) {
 | 
			
		||||
			Family:  "ubuntu",
 | 
			
		||||
			Release: "16.04",
 | 
			
		||||
		},
 | 
			
		||||
		Packs: []models.PackageInfo{pack},
 | 
			
		||||
		Packs: models.Packages{
 | 
			
		||||
			"apt": pack,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
 
 | 
			
		||||
@@ -33,10 +33,10 @@ import (
 | 
			
		||||
	"golang.org/x/crypto/ssh"
 | 
			
		||||
	"golang.org/x/crypto/ssh/agent"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	conf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type execResult struct {
 | 
			
		||||
@@ -148,6 +148,9 @@ func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result execResult) {
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debugf("Executing... %s", strings.Replace(cmd, "\n", "", -1))
 | 
			
		||||
 | 
			
		||||
	if c.Port == "local" &&
 | 
			
		||||
		(c.Host == "127.0.0.1" || c.Host == "localhost") {
 | 
			
		||||
		result = localExec(c, cmd, sudo)
 | 
			
		||||
@@ -157,7 +160,6 @@ func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (resul
 | 
			
		||||
		result = sshExecExternal(c, cmd, sudo)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger := getSSHLogger(log...)
 | 
			
		||||
	logger.Debug(result)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -165,7 +167,7 @@ func exec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (resul
 | 
			
		||||
func localExec(c conf.ServerInfo, cmdstr string, sudo bool) (result execResult) {
 | 
			
		||||
	cmdstr = decorateCmd(c, cmdstr, sudo)
 | 
			
		||||
	var cmd *ex.Cmd
 | 
			
		||||
	if c.Distro.Family == "FreeBSD" {
 | 
			
		||||
	if c.Distro.Family == conf.FreeBSD {
 | 
			
		||||
		cmd = ex.Command("/bin/sh", "-c", cmdstr)
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = ex.Command("/bin/bash", "-c", cmdstr)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										115
									
								
								scan/freebsd.go
									
									
									
									
									
								
							
							
						
						@@ -33,7 +33,14 @@ type bsd struct {
 | 
			
		||||
 | 
			
		||||
// NewBSD constructor
 | 
			
		||||
func newBsd(c config.ServerInfo) *bsd {
 | 
			
		||||
	d := &bsd{}
 | 
			
		||||
	d := &bsd{
 | 
			
		||||
		base: base{
 | 
			
		||||
			osPackages: osPackages{
 | 
			
		||||
				Packages:  models.Packages{},
 | 
			
		||||
				VulnInfos: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	d.log = util.NewCustomLogger(c)
 | 
			
		||||
	d.setServerInfo(c)
 | 
			
		||||
	return d
 | 
			
		||||
@@ -44,13 +51,13 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) {
 | 
			
		||||
	bsd = newBsd(c)
 | 
			
		||||
 | 
			
		||||
	// Prevent from adding `set -o pipefail` option
 | 
			
		||||
	c.Distro = config.Distro{Family: "FreeBSD"}
 | 
			
		||||
	c.Distro = config.Distro{Family: config.FreeBSD}
 | 
			
		||||
 | 
			
		||||
	if r := exec(c, "uname", noSudo); r.isSuccess() {
 | 
			
		||||
		if strings.Contains(r.Stdout, "FreeBSD") == true {
 | 
			
		||||
		if strings.Contains(strings.ToLower(r.Stdout), config.FreeBSD) == true {
 | 
			
		||||
			if b := exec(c, "freebsd-version", noSudo); b.isSuccess() {
 | 
			
		||||
				rel := strings.TrimSpace(b.Stdout)
 | 
			
		||||
				bsd.setDistro("FreeBSD", rel)
 | 
			
		||||
				bsd.setDistro(config.FreeBSD, rel)
 | 
			
		||||
				return true, bsd
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -66,28 +73,54 @@ func (o *bsd) checkIfSudoNoPasswd() error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) checkDependencies() error {
 | 
			
		||||
	o.log.Infof("Dependencies... No need")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanPackages() error {
 | 
			
		||||
	var err error
 | 
			
		||||
	var packs []models.PackageInfo
 | 
			
		||||
	if packs, err = o.scanInstalledPackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages")
 | 
			
		||||
	// collect the running kernel information
 | 
			
		||||
	release, version, err := o.runningKernel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setPackages(packs)
 | 
			
		||||
	o.Kernel = models.Kernel{
 | 
			
		||||
		Release: release,
 | 
			
		||||
		Version: version,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var vinfos []models.VulnInfo
 | 
			
		||||
	if vinfos, err = o.scanUnsecurePackages(); err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages")
 | 
			
		||||
	rebootRequired, err := o.rebootRequired()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.setVulnInfos(vinfos)
 | 
			
		||||
	o.Kernel.RebootRequired = rebootRequired
 | 
			
		||||
 | 
			
		||||
	packs, err := o.scanInstalledPackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.Packages = packs
 | 
			
		||||
 | 
			
		||||
	unsecures, err := o.scanUnsecurePackages()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	o.VulnInfos = unsecures
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 | 
			
		||||
func (o *bsd) rebootRequired() (bool, error) {
 | 
			
		||||
	r := o.exec("freebsd-version -k", noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
		return false, fmt.Errorf("Failed to SSH: %s", r)
 | 
			
		||||
	}
 | 
			
		||||
	return o.Kernel.Release != strings.TrimSpace(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanInstalledPackages() (models.Packages, error) {
 | 
			
		||||
	cmd := util.PrependProxyEnv("pkg version -v")
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
	if !r.isSuccess() {
 | 
			
		||||
@@ -96,7 +129,7 @@ func (o *bsd) scanInstalledPackages() ([]models.PackageInfo, error) {
 | 
			
		||||
	return o.parsePkgVersion(r.Stdout), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) {
 | 
			
		||||
	const vulndbPath = "/tmp/vuln.db"
 | 
			
		||||
	cmd := "rm -f " + vulndbPath
 | 
			
		||||
	r := o.exec(cmd, noSudo)
 | 
			
		||||
@@ -111,7 +144,7 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
	}
 | 
			
		||||
	if r.ExitStatus == 0 {
 | 
			
		||||
		// no vulnerabilities
 | 
			
		||||
		return []models.VulnInfo{}, nil
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var packAdtRslt []pkgAuditResult
 | 
			
		||||
@@ -121,7 +154,7 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
		if len(cveIDs) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		pack, found := o.Packages.FindByName(name)
 | 
			
		||||
		pack, found := o.Packages[name]
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("Vulnerable package: %s is not found", name)
 | 
			
		||||
		}
 | 
			
		||||
@@ -142,30 +175,38 @@ func (o *bsd) scanUnsecurePackages() (vulnInfos []models.VulnInfo, err error) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k := range cveIDAdtMap {
 | 
			
		||||
		packs := []models.PackageInfo{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
			packs = append(packs, r.pack)
 | 
			
		||||
	vinfos := models.VulnInfos{}
 | 
			
		||||
	for cveID := range cveIDAdtMap {
 | 
			
		||||
		packs := models.Packages{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			packs[r.pack.Name] = r.pack
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		disAdvs := []models.DistroAdvisory{}
 | 
			
		||||
		for _, r := range cveIDAdtMap[k] {
 | 
			
		||||
		for _, r := range cveIDAdtMap[cveID] {
 | 
			
		||||
			disAdvs = append(disAdvs, models.DistroAdvisory{
 | 
			
		||||
				AdvisoryID: r.vulnIDCveIDs.vulnID,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vulnInfos = append(vulnInfos, models.VulnInfo{
 | 
			
		||||
			CveID:            k,
 | 
			
		||||
			Packages:         packs,
 | 
			
		||||
		affected := models.PackageStatuses{}
 | 
			
		||||
		for name := range packs {
 | 
			
		||||
			affected = append(affected, models.PackageStatus{
 | 
			
		||||
				Name: name,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		vinfos[cveID] = models.VulnInfo{
 | 
			
		||||
			CveID:            cveID,
 | 
			
		||||
			AffectedPackages: affected,
 | 
			
		||||
			DistroAdvisories: disAdvs,
 | 
			
		||||
			Confidence:       models.PkgAuditMatch,
 | 
			
		||||
		})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
 | 
			
		||||
func (o *bsd) parsePkgVersion(stdout string) models.Packages {
 | 
			
		||||
	packs := models.Packages{}
 | 
			
		||||
	lines := strings.Split(stdout, "\n")
 | 
			
		||||
	for _, l := range lines {
 | 
			
		||||
		fields := strings.Fields(l)
 | 
			
		||||
@@ -180,20 +221,26 @@ func (o *bsd) parsePkgVersion(stdout string) (packs []models.PackageInfo) {
 | 
			
		||||
 | 
			
		||||
		switch fields[1] {
 | 
			
		||||
		case "?", "=":
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			})
 | 
			
		||||
			}
 | 
			
		||||
		case "<":
 | 
			
		||||
			candidate := strings.TrimSuffix(fields[6], ")")
 | 
			
		||||
			packs = append(packs, models.PackageInfo{
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:       name,
 | 
			
		||||
				Version:    ver,
 | 
			
		||||
				NewVersion: candidate,
 | 
			
		||||
			})
 | 
			
		||||
			}
 | 
			
		||||
		case ">":
 | 
			
		||||
			o.log.Warn("The installed version of the %s is newer than the current version. *This situation can arise with an out of date index file, or when testing new ports.*", name)
 | 
			
		||||
			packs[name] = models.Package{
 | 
			
		||||
				Name:    name,
 | 
			
		||||
				Version: ver,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
	return packs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type vulnIDCveIDs struct {
 | 
			
		||||
@@ -202,7 +249,7 @@ type vulnIDCveIDs struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type pkgAuditResult struct {
 | 
			
		||||
	pack         models.PackageInfo
 | 
			
		||||
	pack         models.Package
 | 
			
		||||
	vulnIDCveIDs vulnIDCveIDs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ import (
 | 
			
		||||
func TestParsePkgVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       string
 | 
			
		||||
		expected []models.PackageInfo
 | 
			
		||||
		expected models.Packages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			`Updating FreeBSD repository catalogue...
 | 
			
		||||
@@ -21,27 +21,32 @@ All repositories are up-to-date.
 | 
			
		||||
bash-4.2.45                        <   needs updating (remote has 4.3.42_1)
 | 
			
		||||
gettext-0.18.3.1                   <   needs updating (remote has 0.19.7)
 | 
			
		||||
tcl84-8.4.20_2,1                   =   up-to-date with remote
 | 
			
		||||
ntp-4.2.8p8_1                      >   succeeds port (port has 4.2.8p6)
 | 
			
		||||
teTeX-base-3.0_25                  ?   orphaned: print/teTeX-base`,
 | 
			
		||||
 | 
			
		||||
			[]models.PackageInfo{
 | 
			
		||||
				{
 | 
			
		||||
			models.Packages{
 | 
			
		||||
				"bash": {
 | 
			
		||||
					Name:       "bash",
 | 
			
		||||
					Version:    "4.2.45",
 | 
			
		||||
					NewVersion: "4.3.42_1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"gettext": {
 | 
			
		||||
					Name:       "gettext",
 | 
			
		||||
					Version:    "0.18.3.1",
 | 
			
		||||
					NewVersion: "0.19.7",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"tcl84": {
 | 
			
		||||
					Name:    "tcl84",
 | 
			
		||||
					Version: "8.4.20_2,1",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
				"teTeX-base": {
 | 
			
		||||
					Name:    "teTeX-base",
 | 
			
		||||
					Version: "3.0_25",
 | 
			
		||||
				},
 | 
			
		||||
				"ntp": {
 | 
			
		||||
					Name:    "ntp",
 | 
			
		||||
					Version: "4.2.8p8_1",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										991
									
								
								scan/redhat.go
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										1145
									
								
								scan/redhat_test.go
									
									
									
									
									
								
							
							
						
						@@ -59,18 +59,13 @@ type osTypeInterface interface {
 | 
			
		||||
// osPackages is included by base struct
 | 
			
		||||
type osPackages struct {
 | 
			
		||||
	// installed packages
 | 
			
		||||
	Packages models.PackageInfoList
 | 
			
		||||
	Packages models.Packages
 | 
			
		||||
 | 
			
		||||
	// unsecure packages
 | 
			
		||||
	VulnInfos models.VulnInfos
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setPackages(pi models.PackageInfoList) {
 | 
			
		||||
	p.Packages = pi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *osPackages) setVulnInfos(vi []models.VulnInfo) {
 | 
			
		||||
	p.VulnInfos = vi
 | 
			
		||||
	// kernel information
 | 
			
		||||
	Kernel models.Kernel
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectOS(c config.ServerInfo) (osType osTypeInterface) {
 | 
			
		||||
@@ -421,9 +416,15 @@ func setupChangelogCache() error {
 | 
			
		||||
	needToSetupCache := false
 | 
			
		||||
	for _, s := range servers {
 | 
			
		||||
		switch s.getDistro().Family {
 | 
			
		||||
		case "ubuntu", "debian", "raspbian":
 | 
			
		||||
		case config.Raspbian:
 | 
			
		||||
			needToSetupCache = true
 | 
			
		||||
			break
 | 
			
		||||
		case config.Ubuntu, config.Debian:
 | 
			
		||||
			//TODO changelopg cache for RedHat, Oracle, Amazon, CentOS is not implemented yet.
 | 
			
		||||
			if config.Conf.Deep {
 | 
			
		||||
				needToSetupCache = true
 | 
			
		||||
			}
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if needToSetupCache {
 | 
			
		||||
@@ -443,6 +444,7 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error {
 | 
			
		||||
	for _, s := range append(servers, errServers...) {
 | 
			
		||||
		r := s.convertToModel()
 | 
			
		||||
		r.ScannedAt = scannedAt
 | 
			
		||||
		r.Config.Scan = config.Conf
 | 
			
		||||
		results = append(results, r)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ func (o *unknown) checkIfSudoNoPasswd() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o unknown) checkDependencies() error {
 | 
			
		||||
func (o *unknown) checkDependencies() error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,8 @@ This is the Git repo of the official Docker image for vuls.
 | 
			
		||||
 | 
			
		||||
- go-cve-dictionary
 | 
			
		||||
  - [`latest` (*go-cve-dictionary:latest Dockerfile*)]()
 | 
			
		||||
- goval-dictionary
 | 
			
		||||
  - [`latest` (*goval-dictionary:latest Dockerfile*)]()
 | 
			
		||||
- vuls
 | 
			
		||||
  - [`latest` (*vuls:latest Dockerfile*)]()
 | 
			
		||||
- vulsrepo
 | 
			
		||||
@@ -28,6 +30,14 @@ $ docker run  --rm  vuls/go-cve-dictionary -v
 | 
			
		||||
go-cve-dictionary v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- goval-dictionary
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/goval-dictionary -v
 | 
			
		||||
 | 
			
		||||
goval-dictionary v0.0.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
@@ -44,6 +54,12 @@ vuls v0.0.xxx xxxx
 | 
			
		||||
$ docker rmi vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- goval-dictionary
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/goval-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -58,6 +74,12 @@ $ docker rmi vuls/vuls
 | 
			
		||||
$ docker pull vuls/go-cve-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- goval-dictionary
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/goval-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -72,6 +94,12 @@ $ docker run  --rm  vuls/go-cve-dictionary -v
 | 
			
		||||
go-cve-dictionary v0.1.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run  --rm  vuls/goval-dictionary -v
 | 
			
		||||
 | 
			
		||||
goval-dictionary v0.1.xxx xxxx
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- vuls
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
@@ -84,6 +112,7 @@ vuls v0.1.xxx xxxx
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
1. fetch nvd (vuls/go-cve-dictionary)
 | 
			
		||||
1. fetch oval (vuls/goval-dictionary)
 | 
			
		||||
1. configuration (vuls/vuls)
 | 
			
		||||
1. configtest (vuls/vuls)
 | 
			
		||||
1. scan (vuls/vuls)
 | 
			
		||||
@@ -100,6 +129,19 @@ $ for i in `seq 2002 $(date +"%Y")`; do \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- To fetch JVN(Japanese), See [README](https://github.com/kotakanbe/go-cve-dictionary#usage-fetch-jvn-data)
 | 
			
		||||
 | 
			
		||||
## Step2. Fetch OVAL (e.g. redhat)
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/goval-dictionary fetch-redhat 5 6 7
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- To fetch other OVAL, See [README](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-redhat)
 | 
			
		||||
 | 
			
		||||
## Step2. Configuration
 | 
			
		||||
 | 
			
		||||
Create config.toml referring to [this](https://github.com/future-architect/vuls#configuration).
 | 
			
		||||
@@ -149,6 +191,7 @@ $ docker run --rm -it \
 | 
			
		||||
    -v /etc/localtime:/etc/localtime:ro \
 | 
			
		||||
    vuls/vuls report \
 | 
			
		||||
    -cvedb-path=/vuls/cve.sqlite3 \
 | 
			
		||||
    -ovaldb-path=/vuls/oval.sqlite3 \
 | 
			
		||||
    -format-short-text \
 | 
			
		||||
    -config=./config.toml # path to config.toml in docker
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								setup/docker/goval-dictionary/latest/Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
			
		||||
FROM golang:latest
 | 
			
		||||
 | 
			
		||||
MAINTAINER sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
ENV REPOSITORY github.com/kotakanbe/goval-dictionary
 | 
			
		||||
ENV LOGDIR /var/log/vuls
 | 
			
		||||
ENV WORKDIR /vuls
 | 
			
		||||
# goval-dictionary install
 | 
			
		||||
RUN git clone https://$REPOSITORY.git $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && cd $GOPATH/src/$REPOSITORY \
 | 
			
		||||
    && make install \
 | 
			
		||||
    && mkdir -p $LOGDIR
 | 
			
		||||
 | 
			
		||||
VOLUME [$WORKDIR, $LOGDIR]
 | 
			
		||||
WORKDIR $WORKDIR
 | 
			
		||||
ENV PWD $WORKDIR
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["goval-dictionary"]
 | 
			
		||||
CMD ["--help"]
 | 
			
		||||
							
								
								
									
										125
									
								
								setup/docker/goval-dictionary/latest/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,125 @@
 | 
			
		||||
# goval-dictionary-Docker
 | 
			
		||||
 | 
			
		||||
This is the Git repo of the official Docker image for goval-dictionary.
 | 
			
		||||
See the [Hub page](https://hub.docker.com/r/vuls/goval-dictionary/) for the full readme on how to use the Docker image and for information regarding contributing and issues.
 | 
			
		||||
 | 
			
		||||
# Supported tags and respective `Dockerfile` links
 | 
			
		||||
 | 
			
		||||
- [`latest` (*goval-dictionary:latest Dockerfile*)](https://github.com/future-architect/vuls/blob/master/setup/docker/goval-dictionary/latest/Dockerfile)
 | 
			
		||||
 | 
			
		||||
# Caution
 | 
			
		||||
 | 
			
		||||
This image is built per commit.
 | 
			
		||||
If you want to use the latest docker image, you should remove the existing image, and pull it once again.
 | 
			
		||||
 | 
			
		||||
- Remove old docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker rmi vuls/goval-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- Pull new docker image
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker pull vuls/goval-dictionary
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
# What is goval-dictionary?
 | 
			
		||||
 | 
			
		||||
This is tool to build a local copy of the OVAL. The local copy is generated in sqlite format, and the tool has a server mode for easy querying.
 | 
			
		||||
 | 
			
		||||
# How to use this image
 | 
			
		||||
 | 
			
		||||
## check vuls version
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
$ docker run --rm vuls/goval-dictionary -v
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetch-redhat
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in `seq 5 7`; do \
 | 
			
		||||
    docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/goval-dictionary fetch-redhat $i; \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetch-debian
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in `seq 7 10`; do \
 | 
			
		||||
    docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/goval-dictionary fetch-debian $i; \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetch-ubuntu
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ for i in `seq 12 2 16`; do \
 | 
			
		||||
    docker run --rm -it \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
    vuls/goval-dictionary fetch-ubuntu $i; \
 | 
			
		||||
  done
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetch-suse
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$  docker run --rm -it \
 | 
			
		||||
  -v $PWD:/vuls \
 | 
			
		||||
  -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
  vuls/goval-dictionary fetch-suse -opensuse 13.2
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## fetch-oracle
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$  docker run --rm -it \
 | 
			
		||||
  -v $PWD:/vuls \
 | 
			
		||||
  -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
  vuls/goval-dictionary fetch-oracle
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## server
 | 
			
		||||
 | 
			
		||||
```console
 | 
			
		||||
$ docker run -dt \
 | 
			
		||||
    --name goval-dictionary \
 | 
			
		||||
    -v $PWD:/vuls \
 | 
			
		||||
    -v $PWD/goval-dictionary-log:/var/log/vuls \
 | 
			
		||||
    --expose 1324 \
 | 
			
		||||
    -p 1324:1324 \
 | 
			
		||||
    vuls/goval-dictionary server --bind=0.0.0.0
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Prease refer to [this](https://hub.docker.com/r/vuls/goval-dictionary).
 | 
			
		||||
 | 
			
		||||
## vuls
 | 
			
		||||
 | 
			
		||||
Please refer to [this](https://hub.docker.com/r/vuls/vuls/).
 | 
			
		||||
 | 
			
		||||
# User Feedback
 | 
			
		||||
 | 
			
		||||
## Documentation
 | 
			
		||||
 | 
			
		||||
Documentation for this image is stored in the [`docker/` directory](https://github.com/future-architect/vuls/tree/master/setup/docker) of the [`future-architect/vuls` GitHub repo](https://github.com/future-architect/vuls). 
 | 
			
		||||
 | 
			
		||||
## Issues
 | 
			
		||||
 | 
			
		||||
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/future-architect/vuls/issues). 
 | 
			
		||||
 | 
			
		||||
## Contributing
 | 
			
		||||
 | 
			
		||||
1. fork a repository: github.com/future-architect/vuls to github.com/you/repo
 | 
			
		||||
1. get original code: go get github.com/future-architect/vuls
 | 
			
		||||
1. work on original code
 | 
			
		||||
1. add remote to your repo: git remote add myfork https://github.com/you/repo.git
 | 
			
		||||
1. push your changes: git push myfork
 | 
			
		||||
1. create a new Pull Request
 | 
			
		||||
@@ -22,8 +22,8 @@ import (
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/Sirupsen/logrus"
 | 
			
		||||
	"github.com/rifflock/lfshook"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								util/util.go
									
									
									
									
									
								
							
							
						
						@@ -135,3 +135,15 @@ func Truncate(str string, length int) string {
 | 
			
		||||
	}
 | 
			
		||||
	return str
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Distinct a slice
 | 
			
		||||
func Distinct(ss []string) (distincted []string) {
 | 
			
		||||
	m := map[string]bool{}
 | 
			
		||||
	for _, s := range ss {
 | 
			
		||||
		if _, found := m[s]; !found {
 | 
			
		||||
			m[s] = true
 | 
			
		||||
			distincted = append(distincted, s)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||