Compare commits
	
		
			85 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					543dc99ecd | ||
| 
						 | 
					f0b3a8b1db | ||
| 
						 | 
					0b9ec05181 | ||
| 
						 | 
					0bf12412d6 | ||
| 
						 | 
					0ea4d58c63 | ||
| 
						 | 
					5755b00576 | ||
| 
						 | 
					1c8e074c9d | ||
| 
						 | 
					0e0e5ce4be | ||
| 
						 | 
					23dfe53885 | ||
| 
						 | 
					8e6351a9e4 | ||
| 
						 | 
					3086e2760f | ||
| 
						 | 
					b8db2e0b74 | ||
| 
						 | 
					43b46cb324 | ||
| 
						 | 
					d0559c7719 | ||
| 
						 | 
					231c63cf62 | ||
| 
						 | 
					2a9aebe059 | ||
| 
						 | 
					4e535d792f | ||
| 
						 | 
					4b487503d4 | ||
| 
						 | 
					0095c40e69 | ||
| 
						 | 
					82c1abfd3a | ||
| 
						 | 
					40988401bd | ||
| 
						 | 
					e8e3f4d138 | ||
| 
						 | 
					7eb77f5b51 | ||
| 
						 | 
					e115235299 | ||
| 
						 | 
					151d4b2d30 | ||
| 
						 | 
					e553f8b4c5 | ||
| 
						 | 
					47652ef0fb | ||
| 
						 | 
					ab0e950800 | ||
| 
						 | 
					a7b0ce1c85 | ||
| 
						 | 
					dc9c0edece | ||
| 
						 | 
					17ae386d1e | ||
| 
						 | 
					2d369d0cfe | ||
| 
						 | 
					c36e645d9b | ||
| 
						 | 
					40039c07e2 | ||
| 
						 | 
					a692cec0ef | ||
| 
						 | 
					e7ca491a94 | ||
| 
						 | 
					23f3e2fc11 | ||
| 
						 | 
					27b3e17b79 | ||
| 
						 | 
					740781af56 | ||
| 
						 | 
					36c9c229b8 | ||
| 
						 | 
					183fdcbdef | ||
| 
						 | 
					a2a697900a | ||
| 
						 | 
					6fef4db8a0 | ||
| 
						 | 
					e879ff1e9e | ||
| 
						 | 
					9bfe0627ae | ||
| 
						 | 
					0179f4299a | ||
| 
						 | 
					56017e57a0 | ||
| 
						 | 
					cda91e0906 | ||
| 
						 | 
					5d47adb5c9 | ||
| 
						 | 
					54e73c2f54 | ||
| 
						 | 
					2d075079f1 | ||
| 
						 | 
					2a8ee4b22b | ||
| 
						 | 
					1ec31d7be9 | ||
| 
						 | 
					02286b0c59 | ||
| 
						 | 
					1d0c5dea9f | ||
| 
						 | 
					1c4a12c4b7 | ||
| 
						 | 
					3f2ac45d71 | ||
| 
						 | 
					518f4dc039 | ||
| 
						 | 
					2cdeef4ffe | ||
| 
						 | 
					03579126fd | ||
| 
						 | 
					e3c27e1817 | ||
| 
						 | 
					aeaf308679 | ||
| 
						 | 
					f5e47bea40 | ||
| 
						 | 
					50cf13a7f2 | ||
| 
						 | 
					abd8041772 | ||
| 
						 | 
					847c6438e7 | ||
| 
						 | 
					ef8309df27 | ||
| 
						 | 
					0dff6cf983 | ||
| 
						 | 
					4c04acbd9e | ||
| 
						 | 
					1c4f231572 | ||
| 
						 | 
					51b8e169d2 | ||
| 
						 | 
					b4611ae9b7 | ||
| 
						 | 
					cd6722017b | ||
| 
						 | 
					290edffccf | ||
| 
						 | 
					64a6222bf9 | ||
| 
						 | 
					adb686b7c9 | ||
| 
						 | 
					d4af341b0f | ||
| 
						 | 
					fea7e93c8d | ||
| 
						 | 
					8b6b8d0f2e | ||
| 
						 | 
					4dcbd865cc | ||
| 
						 | 
					39b19444fe | ||
| 
						 | 
					644d5a5462 | ||
| 
						 | 
					8e18451e3f | ||
| 
						 | 
					3dbdd01f97 | ||
| 
						 | 
					a89079c005 | 
							
								
								
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
								
							@@ -19,7 +19,7 @@ jobs:
 | 
			
		||||
        name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v2
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: 1.15
 | 
			
		||||
          go-version: 1.16
 | 
			
		||||
      -
 | 
			
		||||
        name: Run GoReleaser
 | 
			
		||||
        uses: goreleaser/goreleaser-action@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,7 +11,7 @@ jobs:
 | 
			
		||||
    - name: Set up Go 1.x
 | 
			
		||||
      uses: actions/setup-go@v2
 | 
			
		||||
      with:
 | 
			
		||||
        go-version: 1.15.x
 | 
			
		||||
        go-version: 1.16.x
 | 
			
		||||
      id: go
 | 
			
		||||
 | 
			
		||||
    - name: Check out code into the Go module directory
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/tidy.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/tidy.yml
									
									
									
									
										vendored
									
									
								
							@@ -19,4 +19,4 @@ jobs:
 | 
			
		||||
          github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          git_user_name: kotakanbe
 | 
			
		||||
          git_user_email: kotakanbe@gmail.com
 | 
			
		||||
          go_version: 1.15.6
 | 
			
		||||
          go_version: 1.16.x
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.swp
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
@@ -9,9 +9,13 @@ coverage.out
 | 
			
		||||
issues/
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
results
 | 
			
		||||
!integration/data/results
 | 
			
		||||
config.toml
 | 
			
		||||
!setup/docker/*
 | 
			
		||||
.DS_Store
 | 
			
		||||
dist/
 | 
			
		||||
.idea
 | 
			
		||||
vuls.*
 | 
			
		||||
vuls
 | 
			
		||||
!cmd/vuls
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,8 @@ builds:
 | 
			
		||||
  main: ./cmd/scanner/main.go
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  - -tags=scanner
 | 
			
		||||
  tags:
 | 
			
		||||
  - scanner
 | 
			
		||||
  ldflags: 
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls-scanner
 | 
			
		||||
@@ -46,6 +47,8 @@ builds:
 | 
			
		||||
  - amd64
 | 
			
		||||
  - arm
 | 
			
		||||
  - arm64
 | 
			
		||||
  tags:
 | 
			
		||||
  - scanner
 | 
			
		||||
  main: ./contrib/trivy/cmd/main.go
 | 
			
		||||
  binary: trivy-to-vuls
 | 
			
		||||
 | 
			
		||||
@@ -61,7 +64,8 @@ builds:
 | 
			
		||||
  - arm64
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  - -tags=scanner
 | 
			
		||||
  tags:
 | 
			
		||||
  - scanner
 | 
			
		||||
  main: ./contrib/future-vuls/cmd/main.go
 | 
			
		||||
  binary: future-vuls
 | 
			
		||||
 | 
			
		||||
@@ -74,7 +78,6 @@ archives:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +88,6 @@ archives:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +98,6 @@ archives:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +108,6 @@ archives:
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
snapshot:
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,9 @@ COPY . $GOPATH/src/$REPOSITORY
 | 
			
		||||
RUN cd $GOPATH/src/$REPOSITORY && make install
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FROM alpine:3.11
 | 
			
		||||
FROM alpine:3.13
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
LABEL maintainer hikachan sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
ENV LOGDIR /var/log/vuls
 | 
			
		||||
ENV WORKDIR /vuls
 | 
			
		||||
@@ -22,6 +22,7 @@ RUN apk add --no-cache \
 | 
			
		||||
        openssh-client \
 | 
			
		||||
        ca-certificates \
 | 
			
		||||
        git \
 | 
			
		||||
        nmap \
 | 
			
		||||
    && mkdir -p $WORKDIR $LOGDIR
 | 
			
		||||
 | 
			
		||||
COPY --from=builder /go/bin/vuls /usr/local/bin/
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										169
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								GNUmakefile
									
									
									
									
									
								
							@@ -32,13 +32,13 @@ build: ./cmd/vuls/main.go pretest fmt
 | 
			
		||||
b: ./cmd/vuls/main.go 
 | 
			
		||||
	$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
install: ./cmd/vuls/main.go pretest fmt
 | 
			
		||||
install: ./cmd/vuls/main.go
 | 
			
		||||
	$(GO) install -ldflags "$(LDFLAGS)" ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
build-scanner: ./cmd/scanner/main.go pretest fmt
 | 
			
		||||
build-scanner: ./cmd/scanner/main.go 
 | 
			
		||||
	$(CGO_UNABLED) build -tags=scanner -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/scanner
 | 
			
		||||
 | 
			
		||||
install-scanner: ./cmd/scanner/main.go pretest fmt
 | 
			
		||||
install-scanner: ./cmd/scanner/main.go 
 | 
			
		||||
	$(CGO_UNABLED) install -tags=scanner -ldflags "$(LDFLAGS)" ./cmd/scanner
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
@@ -68,7 +68,7 @@ unused:
 | 
			
		||||
cov:
 | 
			
		||||
	@ go get -v github.com/axw/gocov/gocov
 | 
			
		||||
	@ go get golang.org/x/tools/cmd/cover
 | 
			
		||||
	gocov test | gocov report
 | 
			
		||||
	gocov test -v ./... | gocov report
 | 
			
		||||
 | 
			
		||||
clean:
 | 
			
		||||
	echo $(PKGS) | xargs go clean || exit;
 | 
			
		||||
@@ -80,3 +80,164 @@ build-trivy-to-vuls: pretest fmt
 | 
			
		||||
# future-vuls
 | 
			
		||||
build-future-vuls: pretest fmt
 | 
			
		||||
	$(GO) build -o future-vuls contrib/future-vuls/cmd/*.go
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# integration-test
 | 
			
		||||
BASE_DIR := '${PWD}/integration/results'
 | 
			
		||||
# $(shell mkdir -p ${BASE_DIR})
 | 
			
		||||
NOW=$(shell date --iso-8601=seconds)
 | 
			
		||||
NOW_JSON_DIR := '${BASE_DIR}/$(NOW)'
 | 
			
		||||
ONE_SEC_AFTER=$(shell date -d '+1 second' --iso-8601=seconds)
 | 
			
		||||
ONE_SEC_AFTER_JSON_DIR := '${BASE_DIR}/$(ONE_SEC_AFTER)'
 | 
			
		||||
LIBS := 'gemfile' 'pipfile' 'poetry' 'composer' 'packagelock' 'yarn' 'cargo' 'gomod' 'rails' 'cpe_vendor_product_match'
 | 
			
		||||
 | 
			
		||||
diff:
 | 
			
		||||
	# git clone git@github.com:vulsio/vulsctl.git
 | 
			
		||||
	# cd vulsctl/docker
 | 
			
		||||
	# ./update-all.sh
 | 
			
		||||
	# cd /path/to/vuls
 | 
			
		||||
	# vim integration/int-config.toml
 | 
			
		||||
	# ln -s vuls vuls.new
 | 
			
		||||
	# ln -s oldvuls vuls.old
 | 
			
		||||
	# make int
 | 
			
		||||
    # (ex. test 10 times: for i in `seq 10`; do make int ARGS=-quiet ; done)
 | 
			
		||||
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
 | 
			
		||||
	mv ${BASE_DIR} /tmp/${NOW}
 | 
			
		||||
endif
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	- cp integration/data/results/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
 | 
			
		||||
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	- cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${ONE_SEC_AFTER}
 | 
			
		||||
 | 
			
		||||
	$(call sed-d)
 | 
			
		||||
	- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
	$(call count-cve)
 | 
			
		||||
 | 
			
		||||
diff-redis:
 | 
			
		||||
	# docker network create redis-nw
 | 
			
		||||
    # docker run --name redis -d --network redis-nw -p 127.0.0.1:6379:6379 redis
 | 
			
		||||
	# git clone git@github.com:vulsio/vulsctl.git
 | 
			
		||||
	# cd vulsctl/docker
 | 
			
		||||
	# ./update-all-redis.sh
 | 
			
		||||
	# (or export DOCKER_NETWORK=redis-nw; cd /home/ubuntu/vulsctl/docker; ./update-all.sh --dbtype redis --dbpath "redis://redis/0")
 | 
			
		||||
	# vim integration/int-redis-config.toml
 | 
			
		||||
	# ln -s vuls vuls.new
 | 
			
		||||
	# ln -s oldvuls vuls.old
 | 
			
		||||
	# make int-redis
 | 
			
		||||
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
 | 
			
		||||
	mv ${BASE_DIR} /tmp/${NOW}
 | 
			
		||||
endif
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	./vuls.old scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	- cp integration/data/results/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${NOW}
 | 
			
		||||
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	- cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
 | 
			
		||||
 | 
			
		||||
	$(call sed-d)
 | 
			
		||||
	- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
	$(call count-cve)
 | 
			
		||||
 | 
			
		||||
diff-rdb-redis:
 | 
			
		||||
ifneq ($(shell ls -U1 ${BASE_DIR} | wc -l), 0)
 | 
			
		||||
	mv ${BASE_DIR} /tmp/${NOW}
 | 
			
		||||
endif
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	# new vs new
 | 
			
		||||
	./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp -f ${BASE_DIR}/current/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	cp integration/data/results/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml ${NOW}
 | 
			
		||||
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	sleep 1
 | 
			
		||||
	./vuls.new scan -config=./integration/int-config.toml --results-dir=${BASE_DIR} ${LIBS}
 | 
			
		||||
	cp -f ${BASE_DIR}/current/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	cp integration/data/results/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml ${ONE_SEC_AFTER}
 | 
			
		||||
 | 
			
		||||
	$(call sed-d)
 | 
			
		||||
	- diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
	$(call count-cve)
 | 
			
		||||
 | 
			
		||||
head= $(shell git rev-parse HEAD)
 | 
			
		||||
prev= $(shell git rev-parse HEAD^)
 | 
			
		||||
branch=$(shell git rev-parse --abbrev-ref HEAD)
 | 
			
		||||
build-integration:
 | 
			
		||||
	git stash
 | 
			
		||||
 | 
			
		||||
	# buld HEAD
 | 
			
		||||
	git checkout ${head}
 | 
			
		||||
	make build
 | 
			
		||||
	mv -f ./vuls ./vuls.${head}
 | 
			
		||||
 | 
			
		||||
	# HEAD^
 | 
			
		||||
	git checkout ${prev}
 | 
			
		||||
	make build
 | 
			
		||||
	mv -f ./vuls ./vuls.${prev}
 | 
			
		||||
 | 
			
		||||
	# master
 | 
			
		||||
	git checkout master
 | 
			
		||||
	make build
 | 
			
		||||
	mv -f ./vuls ./vuls.master
 | 
			
		||||
 | 
			
		||||
	# working tree
 | 
			
		||||
	git checkout ${branch}
 | 
			
		||||
	git stash apply stash@\{0\}
 | 
			
		||||
	make build
 | 
			
		||||
 | 
			
		||||
	# for integration testing, vuls.new and vuls.old needed.
 | 
			
		||||
	# ex)
 | 
			
		||||
	# $ ln -s ./vuls ./vuls.new
 | 
			
		||||
	# $ ln -s ./vuls.${head} ./vuls.old
 | 
			
		||||
	# or 
 | 
			
		||||
	# $ ln -s ./vuls.${prev} ./vuls.old
 | 
			
		||||
	# then
 | 
			
		||||
	# $ make diff
 | 
			
		||||
	# $ make diff-redis
 | 
			
		||||
	# $ make diff-rdb-redis
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
define sed-d
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/scannedAt/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/scannedAt/d' {} \;
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/reportedAt/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/reportedAt/d' {} \;
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/"Type":/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/"Type":/d' {} \;
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/"SQLite3Path":/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/"SQLite3Path":/d' {} \;
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/reportedRevision/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/reportedRevision/d' {} \;
 | 
			
		||||
	find ${NOW_JSON_DIR} -type f -exec sed -i -e '/scannedRevision/d' {} \;
 | 
			
		||||
	find ${ONE_SEC_AFTER_JSON_DIR} -type f -exec sed -i -e '/scannedRevision/d' {} \;
 | 
			
		||||
endef
 | 
			
		||||
 | 
			
		||||
define count-cve
 | 
			
		||||
	for jsonfile in ${NOW_JSON_DIR}/*.json ;  do \
 | 
			
		||||
		echo $$jsonfile; cat $$jsonfile | jq ".scannedCves | length" ; \
 | 
			
		||||
	done
 | 
			
		||||
	for jsonfile in ${ONE_SEC_AFTER_JSON_DIR}/*.json ;  do \
 | 
			
		||||
		echo $$jsonfile; cat $$jsonfile | jq ".scannedCves | length" ; \
 | 
			
		||||
	done
 | 
			
		||||
endef
 | 
			
		||||
@@ -50,7 +50,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
 | 
			
		||||
[Supports major Linux/FreeBSD](https://vuls.io/docs/en/supported-os.html)
 | 
			
		||||
 | 
			
		||||
- Alpine, Amazon Linux, CentOS, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, and Ubuntu
 | 
			
		||||
- Alpine, Amazon Linux, CentOS, Rocky Linux, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, and Ubuntu
 | 
			
		||||
- FreeBSD
 | 
			
		||||
- Cloud, on-premise, Running Docker Container
 | 
			
		||||
 | 
			
		||||
@@ -71,6 +71,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
  - [Alpine-secdb](https://git.alpinelinux.org/cgit/alpine-secdb/)
 | 
			
		||||
  - [Red Hat Security Advisories](https://access.redhat.com/security/security-updates/)
 | 
			
		||||
  - [Debian Security Bug Tracker](https://security-tracker.debian.org/tracker/)
 | 
			
		||||
  - [Ubuntu CVE Tracker](https://people.canonical.com/~ubuntu-security/cve/)
 | 
			
		||||
 | 
			
		||||
- Commands(yum, zypper, pkg-audit)
 | 
			
		||||
  - RHSA / ALAS / ELSA / FreeBSD-SA
 | 
			
		||||
@@ -100,15 +101,15 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
 | 
			
		||||
- Scan without root privilege, no dependencies
 | 
			
		||||
- Almost no load on the scan target server
 | 
			
		||||
- Offline mode scan with no internet access. (CentOS, Debian, Oracle Linux, Red Hat, and Ubuntu)
 | 
			
		||||
- Offline mode scan with no internet access. (CentOS, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu)
 | 
			
		||||
 | 
			
		||||
[Fast Root Scan](https://vuls.io/docs/en/architecture-fast-root-scan.html)
 | 
			
		||||
 | 
			
		||||
- Scan with root privilege
 | 
			
		||||
- Almost no load on the scan target server
 | 
			
		||||
- Detect processes affected by update using yum-ps (Amazon Linux, CentOS, Oracle Linux, and RedHat)
 | 
			
		||||
- Detect processes affected by update using yum-ps (Amazon Linux, CentOS, Rocky Linux, Oracle Linux, and RedHat)
 | 
			
		||||
- Detect processes which updated before but not restarting yet using checkrestart of debian-goodies (Debian and Ubuntu)
 | 
			
		||||
- Offline mode scan with no internet access. (CentOS, Debian, Oracle Linux, Red Hat, and Ubuntu)
 | 
			
		||||
- Offline mode scan with no internet access. (CentOS, Rocky Linux, Debian, Oracle Linux, Red Hat, and Ubuntu)
 | 
			
		||||
 | 
			
		||||
### [Remote, Local scan mode, Server mode](https://vuls.io/docs/en/architecture-remote-local.html)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								cache/bolt.go
									
									
									
									
										vendored
									
									
								
							@@ -5,8 +5,8 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -14,12 +14,12 @@ import (
 | 
			
		||||
// boltdb is used to store a cache of Changelogs of Ubuntu/Debian
 | 
			
		||||
type Bolt struct {
 | 
			
		||||
	Path string
 | 
			
		||||
	Log  *logrus.Entry
 | 
			
		||||
	Log  logging.Logger
 | 
			
		||||
	db   *bolt.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetupBolt opens a boltdb and creates a meta bucket if not exists.
 | 
			
		||||
func SetupBolt(path string, l *logrus.Entry) error {
 | 
			
		||||
func SetupBolt(path string, l logging.Logger) error {
 | 
			
		||||
	l.Infof("Open boltDB: %s", path)
 | 
			
		||||
	db, err := bolt.Open(path, 0600, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -47,7 +47,7 @@ func (b Bolt) Close() error {
 | 
			
		||||
	return b.db.Close()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//  CreateBucketIfNotExists creates a buket that is specified by arg.
 | 
			
		||||
//  CreateBucketIfNotExists creates a bucket that is specified by arg.
 | 
			
		||||
func (b *Bolt) createBucketIfNotExists(name string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		_, err := tx.CreateBucketIfNotExists([]byte(name))
 | 
			
		||||
@@ -93,7 +93,7 @@ func (b Bolt) RefreshMeta(meta Meta) error {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnsureBuckets puts a Meta information and create a buket that holds changelogs.
 | 
			
		||||
// EnsureBuckets puts a Meta information and create a bucket that holds changelogs.
 | 
			
		||||
func (b Bolt) EnsureBuckets(meta Meta) error {
 | 
			
		||||
	jsonBytes, err := json.Marshal(meta)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -159,7 +159,7 @@ func (b Bolt) GetChangelog(servername, packName string) (changelog string, err e
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PutChangelog put the changelgo of specified packName into the Bucket
 | 
			
		||||
// PutChangelog put the changelog of specified packName into the Bucket
 | 
			
		||||
func (b Bolt) PutChangelog(servername, packName, changelog string) error {
 | 
			
		||||
	return b.db.Update(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								cache/bolt_test.go
									
									
									
									
										vendored
									
									
								
							@@ -7,8 +7,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/boltdb/bolt"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const path = "/tmp/vuls-test-cache-11111111.db"
 | 
			
		||||
@@ -29,7 +29,7 @@ var meta = Meta{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestSetupBolt(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	log := logging.NewNormalLogger()
 | 
			
		||||
	err := SetupBolt(path, log)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
@@ -57,7 +57,7 @@ func TestSetupBolt(t *testing.T) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	log := logging.NewNormalLogger()
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -98,7 +98,7 @@ func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
func TestPutGetChangelog(t *testing.T) {
 | 
			
		||||
	clog := "changelog-text"
 | 
			
		||||
	log := logrus.NewEntry(&logrus.Logger{})
 | 
			
		||||
	log := logging.NewNormalLogger()
 | 
			
		||||
	if err := SetupBolt(path, log); err != nil {
 | 
			
		||||
		t.Errorf("Failed to setup bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,7 @@ func main() {
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
 | 
			
		||||
		fmt.Printf("vuls-%s-%s\n", config.Version, config.Revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										30
									
								
								config/awsconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								config/awsconf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// AWSConf is aws config
 | 
			
		||||
type AWSConf struct {
 | 
			
		||||
	// AWS profile to use
 | 
			
		||||
	Profile string `json:"profile"`
 | 
			
		||||
 | 
			
		||||
	// AWS region to use
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
 | 
			
		||||
	// S3 bucket name
 | 
			
		||||
	S3Bucket string `json:"s3Bucket"`
 | 
			
		||||
 | 
			
		||||
	// /bucket/path/to/results
 | 
			
		||||
	S3ResultsDir string `json:"s3ResultsDir"`
 | 
			
		||||
 | 
			
		||||
	// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
 | 
			
		||||
	S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
 | 
			
		||||
 | 
			
		||||
	Enabled bool `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c *AWSConf) Validate() (errs []error) {
 | 
			
		||||
	// TODO
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								config/azureconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								config/azureconf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AzureConf is azure config
 | 
			
		||||
type AzureConf struct {
 | 
			
		||||
	// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
 | 
			
		||||
	AccountName string `json:"accountName"`
 | 
			
		||||
 | 
			
		||||
	// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
 | 
			
		||||
	AccountKey string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// Azure storage container name
 | 
			
		||||
	ContainerName string `json:"containerName"`
 | 
			
		||||
 | 
			
		||||
	Enabled bool `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	azureAccount = "AZURE_STORAGE_ACCOUNT"
 | 
			
		||||
	azureKey     = "AZURE_STORAGE_ACCESS_KEY"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Validate configuration
 | 
			
		||||
func (c *AzureConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// overwrite if env var is not empty
 | 
			
		||||
	if os.Getenv(azureAccount) != "" {
 | 
			
		||||
		c.AccountName = os.Getenv(azureAccount)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(azureKey) != "" {
 | 
			
		||||
		c.AccountKey = os.Getenv(azureKey)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.ContainerName == "" {
 | 
			
		||||
		errs = append(errs, xerrors.Errorf("Azure storage container name is required"))
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -9,11 +9,12 @@ import (
 | 
			
		||||
type ChatWorkConf struct {
 | 
			
		||||
	APIToken string `json:"-"`
 | 
			
		||||
	Room     string `json:"-"`
 | 
			
		||||
	Enabled  bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *ChatWorkConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToChatWork {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.Room) == 0 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										328
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										328
									
								
								config/config.go
									
									
									
									
									
								
							@@ -3,12 +3,12 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	log "github.com/sirupsen/logrus"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -23,97 +23,80 @@ var Conf Config
 | 
			
		||||
 | 
			
		||||
//Config is struct of Configuration
 | 
			
		||||
type Config struct {
 | 
			
		||||
	Debug      bool   `json:"debug,omitempty"`
 | 
			
		||||
	DebugSQL   bool   `json:"debugSQL,omitempty"`
 | 
			
		||||
	Lang       string `json:"lang,omitempty"`
 | 
			
		||||
	logging.LogOpts
 | 
			
		||||
 | 
			
		||||
	// scan, report
 | 
			
		||||
	HTTPProxy  string `valid:"url" json:"httpProxy,omitempty"`
 | 
			
		||||
	LogDir     string `json:"logDir,omitempty"`
 | 
			
		||||
	ResultsDir string `json:"resultsDir,omitempty"`
 | 
			
		||||
	Pipe       bool   `json:"pipe,omitempty"`
 | 
			
		||||
	Quiet      bool   `json:"quiet,omitempty"`
 | 
			
		||||
	NoProgress bool   `json:"noProgress,omitempty"`
 | 
			
		||||
	SSHNative  bool   `json:"sshNative,omitempty"`
 | 
			
		||||
	Vvv        bool   `json:"vvv,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Default       ServerInfo            `json:"default,omitempty"`
 | 
			
		||||
	Servers       map[string]ServerInfo `json:"servers,omitempty"`
 | 
			
		||||
	CvssScoreOver float64               `json:"cvssScoreOver,omitempty"`
 | 
			
		||||
	Default ServerInfo            `json:"default,omitempty"`
 | 
			
		||||
	Servers map[string]ServerInfo `json:"servers,omitempty"`
 | 
			
		||||
 | 
			
		||||
	IgnoreUnscoredCves    bool `json:"ignoreUnscoredCves,omitempty"`
 | 
			
		||||
	IgnoreUnfixed         bool `json:"ignoreUnfixed,omitempty"`
 | 
			
		||||
	IgnoreGitHubDismissed bool `json:"ignore_git_hub_dismissed,omitempty"`
 | 
			
		||||
 | 
			
		||||
	CacheDBPath     string `json:"cacheDBPath,omitempty"`
 | 
			
		||||
	TrivyCacheDBDir string `json:"trivyCacheDBDir,omitempty"`
 | 
			
		||||
	ScanOpts
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	CveDict    GoCveDictConf  `json:"cveDict,omitempty"`
 | 
			
		||||
	OvalDict   GovalDictConf  `json:"ovalDict,omitempty"`
 | 
			
		||||
	Gost       GostConf       `json:"gost,omitempty"`
 | 
			
		||||
	Exploit    ExploitConf    `json:"exploit,omitempty"`
 | 
			
		||||
	Metasploit MetasploitConf `json:"metasploit,omitempty"`
 | 
			
		||||
 | 
			
		||||
	Slack    SlackConf    `json:"-"`
 | 
			
		||||
	EMail    SMTPConf     `json:"-"`
 | 
			
		||||
	HTTP     HTTPConf     `json:"-"`
 | 
			
		||||
	Syslog   SyslogConf   `json:"-"`
 | 
			
		||||
	AWS      AWSConf      `json:"-"`
 | 
			
		||||
	Azure    AzureConf    `json:"-"`
 | 
			
		||||
	ChatWork ChatWorkConf `json:"-"`
 | 
			
		||||
	Telegram TelegramConf `json:"-"`
 | 
			
		||||
	Slack      SlackConf      `json:"-"`
 | 
			
		||||
	EMail      SMTPConf       `json:"-"`
 | 
			
		||||
	HTTP       HTTPConf       `json:"-"`
 | 
			
		||||
	Syslog     SyslogConf     `json:"-"`
 | 
			
		||||
	AWS        AWSConf        `json:"-"`
 | 
			
		||||
	Azure      AzureConf      `json:"-"`
 | 
			
		||||
	ChatWork   ChatWorkConf   `json:"-"`
 | 
			
		||||
	GoogleChat GoogleChatConf `json:"-"`
 | 
			
		||||
	Telegram   TelegramConf   `json:"-"`
 | 
			
		||||
	WpScan     WpScanConf     `json:"-"`
 | 
			
		||||
	Saas       SaasConf       `json:"-"`
 | 
			
		||||
 | 
			
		||||
	WpScan WpScanConf `json:"WpScan,omitempty"`
 | 
			
		||||
	ReportOpts
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	Saas      SaasConf `json:"-"`
 | 
			
		||||
	DetectIPS bool     `json:"detectIps,omitempty"`
 | 
			
		||||
// ReportConf is an interface to Validate Report Config
 | 
			
		||||
type ReportConf interface {
 | 
			
		||||
	Validate() []error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	RefreshCve        bool `json:"refreshCve,omitempty"`
 | 
			
		||||
	ToSlack           bool `json:"toSlack,omitempty"`
 | 
			
		||||
	ToChatWork        bool `json:"toChatWork,omitempty"`
 | 
			
		||||
	ToTelegram        bool `json:"ToTelegram,omitempty"`
 | 
			
		||||
	ToEmail           bool `json:"toEmail,omitempty"`
 | 
			
		||||
	ToSyslog          bool `json:"toSyslog,omitempty"`
 | 
			
		||||
	ToLocalFile       bool `json:"toLocalFile,omitempty"`
 | 
			
		||||
	ToS3              bool `json:"toS3,omitempty"`
 | 
			
		||||
	ToAzureBlob       bool `json:"toAzureBlob,omitempty"`
 | 
			
		||||
	ToHTTP            bool `json:"toHTTP,omitempty"`
 | 
			
		||||
	FormatJSON        bool `json:"formatJSON,omitempty"`
 | 
			
		||||
	FormatOneEMail    bool `json:"formatOneEMail,omitempty"`
 | 
			
		||||
	FormatOneLineText bool `json:"formatOneLineText,omitempty"`
 | 
			
		||||
	FormatList        bool `json:"formatList,omitempty"`
 | 
			
		||||
	FormatFullText    bool `json:"formatFullText,omitempty"`
 | 
			
		||||
	FormatCsvList     bool `json:"formatCsvList,omitempty"`
 | 
			
		||||
	GZIP              bool `json:"gzip,omitempty"`
 | 
			
		||||
	Diff              bool `json:"diff,omitempty"`
 | 
			
		||||
// ScanOpts is options for scan
 | 
			
		||||
type ScanOpts struct {
 | 
			
		||||
	Vvv bool `json:"vvv,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportOpts is options for report
 | 
			
		||||
type ReportOpts struct {
 | 
			
		||||
	// refactored
 | 
			
		||||
	CvssScoreOver      float64 `json:"cvssScoreOver,omitempty"`
 | 
			
		||||
	TrivyCacheDBDir    string  `json:"trivyCacheDBDir,omitempty"`
 | 
			
		||||
	NoProgress         bool    `json:"noProgress,omitempty"`
 | 
			
		||||
	RefreshCve         bool    `json:"refreshCve,omitempty"`
 | 
			
		||||
	IgnoreUnfixed      bool    `json:"ignoreUnfixed,omitempty"`
 | 
			
		||||
	IgnoreUnscoredCves bool    `json:"ignoreUnscoredCves,omitempty"`
 | 
			
		||||
	DiffPlus           bool    `json:"diffPlus,omitempty"`
 | 
			
		||||
	DiffMinus          bool    `json:"diffMinus,omitempty"`
 | 
			
		||||
	Diff               bool    `json:"diff,omitempty"`
 | 
			
		||||
	Lang               string  `json:"lang,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnConfigtest validates
 | 
			
		||||
func (c Config) ValidateOnConfigtest() bool {
 | 
			
		||||
	errs := c.checkSSHKeyExist()
 | 
			
		||||
 | 
			
		||||
	if runtime.GOOS == "windows" && !c.SSHNative {
 | 
			
		||||
		errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnScan validates configuration
 | 
			
		||||
func (c Config) ValidateOnScan() bool {
 | 
			
		||||
	errs := c.checkSSHKeyExist()
 | 
			
		||||
 | 
			
		||||
	if runtime.GOOS == "windows" && !c.SSHNative {
 | 
			
		||||
		errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
@@ -121,29 +104,28 @@ func (c Config) ValidateOnScan() bool {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.CacheDBPath) != 0 {
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(c.CacheDBPath); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s",
 | 
			
		||||
				c.CacheDBPath))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	for _, server := range c.Servers {
 | 
			
		||||
		if !server.Module.IsScanPort() {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if es := server.PortScan.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (c Config) checkSSHKeyExist() (errs []error) {
 | 
			
		||||
	for serverName, v := range c.Servers {
 | 
			
		||||
		if v.Type == ServerTypePseudo {
 | 
			
		||||
		if v.Type == constant.ServerTypePseudo {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if v.KeyPath != "" {
 | 
			
		||||
@@ -156,39 +138,8 @@ func (c Config) checkSSHKeyExist() (errs []error) {
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnReportDB validates configuration
 | 
			
		||||
func (c Config) ValidateOnReportDB() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("cvedb", c.CveDict.Type, c.CveDict.SQLite3Path, c.CveDict.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("ovaldb", c.OvalDict.Type, c.OvalDict.SQLite3Path, c.OvalDict.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("gostdb", c.Gost.Type, c.Gost.SQLite3Path, c.Gost.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("exploitdb", c.Exploit.Type, c.Exploit.SQLite3Path, c.Exploit.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("msfdb", c.Metasploit.Type, c.Metasploit.SQLite3Path, c.Metasploit.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnReport validates configuration
 | 
			
		||||
func (c Config) ValidateOnReport() bool {
 | 
			
		||||
func (c *Config) ValidateOnReport() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
@@ -203,54 +154,39 @@ func (c Config) ValidateOnReport() bool {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 | 
			
		||||
		errs = append(errs, mailerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
 | 
			
		||||
		errs = append(errs, slackerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if chatworkerrs := c.ChatWork.Validate(); 0 < len(chatworkerrs) {
 | 
			
		||||
		errs = append(errs, chatworkerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if telegramerrs := c.Telegram.Validate(); 0 < len(telegramerrs) {
 | 
			
		||||
		errs = append(errs, telegramerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) {
 | 
			
		||||
		errs = append(errs, syslogerrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if httperrs := c.HTTP.Validate(); 0 < len(httperrs) {
 | 
			
		||||
		errs = append(errs, httperrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateOnTui validates configuration
 | 
			
		||||
func (c Config) ValidateOnTui() bool {
 | 
			
		||||
	errs := []error{}
 | 
			
		||||
 | 
			
		||||
	if len(c.ResultsDir) != 0 {
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(c.ResultsDir); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
			
		||||
	for _, rc := range []ReportConf{
 | 
			
		||||
		&c.EMail,
 | 
			
		||||
		&c.Slack,
 | 
			
		||||
		&c.ChatWork,
 | 
			
		||||
		&c.GoogleChat,
 | 
			
		||||
		&c.Telegram,
 | 
			
		||||
		&c.Syslog,
 | 
			
		||||
		&c.HTTP,
 | 
			
		||||
		&c.AWS,
 | 
			
		||||
		&c.Azure,
 | 
			
		||||
	} {
 | 
			
		||||
		if es := rc.Validate(); 0 < len(es) {
 | 
			
		||||
			errs = append(errs, es...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := validateDB("cvedb", c.CveDict.Type, c.CveDict.SQLite3Path, c.CveDict.URL); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	for _, cnf := range []VulnDictInterface{
 | 
			
		||||
		&Conf.CveDict,
 | 
			
		||||
		&Conf.OvalDict,
 | 
			
		||||
		&Conf.Gost,
 | 
			
		||||
		&Conf.Exploit,
 | 
			
		||||
		&Conf.Metasploit,
 | 
			
		||||
	} {
 | 
			
		||||
		if err := cnf.Validate(); err != nil {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("Failed to validate %s: %+v", cnf.GetName(), err))
 | 
			
		||||
		}
 | 
			
		||||
		if err := cnf.CheckHTTPHealth(); err != nil {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("Run %s as server mode before reporting: %+v", cnf.GetName(), err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, err := range errs {
 | 
			
		||||
		log.Error(err)
 | 
			
		||||
		logging.Log.Error(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return len(errs) == 0
 | 
			
		||||
@@ -260,86 +196,14 @@ func (c Config) ValidateOnTui() bool {
 | 
			
		||||
func (c Config) ValidateOnSaaS() bool {
 | 
			
		||||
	saaserrs := c.Saas.Validate()
 | 
			
		||||
	for _, err := range saaserrs {
 | 
			
		||||
		log.Error("Failed to validate SaaS conf: %+w", err)
 | 
			
		||||
		logging.Log.Error("Failed to validate SaaS conf: %+w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return len(saaserrs) == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// validateDB validates configuration
 | 
			
		||||
func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error {
 | 
			
		||||
	log.Infof("-%s-type: %s, -%s-url: %s, -%s-path: %s",
 | 
			
		||||
		dictionaryDBName, dbType, dictionaryDBName, dbURL, dictionaryDBName, dbPath)
 | 
			
		||||
 | 
			
		||||
	switch dbType {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if dbURL != "" {
 | 
			
		||||
			return xerrors.Errorf("To use SQLite3, specify -%s-type=sqlite3 and -%s-path. To use as http server mode, specify -%s-type=http and -%s-url",
 | 
			
		||||
				dictionaryDBName, dictionaryDBName, dictionaryDBName, dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(dbPath); !ok {
 | 
			
		||||
			return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. -%s-path: %s",
 | 
			
		||||
				dictionaryDBName, dbPath)
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return xerrors.Errorf(`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return xerrors.Errorf(`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "redis":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return xerrors.Errorf(`Redis connection string is needed. -%s-url="redis://localhost/0"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	case "http":
 | 
			
		||||
		if dbURL == "" {
 | 
			
		||||
			return xerrors.Errorf(`URL is needed. -%s-url="http://localhost:1323"`,
 | 
			
		||||
				dictionaryDBName)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return xerrors.Errorf("%s type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'.  -%s-type: %s",
 | 
			
		||||
			dictionaryDBName, dictionaryDBName, dbType)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AWSConf is aws config
 | 
			
		||||
type AWSConf struct {
 | 
			
		||||
	// AWS profile to use
 | 
			
		||||
	Profile string `json:"profile"`
 | 
			
		||||
 | 
			
		||||
	// AWS region to use
 | 
			
		||||
	Region string `json:"region"`
 | 
			
		||||
 | 
			
		||||
	// S3 bucket name
 | 
			
		||||
	S3Bucket string `json:"s3Bucket"`
 | 
			
		||||
 | 
			
		||||
	// /bucket/path/to/results
 | 
			
		||||
	S3ResultsDir string `json:"s3ResultsDir"`
 | 
			
		||||
 | 
			
		||||
	// The Server-side encryption algorithm used when storing the reports in S3 (e.g., AES256, aws:kms).
 | 
			
		||||
	S3ServerSideEncryption string `json:"s3ServerSideEncryption"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AzureConf is azure config
 | 
			
		||||
type AzureConf struct {
 | 
			
		||||
	// Azure account name to use. AZURE_STORAGE_ACCOUNT environment variable is used if not specified
 | 
			
		||||
	AccountName string `json:"accountName"`
 | 
			
		||||
 | 
			
		||||
	// Azure account key to use. AZURE_STORAGE_ACCESS_KEY environment variable is used if not specified
 | 
			
		||||
	AccountKey string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// Azure storage container name
 | 
			
		||||
	ContainerName string `json:"containerName"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WpScanConf is wpscan.com config
 | 
			
		||||
type WpScanConf struct {
 | 
			
		||||
	Token          string `toml:"Token,omitempty" json:"-"`
 | 
			
		||||
	Token          string `toml:"token,omitempty" json:"-"`
 | 
			
		||||
	DetectInactive bool   `toml:"detectInactive,omitempty" json:"detectInactive,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -352,7 +216,6 @@ type ServerInfo struct {
 | 
			
		||||
	Port               string                      `toml:"port,omitempty" json:"port,omitempty"`
 | 
			
		||||
	SSHConfigPath      string                      `toml:"sshConfigPath,omitempty" json:"sshConfigPath,omitempty"`
 | 
			
		||||
	KeyPath            string                      `toml:"keyPath,omitempty" json:"keyPath,omitempty"`
 | 
			
		||||
	KeyPassword        string                      `json:"-" toml:"-"`
 | 
			
		||||
	CpeNames           []string                    `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"`
 | 
			
		||||
	ScanMode           []string                    `toml:"scanMode,omitempty" json:"scanMode,omitempty"`
 | 
			
		||||
	ScanModules        []string                    `toml:"scanModules,omitempty" json:"scanModules,omitempty"`
 | 
			
		||||
@@ -367,7 +230,7 @@ type ServerInfo struct {
 | 
			
		||||
	GitHubRepos        map[string]GitHubConf       `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo
 | 
			
		||||
	UUIDs              map[string]string           `toml:"uuids,omitempty" json:"uuids,omitempty"`
 | 
			
		||||
	Memo               string                      `toml:"memo,omitempty" json:"memo,omitempty"`
 | 
			
		||||
	Enablerepo         []string                    `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, RHEL, Amazon
 | 
			
		||||
	Enablerepo         []string                    `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, Rocky, RHEL, Amazon
 | 
			
		||||
	Optional           map[string]interface{}      `toml:"optional,omitempty" json:"optional,omitempty"`     // Optional key-value set that will be outputted to JSON
 | 
			
		||||
	Lockfiles          []string                    `toml:"lockfiles,omitempty" json:"lockfiles,omitempty"`   // ie) path/to/package-lock.json
 | 
			
		||||
	FindLock           bool                        `toml:"findLock,omitempty" json:"findLock,omitempty"`
 | 
			
		||||
@@ -375,8 +238,9 @@ type ServerInfo struct {
 | 
			
		||||
	IgnoredJSONKeys    []string                    `toml:"ignoredJSONKeys,omitempty" json:"ignoredJSONKeys,omitempty"`
 | 
			
		||||
	IPv4Addrs          []string                    `toml:"-" json:"ipv4Addrs,omitempty"`
 | 
			
		||||
	IPv6Addrs          []string                    `toml:"-" json:"ipv6Addrs,omitempty"`
 | 
			
		||||
	IPSIdentifiers     map[IPS]string              `toml:"-" json:"ipsIdentifiers,omitempty"`
 | 
			
		||||
	IPSIdentifiers     map[string]string           `toml:"-" json:"ipsIdentifiers,omitempty"`
 | 
			
		||||
	WordPress          *WordPressConf              `toml:"wordpress,omitempty" json:"wordpress,omitempty"`
 | 
			
		||||
	PortScan           *PortScanConf               `toml:"portscan,omitempty" json:"portscan,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// internal use
 | 
			
		||||
	LogMsgAnsiColor string     `toml:"-" json:"-"` // DebugLog Color
 | 
			
		||||
@@ -408,7 +272,8 @@ func (cnf WordPressConf) IsZero() bool {
 | 
			
		||||
 | 
			
		||||
// GitHubConf is used for GitHub Security Alerts
 | 
			
		||||
type GitHubConf struct {
 | 
			
		||||
	Token string `json:"-"`
 | 
			
		||||
	Token                 string `json:"-"`
 | 
			
		||||
	IgnoreGitHubDismissed bool   `json:"ignoreGitHubDismissed,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetServerName returns ServerName if this serverInfo is about host.
 | 
			
		||||
@@ -432,7 +297,7 @@ func (l Distro) String() string {
 | 
			
		||||
 | 
			
		||||
// MajorVersion returns Major version
 | 
			
		||||
func (l Distro) MajorVersion() (int, error) {
 | 
			
		||||
	if l.Family == Amazon {
 | 
			
		||||
	if l.Family == constant.Amazon {
 | 
			
		||||
		if isAmazonLinux1(l.Release) {
 | 
			
		||||
			return 1, nil
 | 
			
		||||
		}
 | 
			
		||||
@@ -460,8 +325,3 @@ type Container struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Image       string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnSrcConf is an interface of vulnsrc
 | 
			
		||||
type VulnSrcConf interface {
 | 
			
		||||
	CheckHTTPHealth() error
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,8 @@ package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	. "github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSyslogConfValidate(t *testing.T) {
 | 
			
		||||
@@ -55,7 +57,7 @@ func TestSyslogConfValidate(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		Conf.ToSyslog = true
 | 
			
		||||
		tt.conf.Enabled = true
 | 
			
		||||
		errs := tt.conf.Validate()
 | 
			
		||||
		if len(errs) != tt.expectedErrLength {
 | 
			
		||||
			t.Errorf("test: %d, expected %d, actual %d", i, tt.expectedErrLength, len(errs))
 | 
			
		||||
 
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ExploitConf is exploit config
 | 
			
		||||
type ExploitConf struct {
 | 
			
		||||
	// DB type for exploit dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://exploit-dictionary.com:1324 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/exploit.sqlite3
 | 
			
		||||
	SQLite3Path string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cnf *ExploitConf) setDefault() {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, "go-exploitdb.sqlite3")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const exploitDBType = "EXPLOITDB_TYPE"
 | 
			
		||||
const exploitDBURL = "EXPLOITDB_URL"
 | 
			
		||||
const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *ExploitConf) Init() {
 | 
			
		||||
	if os.Getenv(exploitDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(exploitDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(exploitDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(exploitDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(exploitDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(exploitDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns wether fetch via http
 | 
			
		||||
func (cnf *ExploitConf) IsFetchViaHTTP() bool {
 | 
			
		||||
	return Conf.Exploit.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (cnf *ExploitConf) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	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 xerrors.Errorf("Failed to connect to exploit server. url: %s, errs: %s", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GoCveDictConf is go-cve-dictionary config
 | 
			
		||||
type GoCveDictConf struct {
 | 
			
		||||
	// DB type of CVE dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://cve-dictionary.com:1323 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/cve.sqlite3
 | 
			
		||||
	SQLite3Path string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cnf *GoCveDictConf) setDefault() {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, "cve.sqlite3")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cveDBType = "CVEDB_TYPE"
 | 
			
		||||
const cveDBURL = "CVEDB_URL"
 | 
			
		||||
const cveDBPATH = "CVEDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GoCveDictConf) Init() {
 | 
			
		||||
	if os.Getenv(cveDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(cveDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(cveDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(cveDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(cveDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(cveDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns wether fetch via http
 | 
			
		||||
func (cnf *GoCveDictConf) IsFetchViaHTTP() bool {
 | 
			
		||||
	return Conf.CveDict.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth checks http server status
 | 
			
		||||
func (cnf *GoCveDictConf) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().SetDebug(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 xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %s",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								config/googlechatconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								config/googlechatconf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GoogleChatConf is GoogleChat config
 | 
			
		||||
type GoogleChatConf struct {
 | 
			
		||||
	WebHookURL       string `valid:"url" json:"-" toml:"webHookURL,omitempty"`
 | 
			
		||||
	SkipIfNoCve      bool   `valid:"type(bool)" json:"-" toml:"skipIfNoCve"`
 | 
			
		||||
	ServerNameRegexp string `valid:"type(string)" json:"-" toml:"serverNameRegexp,omitempty"`
 | 
			
		||||
	Enabled          bool   `valid:"type(bool)" json:"-" toml:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *GoogleChatConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.WebHookURL) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("googleChatConf.webHookURL must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if !govalidator.IsRegex(c.ServerNameRegexp) {
 | 
			
		||||
		errs = append(errs, xerrors.New("googleChatConf.serverNameRegexp must be regex"))
 | 
			
		||||
	}
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GostConf is gost config
 | 
			
		||||
type GostConf struct {
 | 
			
		||||
	// DB type for gost dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://gost-dictionary.com:1324 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/gost.sqlite3
 | 
			
		||||
	SQLite3Path string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cnf *GostConf) setDefault() {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, "gost.sqlite3")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const gostDBType = "GOSTDB_TYPE"
 | 
			
		||||
const gostDBURL = "GOSTDB_URL"
 | 
			
		||||
const gostDBPATH = "GOSTDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GostConf) Init() {
 | 
			
		||||
	if os.Getenv(gostDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(gostDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(gostDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(gostDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(gostDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(gostDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns wether fetch via http
 | 
			
		||||
func (cnf *GostConf) IsFetchViaHTTP() bool {
 | 
			
		||||
	return Conf.Gost.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (cnf *GostConf) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	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 xerrors.Errorf("Failed to connect to gost server. url: %s, errs: %s", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GovalDictConf is goval-dictionary config
 | 
			
		||||
type GovalDictConf struct {
 | 
			
		||||
 | 
			
		||||
	// DB type of OVAL dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://goval-dictionary.com:1324 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/oval.sqlite3
 | 
			
		||||
	SQLite3Path string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cnf *GovalDictConf) setDefault() {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, "oval.sqlite3")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const govalType = "OVALDB_TYPE"
 | 
			
		||||
const govalURL = "OVALDB_URL"
 | 
			
		||||
const govalPATH = "OVALDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GovalDictConf) Init() {
 | 
			
		||||
	if os.Getenv(govalType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(govalType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(govalURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(govalURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(govalPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(govalPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns wether fetch via http
 | 
			
		||||
func (cnf *GovalDictConf) IsFetchViaHTTP() bool {
 | 
			
		||||
	return Conf.OvalDict.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (cnf *GovalDictConf) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	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 xerrors.Errorf("Failed to request to OVAL server. url: %s, errs: %s",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -8,31 +8,25 @@ import (
 | 
			
		||||
 | 
			
		||||
// HTTPConf is HTTP config
 | 
			
		||||
type HTTPConf struct {
 | 
			
		||||
	URL string `valid:"url" json:"-"`
 | 
			
		||||
	URL     string `valid:"url" json:"-"`
 | 
			
		||||
	Enabled bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const httpKey = "VULS_HTTP_URL"
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *HTTPConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToHTTP {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// overwrite if env var is not empty
 | 
			
		||||
	if os.Getenv(httpKey) != "" {
 | 
			
		||||
		c.URL = os.Getenv(httpKey)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const httpKey = "VULS_HTTP_URL"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (c *HTTPConf) Init(toml HTTPConf) {
 | 
			
		||||
	if os.Getenv(httpKey) != "" {
 | 
			
		||||
		c.URL = os.Getenv(httpKey)
 | 
			
		||||
	}
 | 
			
		||||
	if toml.URL != "" {
 | 
			
		||||
		c.URL = toml.URL
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// IPS is
 | 
			
		||||
type IPS string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// DeepSecurity is
 | 
			
		||||
	DeepSecurity IPS = "deepsecurity"
 | 
			
		||||
)
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MetasploitConf is metasploit config
 | 
			
		||||
type MetasploitConf struct {
 | 
			
		||||
	// DB type for metasploit dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://metasploit-dictionary.com:1324 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/metasploit.sqlite3
 | 
			
		||||
	SQLite3Path string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (cnf *MetasploitConf) setDefault() {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, "go-msfdb.sqlite3")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const metasploitDBType = "METASPLOITDB_TYPE"
 | 
			
		||||
const metasploitDBURL = "METASPLOITDB_URL"
 | 
			
		||||
const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *MetasploitConf) Init() {
 | 
			
		||||
	if os.Getenv(metasploitDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(metasploitDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(metasploitDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(metasploitDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(metasploitDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(metasploitDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns wether fetch via http
 | 
			
		||||
func (cnf *MetasploitConf) IsFetchViaHTTP() bool {
 | 
			
		||||
	return Conf.Metasploit.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (cnf *MetasploitConf) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	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 xerrors.Errorf("Failed to connect to metasploit server. url: %s, errs: %s", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										83
									
								
								config/os.go
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								config/os.go
									
									
									
									
									
								
							@@ -4,59 +4,8 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
	// Windows is
 | 
			
		||||
	Windows = "windows"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSE is
 | 
			
		||||
	OpenSUSE = "opensuse"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSELeap is
 | 
			
		||||
	OpenSUSELeap = "opensuse.leap"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseServer is
 | 
			
		||||
	SUSEEnterpriseServer = "suse.linux.enterprise.server"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseDesktop is
 | 
			
		||||
	SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
 | 
			
		||||
 | 
			
		||||
	// SUSEOpenstackCloud is
 | 
			
		||||
	SUSEOpenstackCloud = "suse.openstack.cloud"
 | 
			
		||||
 | 
			
		||||
	// Alpine is
 | 
			
		||||
	Alpine = "alpine"
 | 
			
		||||
 | 
			
		||||
	// ServerTypePseudo is used for ServerInfo.Type, r.Family
 | 
			
		||||
	ServerTypePseudo = "pseudo"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EOL has End-of-Life information
 | 
			
		||||
@@ -89,7 +38,7 @@ func (e EOL) IsExtendedSuppportEnded(now time.Time) bool {
 | 
			
		||||
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/redhat/redhat.go#L20
 | 
			
		||||
func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
	switch family {
 | 
			
		||||
	case Amazon:
 | 
			
		||||
	case constant.Amazon:
 | 
			
		||||
		rel := "2"
 | 
			
		||||
		if isAmazonLinux1(release) {
 | 
			
		||||
			rel = "1"
 | 
			
		||||
@@ -98,7 +47,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"2": {},
 | 
			
		||||
		}[rel]
 | 
			
		||||
	case RedHat:
 | 
			
		||||
	case constant.RedHat:
 | 
			
		||||
		// https://access.redhat.com/support/policy/updates/errata
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"3": {Ended: true},
 | 
			
		||||
@@ -115,7 +64,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
				StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case CentOS:
 | 
			
		||||
	case constant.CentOS:
 | 
			
		||||
		// https://en.wikipedia.org/wiki/CentOS#End-of-support_schedule
 | 
			
		||||
		// TODO Stream
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
@@ -126,7 +75,11 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case Oracle:
 | 
			
		||||
	case constant.Rocky:
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"8": {StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case constant.Oracle:
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			// Source:
 | 
			
		||||
			// https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf
 | 
			
		||||
@@ -145,7 +98,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
				StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case Debian:
 | 
			
		||||
	case constant.Debian:
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			// https://wiki.debian.org/LTS
 | 
			
		||||
			"6":  {Ended: true},
 | 
			
		||||
@@ -154,10 +107,10 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"9":  {StandardSupportUntil: time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"10": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case Raspbian:
 | 
			
		||||
	case constant.Raspbian:
 | 
			
		||||
		// Not found
 | 
			
		||||
		eol, found = map[string]EOL{}[major(release)]
 | 
			
		||||
	case Ubuntu:
 | 
			
		||||
	case constant.Ubuntu:
 | 
			
		||||
		// https://wiki.ubuntu.com/Releases
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"14.10": {Ended: true},
 | 
			
		||||
@@ -182,16 +135,19 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"20.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2025, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"20.10": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2021, 7, 22, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"21.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2022, 1, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
				StandardSupportUntil: time.Date(2022, 1, 22, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"21.10": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[release]
 | 
			
		||||
	case SUSEEnterpriseServer:
 | 
			
		||||
	case constant.SUSEEnterpriseServer:
 | 
			
		||||
		//TODO
 | 
			
		||||
	case Alpine:
 | 
			
		||||
	case constant.Alpine:
 | 
			
		||||
		// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19
 | 
			
		||||
		// https://wiki.alpinelinux.org/wiki/Alpine_Linux:Releases
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
@@ -218,7 +174,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"3.12": {StandardSupportUntil: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"3.13": {StandardSupportUntil: time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[majorDotMinor(release)]
 | 
			
		||||
	case FreeBSD:
 | 
			
		||||
	case constant.FreeBSD:
 | 
			
		||||
		// https://www.freebsd.org/security/
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"7":  {Ended: true},
 | 
			
		||||
@@ -227,6 +183,7 @@ func GetEOL(family, release string) (eol EOL, found bool) {
 | 
			
		||||
			"10": {Ended: true},
 | 
			
		||||
			"11": {StandardSupportUntil: time.Date(2021, 9, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"12": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"13": {StandardSupportUntil: time.Date(2026, 1, 31, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package config
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	. "github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
@@ -109,6 +111,31 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		// Rocky
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Rocky Linux 8 supported",
 | 
			
		||||
			fields:   fields{family: Rocky, release: "8"},
 | 
			
		||||
			now:      time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Rocky Linux 8 EOL",
 | 
			
		||||
			fields:   fields{family: Rocky, release: "8"},
 | 
			
		||||
			now:      time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Rocky Linux 9 Not Found",
 | 
			
		||||
			fields:   fields{family: Rocky, release: "9"},
 | 
			
		||||
			now:      time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		//Oracle
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Oracle Linux 7 supported",
 | 
			
		||||
@@ -191,6 +218,14 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 20.10 supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "20.10"},
 | 
			
		||||
			now:      time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			found:    true,
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 21.04 supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "21.04"},
 | 
			
		||||
@@ -298,6 +333,14 @@ func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 13 supported",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "13"},
 | 
			
		||||
			now:      time.Date(2021, 7, 2, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 10 eol",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "10"},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										222
									
								
								config/portscan.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								config/portscan.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/exec"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PortScanConf is the setting for using an external port scanner
 | 
			
		||||
type PortScanConf struct {
 | 
			
		||||
	IsUseExternalScanner bool `toml:"-" json:"-"`
 | 
			
		||||
 | 
			
		||||
	// Path to external scanner
 | 
			
		||||
	ScannerBinPath string `toml:"scannerBinPath,omitempty" json:"scannerBinPath,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// set user has privileged
 | 
			
		||||
	HasPrivileged bool `toml:"hasPrivileged,omitempty" json:"hasPrivileged,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// set the ScanTechniques for ScannerBinPath
 | 
			
		||||
	ScanTechniques []string `toml:"scanTechniques,omitempty" json:"scanTechniques,omitempty"`
 | 
			
		||||
 | 
			
		||||
	// set the FIREWALL/IDS EVASION AND SPOOFING(Use given port number)
 | 
			
		||||
	SourcePort string `toml:"sourcePort,omitempty" json:"sourcePort,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ScanTechnique is implemented to represent the supported ScanTechniques in an Enum.
 | 
			
		||||
type ScanTechnique int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// NotSupportTechnique is a ScanTechnique that is currently not supported.
 | 
			
		||||
	NotSupportTechnique ScanTechnique = iota
 | 
			
		||||
	// TCPSYN is SYN scan
 | 
			
		||||
	TCPSYN
 | 
			
		||||
	// TCPConnect is TCP connect scan
 | 
			
		||||
	TCPConnect
 | 
			
		||||
	// TCPACK is ACK scan
 | 
			
		||||
	TCPACK
 | 
			
		||||
	// TCPWindow is Window scan
 | 
			
		||||
	TCPWindow
 | 
			
		||||
	// TCPMaimon is Maimon scan
 | 
			
		||||
	TCPMaimon
 | 
			
		||||
	// TCPNull is Null scan
 | 
			
		||||
	TCPNull
 | 
			
		||||
	// TCPFIN is FIN scan
 | 
			
		||||
	TCPFIN
 | 
			
		||||
	// TCPXmas is Xmas scan
 | 
			
		||||
	TCPXmas
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var scanTechniqueMap = map[ScanTechnique]string{
 | 
			
		||||
	TCPSYN:     "sS",
 | 
			
		||||
	TCPConnect: "sT",
 | 
			
		||||
	TCPACK:     "sA",
 | 
			
		||||
	TCPWindow:  "sW",
 | 
			
		||||
	TCPMaimon:  "sM",
 | 
			
		||||
	TCPNull:    "sN",
 | 
			
		||||
	TCPFIN:     "sF",
 | 
			
		||||
	TCPXmas:    "sX",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s ScanTechnique) String() string {
 | 
			
		||||
	switch s {
 | 
			
		||||
	case TCPSYN:
 | 
			
		||||
		return "TCPSYN"
 | 
			
		||||
	case TCPConnect:
 | 
			
		||||
		return "TCPConnect"
 | 
			
		||||
	case TCPACK:
 | 
			
		||||
		return "TCPACK"
 | 
			
		||||
	case TCPWindow:
 | 
			
		||||
		return "TCPWindow"
 | 
			
		||||
	case TCPMaimon:
 | 
			
		||||
		return "TCPMaimon"
 | 
			
		||||
	case TCPNull:
 | 
			
		||||
		return "TCPNull"
 | 
			
		||||
	case TCPFIN:
 | 
			
		||||
		return "TCPFIN"
 | 
			
		||||
	case TCPXmas:
 | 
			
		||||
		return "TCPXmas"
 | 
			
		||||
	default:
 | 
			
		||||
		return "NotSupportTechnique"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetScanTechniques converts ScanTechniques loaded from config.toml to []scanTechniques.
 | 
			
		||||
func (c *PortScanConf) GetScanTechniques() []ScanTechnique {
 | 
			
		||||
	if len(c.ScanTechniques) == 0 {
 | 
			
		||||
		return []ScanTechnique{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanTechniques := []ScanTechnique{}
 | 
			
		||||
	for _, technique := range c.ScanTechniques {
 | 
			
		||||
		findScanTechniqueFlag := false
 | 
			
		||||
		for key, value := range scanTechniqueMap {
 | 
			
		||||
			if strings.EqualFold(value, technique) {
 | 
			
		||||
				scanTechniques = append(scanTechniques, key)
 | 
			
		||||
				findScanTechniqueFlag = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !findScanTechniqueFlag {
 | 
			
		||||
			scanTechniques = append(scanTechniques, NotSupportTechnique)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(scanTechniques) == 0 {
 | 
			
		||||
		return []ScanTechnique{NotSupportTechnique}
 | 
			
		||||
	}
 | 
			
		||||
	return scanTechniques
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *PortScanConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.IsUseExternalScanner {
 | 
			
		||||
		if c.IsZero() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		errs = append(errs, xerrors.New("To enable the PortScan option, ScannerBinPath must be set."))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := os.Stat(c.ScannerBinPath); err != nil {
 | 
			
		||||
		errs = append(errs, xerrors.Errorf(
 | 
			
		||||
			"scanner is not found. ScannerBinPath: %s not exists", c.ScannerBinPath))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	scanTechniques := c.GetScanTechniques()
 | 
			
		||||
	for _, scanTechnique := range scanTechniques {
 | 
			
		||||
		if scanTechnique == NotSupportTechnique {
 | 
			
		||||
			errs = append(errs, xerrors.New("There is an unsupported option in ScanTechniques."))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// It does not currently support multiple ScanTechniques.
 | 
			
		||||
	// But if it supports UDP scanning, it will need to accept multiple ScanTechniques.
 | 
			
		||||
	if len(scanTechniques) > 1 {
 | 
			
		||||
		errs = append(errs, xerrors.New("Currently multiple ScanTechniques are not supported."))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.HasPrivileged {
 | 
			
		||||
		if os.Geteuid() != 0 {
 | 
			
		||||
			output, err := exec.Command("getcap", c.ScannerBinPath).Output()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				errs = append(errs, xerrors.Errorf("Failed to check capability of %s. error message: %w", c.ScannerBinPath, err))
 | 
			
		||||
			} else {
 | 
			
		||||
				parseOutput := strings.SplitN(string(output), "=", 2)
 | 
			
		||||
				if len(parseOutput) != 2 {
 | 
			
		||||
					errs = append(errs, xerrors.Errorf("Failed to parse getcap outputs. please execute this command: `$ getcap %s`. If the following string (`/usr/bin/nmap = ... `) is not displayed, you need to set the capability with the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", c.ScannerBinPath, c.ScannerBinPath))
 | 
			
		||||
				} else {
 | 
			
		||||
					parseCapability := strings.Split(strings.TrimSpace(parseOutput[1]), "+")
 | 
			
		||||
					capabilities := strings.Split(parseCapability[0], ",")
 | 
			
		||||
					for _, needCap := range []string{"cap_net_bind_service", "cap_net_admin", "cap_net_raw"} {
 | 
			
		||||
						existCapFlag := false
 | 
			
		||||
						for _, cap := range capabilities {
 | 
			
		||||
							if needCap == cap {
 | 
			
		||||
								existCapFlag = true
 | 
			
		||||
								break
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						if existCapFlag {
 | 
			
		||||
							continue
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						errs = append(errs, xerrors.Errorf("Not enough capability to execute. needs: ['cap_net_bind_service', 'cap_net_admin', 'cap_net_raw'], actual: %s. To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", capabilities, c.ScannerBinPath))
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if parseCapability[1] != "eip" {
 | 
			
		||||
						errs = append(errs, xerrors.Errorf("Capability(`cap_net_bind_service,cap_net_admin,cap_net_raw`) must belong to the following capability set(need: eip, actual: %s). To fix this, run the following command. `$ setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip %s`", parseCapability[1], c.ScannerBinPath))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !c.HasPrivileged {
 | 
			
		||||
		for _, scanTechnique := range scanTechniques {
 | 
			
		||||
			if scanTechnique != TCPConnect && scanTechnique != NotSupportTechnique {
 | 
			
		||||
				errs = append(errs, xerrors.New("If not privileged, only TCPConnect Scan(-sT) can be used."))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.SourcePort != "" {
 | 
			
		||||
		for _, scanTechnique := range scanTechniques {
 | 
			
		||||
			if scanTechnique == TCPConnect {
 | 
			
		||||
				errs = append(errs, xerrors.New("SourcePort Option(-g/--source-port) is incompatible with the default TCPConnect Scan(-sT)."))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		portNumber, err := strconv.Atoi(c.SourcePort)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("SourcePort conversion failed. %w", err))
 | 
			
		||||
		} else {
 | 
			
		||||
			if portNumber < 0 || 65535 < portNumber {
 | 
			
		||||
				errs = append(errs, xerrors.Errorf("SourcePort(%s) must be between 0 and 65535.", c.SourcePort))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if portNumber == 0 {
 | 
			
		||||
				errs = append(errs, xerrors.New("SourcePort(0) may not work on all systems."))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsZero return  whether this struct is not specified in config.toml
 | 
			
		||||
func (c PortScanConf) IsZero() bool {
 | 
			
		||||
	return c.ScannerBinPath == "" && !c.HasPrivileged && len(c.ScanTechniques) == 0 && c.SourcePort == ""
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										69
									
								
								config/portscan_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								config/portscan_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPortScanConf_getScanTechniques(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		techniques []string
 | 
			
		||||
		want       []ScanTechnique
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:       "nil",
 | 
			
		||||
			techniques: []string{},
 | 
			
		||||
			want:       []ScanTechnique{},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "single",
 | 
			
		||||
			techniques: []string{"sS"},
 | 
			
		||||
			want:       []ScanTechnique{TCPSYN},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "multiple",
 | 
			
		||||
			techniques: []string{"sS", "sT"},
 | 
			
		||||
			want:       []ScanTechnique{TCPSYN, TCPConnect},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "unknown",
 | 
			
		||||
			techniques: []string{"sU"},
 | 
			
		||||
			want:       []ScanTechnique{NotSupportTechnique},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			c := PortScanConf{ScanTechniques: tt.techniques}
 | 
			
		||||
			if got := c.GetScanTechniques(); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("PortScanConf.getScanTechniques() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPortScanConf_IsZero(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		conf PortScanConf
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "not zero",
 | 
			
		||||
			conf: PortScanConf{ScannerBinPath: "/usr/bin/nmap"},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "zero",
 | 
			
		||||
			conf: PortScanConf{},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.conf.IsZero(); got != tt.want {
 | 
			
		||||
				t.Errorf("PortScanConf.IsZero() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -16,11 +16,12 @@ type SlackConf struct {
 | 
			
		||||
	AuthUser    string   `json:"-" toml:"authUser,omitempty"`
 | 
			
		||||
	NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"`
 | 
			
		||||
	Text        string   `json:"-"`
 | 
			
		||||
	Enabled     bool     `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SlackConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToSlack {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ type SMTPConf struct {
 | 
			
		||||
	To            []string `toml:"to,omitempty" json:"-"`
 | 
			
		||||
	Cc            []string `toml:"cc,omitempty" json:"-"`
 | 
			
		||||
	SubjectPrefix string   `toml:"subjectPrefix,omitempty" json:"-"`
 | 
			
		||||
	Enabled       bool     `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
@@ -31,10 +32,9 @@ func checkEmails(emails []string) (errs []error) {
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToEmail {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// Check Emails fromat
 | 
			
		||||
	emails := []string{}
 | 
			
		||||
	emails = append(emails, c.From)
 | 
			
		||||
	emails = append(emails, c.To...)
 | 
			
		||||
@@ -44,10 +44,10 @@ func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
		errs = append(errs, emailErrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.SMTPAddr) == 0 {
 | 
			
		||||
	if c.SMTPAddr == "" {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.SMTPPort) == 0 {
 | 
			
		||||
	if c.SMTPPort == "" {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.To) == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -17,11 +17,12 @@ type SyslogConf struct {
 | 
			
		||||
	Facility string `json:"-"`
 | 
			
		||||
	Tag      string `json:"-"`
 | 
			
		||||
	Verbose  bool   `json:"-"`
 | 
			
		||||
	Enabled  bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SyslogConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToSyslog {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	//  If protocol is empty, it will connect to the local syslog server.
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,14 @@ import (
 | 
			
		||||
 | 
			
		||||
// TelegramConf is Telegram config
 | 
			
		||||
type TelegramConf struct {
 | 
			
		||||
	Token  string `json:"-"`
 | 
			
		||||
	ChatID string `json:"-"`
 | 
			
		||||
	Token   string `json:"-"`
 | 
			
		||||
	ChatID  string `json:"-"`
 | 
			
		||||
	Enabled bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *TelegramConf) Validate() (errs []error) {
 | 
			
		||||
	if !Conf.ToTelegram {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.ChatID) == 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/BurntSushi/toml"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/knqyf263/go-cpe/naming"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
@@ -15,26 +16,24 @@ type TOMLLoader struct {
 | 
			
		||||
 | 
			
		||||
// Load load the configuration TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	// util.Log.Infof("Loading config: %s", pathToToml)
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if keyPass != "" {
 | 
			
		||||
		Conf.Default.KeyPassword = keyPass
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Conf.CveDict.Init()
 | 
			
		||||
	Conf.OvalDict.Init()
 | 
			
		||||
	Conf.Gost.Init()
 | 
			
		||||
	Conf.Exploit.Init()
 | 
			
		||||
	Conf.Metasploit.Init()
 | 
			
		||||
	for _, cnf := range []VulnDictInterface{
 | 
			
		||||
		&Conf.CveDict,
 | 
			
		||||
		&Conf.OvalDict,
 | 
			
		||||
		&Conf.Gost,
 | 
			
		||||
		&Conf.Exploit,
 | 
			
		||||
		&Conf.Metasploit,
 | 
			
		||||
	} {
 | 
			
		||||
		cnf.Init()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	index := 0
 | 
			
		||||
	for name, server := range Conf.Servers {
 | 
			
		||||
		server.ServerName = name
 | 
			
		||||
		if 0 < len(server.KeyPassword) {
 | 
			
		||||
			return xerrors.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", name)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := setDefaultIfEmpty(&server, Conf.Default); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -126,6 +125,10 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if server.PortScan.ScannerBinPath != "" {
 | 
			
		||||
			server.PortScan.IsUseExternalScanner = true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		server.LogMsgAnsiColor = Colors[index%len(Colors)]
 | 
			
		||||
		index++
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +138,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
	if server.Type != ServerTypePseudo {
 | 
			
		||||
	if server.Type != constant.ServerTypePseudo {
 | 
			
		||||
		if len(server.Host) == 0 {
 | 
			
		||||
			return xerrors.Errorf("server.host is empty")
 | 
			
		||||
		}
 | 
			
		||||
@@ -166,10 +169,6 @@ func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
		if server.KeyPath == "" {
 | 
			
		||||
			server.KeyPath = Conf.Default.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if server.KeyPassword == "" {
 | 
			
		||||
			server.KeyPassword = Conf.Default.KeyPassword
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.Lockfiles) == 0 {
 | 
			
		||||
@@ -208,6 +207,13 @@ func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.PortScan == nil {
 | 
			
		||||
		server.PortScan = Conf.Default.PortScan
 | 
			
		||||
		if server.PortScan == nil {
 | 
			
		||||
			server.PortScan = &PortScanConf{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.IgnoredJSONKeys) == 0 {
 | 
			
		||||
		server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										276
									
								
								config/vulnDictConf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								config/vulnDictConf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,276 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// VulnDictInterface is an interface of vulnsrc
 | 
			
		||||
type VulnDictInterface interface {
 | 
			
		||||
	Init()
 | 
			
		||||
	Validate() error
 | 
			
		||||
	IsFetchViaHTTP() bool
 | 
			
		||||
	CheckHTTPHealth() error
 | 
			
		||||
	GetName() string
 | 
			
		||||
	GetType() string
 | 
			
		||||
	GetURL() string
 | 
			
		||||
	GetSQLite3Path() string
 | 
			
		||||
	GetDebugSQL() bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VulnDict is a base struct of vuln dicts
 | 
			
		||||
type VulnDict struct {
 | 
			
		||||
	Name string
 | 
			
		||||
 | 
			
		||||
	// DB type of CVE dictionary (sqlite3, mysql, postgres or redis)
 | 
			
		||||
	Type string
 | 
			
		||||
 | 
			
		||||
	// http://cve-dictionary.com:1323 or DB connection string
 | 
			
		||||
	URL string `json:"-"`
 | 
			
		||||
 | 
			
		||||
	// /path/to/cve.sqlite3
 | 
			
		||||
	SQLite3Path string
 | 
			
		||||
 | 
			
		||||
	DebugSQL bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetType returns type
 | 
			
		||||
func (cnf VulnDict) GetType() string {
 | 
			
		||||
	return cnf.Type
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetName returns name
 | 
			
		||||
func (cnf VulnDict) GetName() string {
 | 
			
		||||
	return cnf.Name
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetURL returns url
 | 
			
		||||
func (cnf VulnDict) GetURL() string {
 | 
			
		||||
	return cnf.URL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSQLite3Path return the path of SQLite3
 | 
			
		||||
func (cnf VulnDict) GetSQLite3Path() string {
 | 
			
		||||
	return cnf.SQLite3Path
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDebugSQL return debugSQL flag
 | 
			
		||||
func (cnf VulnDict) GetDebugSQL() bool {
 | 
			
		||||
	return cnf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate settings
 | 
			
		||||
func (cnf VulnDict) Validate() error {
 | 
			
		||||
	logging.Log.Infof("%s.type=%s, %s.url=%s, %s.SQLite3Path=%s",
 | 
			
		||||
		cnf.Name, cnf.Type, cnf.Name, cnf.URL, cnf.Name, cnf.SQLite3Path)
 | 
			
		||||
 | 
			
		||||
	switch cnf.Type {
 | 
			
		||||
	case "sqlite3":
 | 
			
		||||
		if cnf.URL != "" {
 | 
			
		||||
			return xerrors.Errorf("To use SQLite3, specify %s.type=sqlite3 and %s.SQLite3Path. To use as HTTP server mode, specify %s.type=http and %s.url",
 | 
			
		||||
				cnf.Name, cnf.Name, cnf.Name, cnf.Name)
 | 
			
		||||
		}
 | 
			
		||||
		if ok, _ := govalidator.IsFilePath(cnf.SQLite3Path); !ok {
 | 
			
		||||
			return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. %s.SQLite3Path: %s",
 | 
			
		||||
				cnf.Name, cnf.SQLite3Path)
 | 
			
		||||
		}
 | 
			
		||||
		if _, err := os.Stat(cnf.SQLite3Path); os.IsNotExist(err) {
 | 
			
		||||
			logging.Log.Warnf("%s.SQLite3Path=%s file not found", cnf.Name, cnf.SQLite3Path)
 | 
			
		||||
		}
 | 
			
		||||
	case "mysql":
 | 
			
		||||
		if cnf.URL == "" {
 | 
			
		||||
			return xerrors.Errorf(`MySQL connection string is needed. %s.url="user:pass@tcp(localhost:3306)/dbname"`, cnf.Name)
 | 
			
		||||
		}
 | 
			
		||||
	case "postgres":
 | 
			
		||||
		if cnf.URL == "" {
 | 
			
		||||
			return xerrors.Errorf(`PostgreSQL connection string is needed. %s.url="host=myhost user=user dbname=dbname sslmode=disable password=password"`, cnf.Name)
 | 
			
		||||
		}
 | 
			
		||||
	case "redis":
 | 
			
		||||
		if cnf.URL == "" {
 | 
			
		||||
			return xerrors.Errorf(`Redis connection string is needed. %s.url="redis://localhost/0"`, cnf.Name)
 | 
			
		||||
		}
 | 
			
		||||
	case "http":
 | 
			
		||||
		if cnf.URL == "" {
 | 
			
		||||
			return xerrors.Errorf(`URL is needed. -%s-url="http://localhost:1323"`, cnf.Name)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return xerrors.Errorf("%s.type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'.  %s.type: %s", cnf.Name, cnf.Name, cnf.Type)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init the struct
 | 
			
		||||
func (cnf VulnDict) Init() {}
 | 
			
		||||
 | 
			
		||||
func (cnf *VulnDict) setDefault(sqlite3Name string) {
 | 
			
		||||
	if cnf.Type == "" {
 | 
			
		||||
		cnf.Type = "sqlite3"
 | 
			
		||||
	}
 | 
			
		||||
	if cnf.URL == "" && cnf.SQLite3Path == "" {
 | 
			
		||||
		wd, _ := os.Getwd()
 | 
			
		||||
		cnf.SQLite3Path = filepath.Join(wd, sqlite3Name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFetchViaHTTP returns if fetch via HTTP
 | 
			
		||||
func (cnf VulnDict) IsFetchViaHTTP() bool {
 | 
			
		||||
	return cnf.Type == "http"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth checks http server status
 | 
			
		||||
func (cnf VulnDict) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.URL)
 | 
			
		||||
	resp, _, errs := gorequest.New().Timeout(10 * time.Second).SetDebug(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 xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %s",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GovalDictConf is goval-dictionary config
 | 
			
		||||
type GovalDictConf struct {
 | 
			
		||||
	VulnDict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const govalType = "OVALDB_TYPE"
 | 
			
		||||
const govalURL = "OVALDB_URL"
 | 
			
		||||
const govalPATH = "OVALDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GovalDictConf) Init() {
 | 
			
		||||
	cnf.Name = "ovalDict"
 | 
			
		||||
	if os.Getenv(govalType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(govalType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(govalURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(govalURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(govalPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(govalPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault("oval.sqlite3")
 | 
			
		||||
	cnf.DebugSQL = Conf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExploitConf is exploit config
 | 
			
		||||
type ExploitConf struct {
 | 
			
		||||
	VulnDict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const exploitDBType = "EXPLOITDB_TYPE"
 | 
			
		||||
const exploitDBURL = "EXPLOITDB_URL"
 | 
			
		||||
const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *ExploitConf) Init() {
 | 
			
		||||
	cnf.Name = "exploit"
 | 
			
		||||
	if os.Getenv(exploitDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(exploitDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(exploitDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(exploitDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(exploitDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(exploitDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault("go-exploitdb.sqlite3")
 | 
			
		||||
	cnf.DebugSQL = Conf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GoCveDictConf is GoCveDict config
 | 
			
		||||
type GoCveDictConf struct {
 | 
			
		||||
	VulnDict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cveDBType = "CVEDB_TYPE"
 | 
			
		||||
const cveDBURL = "CVEDB_URL"
 | 
			
		||||
const cveDBPATH = "CVEDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GoCveDictConf) Init() {
 | 
			
		||||
	cnf.Name = "cveDict"
 | 
			
		||||
	if os.Getenv(cveDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(cveDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(cveDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(cveDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(cveDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(cveDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault("cve.sqlite3")
 | 
			
		||||
	cnf.DebugSQL = Conf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GostConf is gost config
 | 
			
		||||
type GostConf struct {
 | 
			
		||||
	VulnDict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const gostDBType = "GOSTDB_TYPE"
 | 
			
		||||
const gostDBURL = "GOSTDB_URL"
 | 
			
		||||
const gostDBPATH = "GOSTDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *GostConf) Init() {
 | 
			
		||||
	cnf.Name = "gost"
 | 
			
		||||
	if os.Getenv(gostDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(gostDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(gostDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(gostDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(gostDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(gostDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault("gost.sqlite3")
 | 
			
		||||
	cnf.DebugSQL = Conf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MetasploitConf is gost go-metasploitdb
 | 
			
		||||
type MetasploitConf struct {
 | 
			
		||||
	VulnDict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const metasploitDBType = "METASPLOITDB_TYPE"
 | 
			
		||||
const metasploitDBURL = "METASPLOITDB_URL"
 | 
			
		||||
const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH"
 | 
			
		||||
 | 
			
		||||
// Init set options with the following priority.
 | 
			
		||||
// 1. Environment variable
 | 
			
		||||
// 2. config.toml
 | 
			
		||||
func (cnf *MetasploitConf) Init() {
 | 
			
		||||
	cnf.Name = "metasploit"
 | 
			
		||||
	if os.Getenv(metasploitDBType) != "" {
 | 
			
		||||
		cnf.Type = os.Getenv(metasploitDBType)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(metasploitDBURL) != "" {
 | 
			
		||||
		cnf.URL = os.Getenv(metasploitDBURL)
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv(metasploitDBPATH) != "" {
 | 
			
		||||
		cnf.SQLite3Path = os.Getenv(metasploitDBPATH)
 | 
			
		||||
	}
 | 
			
		||||
	cnf.setDefault("go-msfdb.sqlite3")
 | 
			
		||||
	cnf.DebugSQL = Conf.DebugSQL
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								constant/constant.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								constant/constant.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
package constant
 | 
			
		||||
 | 
			
		||||
// Global constant
 | 
			
		||||
// Pkg local constants should not be defined here.
 | 
			
		||||
// Define them in the each package.
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// RedHat is
 | 
			
		||||
	RedHat = "redhat"
 | 
			
		||||
 | 
			
		||||
	// Debian is
 | 
			
		||||
	Debian = "debian"
 | 
			
		||||
 | 
			
		||||
	// Ubuntu is
 | 
			
		||||
	Ubuntu = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// CentOS is
 | 
			
		||||
	CentOS = "centos"
 | 
			
		||||
 | 
			
		||||
	// Rocky is
 | 
			
		||||
	Rocky = "rocky"
 | 
			
		||||
 | 
			
		||||
	// Fedora is
 | 
			
		||||
	// Fedora = "fedora"
 | 
			
		||||
 | 
			
		||||
	// Amazon is
 | 
			
		||||
	Amazon = "amazon"
 | 
			
		||||
 | 
			
		||||
	// Oracle is
 | 
			
		||||
	Oracle = "oracle"
 | 
			
		||||
 | 
			
		||||
	// FreeBSD is
 | 
			
		||||
	FreeBSD = "freebsd"
 | 
			
		||||
 | 
			
		||||
	// Raspbian is
 | 
			
		||||
	Raspbian = "raspbian"
 | 
			
		||||
 | 
			
		||||
	// Windows is
 | 
			
		||||
	Windows = "windows"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSE is
 | 
			
		||||
	OpenSUSE = "opensuse"
 | 
			
		||||
 | 
			
		||||
	// OpenSUSELeap is
 | 
			
		||||
	OpenSUSELeap = "opensuse.leap"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseServer is
 | 
			
		||||
	SUSEEnterpriseServer = "suse.linux.enterprise.server"
 | 
			
		||||
 | 
			
		||||
	// SUSEEnterpriseDesktop is
 | 
			
		||||
	SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
 | 
			
		||||
 | 
			
		||||
	// SUSEOpenstackCloud is
 | 
			
		||||
	SUSEOpenstackCloud = "suse.openstack.cloud"
 | 
			
		||||
 | 
			
		||||
	// Alpine is
 | 
			
		||||
	Alpine = "alpine"
 | 
			
		||||
 | 
			
		||||
	// ServerTypePseudo is used for ServerInfo.Type, r.Family
 | 
			
		||||
	ServerTypePseudo = "pseudo"
 | 
			
		||||
 | 
			
		||||
	// DeepSecurity is
 | 
			
		||||
	DeepSecurity = "deepsecurity"
 | 
			
		||||
)
 | 
			
		||||
@@ -22,6 +22,9 @@ func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanR
 | 
			
		||||
	vulnInfos := models.VulnInfos{}
 | 
			
		||||
	uniqueLibraryScannerPaths := map[string]models.LibraryScanner{}
 | 
			
		||||
	for _, trivyResult := range trivyResults {
 | 
			
		||||
		if IsTrivySupportedOS(trivyResult.Type) {
 | 
			
		||||
			overrideServerData(scanResult, &trivyResult)
 | 
			
		||||
		}
 | 
			
		||||
		for _, vuln := range trivyResult.Vulnerabilities {
 | 
			
		||||
			if _, ok := vulnInfos[vuln.VulnerabilityID]; !ok {
 | 
			
		||||
				vulnInfos[vuln.VulnerabilityID] = models.VulnInfo{
 | 
			
		||||
@@ -57,12 +60,24 @@ func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanR
 | 
			
		||||
				return references[i].Link < references[j].Link
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			var published time.Time
 | 
			
		||||
			if vuln.PublishedDate != nil {
 | 
			
		||||
				published = *vuln.PublishedDate
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var lastModified time.Time
 | 
			
		||||
			if vuln.LastModifiedDate != nil {
 | 
			
		||||
				lastModified = *vuln.LastModifiedDate
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			vulnInfo.CveContents = models.CveContents{
 | 
			
		||||
				models.Trivy: models.CveContent{
 | 
			
		||||
					Cvss3Severity: vuln.Severity,
 | 
			
		||||
					References:    references,
 | 
			
		||||
					Title:         vuln.Title,
 | 
			
		||||
					Summary:       vuln.Description,
 | 
			
		||||
					Published:     published,
 | 
			
		||||
					LastModified:  lastModified,
 | 
			
		||||
				},
 | 
			
		||||
			}
 | 
			
		||||
			// do only if image type is Vuln
 | 
			
		||||
@@ -77,16 +92,6 @@ func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanR
 | 
			
		||||
					FixState:    fixState,
 | 
			
		||||
					FixedIn:     vuln.FixedVersion,
 | 
			
		||||
				})
 | 
			
		||||
 | 
			
		||||
				// overwrite every time if os package
 | 
			
		||||
				scanResult.Family = trivyResult.Type
 | 
			
		||||
				scanResult.ServerName = trivyResult.Target
 | 
			
		||||
				scanResult.Optional = map[string]interface{}{
 | 
			
		||||
					"trivy-target": trivyResult.Target,
 | 
			
		||||
				}
 | 
			
		||||
				scanResult.ScannedAt = time.Now()
 | 
			
		||||
				scanResult.ScannedBy = "trivy"
 | 
			
		||||
				scanResult.ScannedVia = "trivy"
 | 
			
		||||
			} else {
 | 
			
		||||
				// LibraryScanの結果
 | 
			
		||||
				vulnInfo.LibraryFixedIns = append(vulnInfo.LibraryFixedIns, models.LibraryFixedIn{
 | 
			
		||||
@@ -162,3 +167,14 @@ func IsTrivySupportedOS(family string) bool {
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func overrideServerData(scanResult *models.ScanResult, trivyResult *report.Result) {
 | 
			
		||||
	scanResult.Family = trivyResult.Type
 | 
			
		||||
	scanResult.ServerName = trivyResult.Target
 | 
			
		||||
	scanResult.Optional = map[string]interface{}{
 | 
			
		||||
		"trivy-target": trivyResult.Target,
 | 
			
		||||
	}
 | 
			
		||||
	scanResult.ScannedAt = time.Now()
 | 
			
		||||
	scanResult.ScannedBy = "trivy"
 | 
			
		||||
	scanResult.ScannedVia = "trivy"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/types"
 | 
			
		||||
	"github.com/d4l3k/messagediff"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -3205,6 +3206,33 @@ func TestParse(t *testing.T) {
 | 
			
		||||
				Optional: map[string]interface{}{"trivy-target": "knqyf263/vuln-image:1.2.3 (alpine 3.7.1)"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		"found-no-vulns": {
 | 
			
		||||
			vulnJSON: []byte(`[
 | 
			
		||||
  {
 | 
			
		||||
    "Target": "no-vuln-image:v1 (debian 9.13)",
 | 
			
		||||
    "Type": "debian",
 | 
			
		||||
    "Vulnerabilities": null
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
`),
 | 
			
		||||
			scanResult: &models.ScanResult{
 | 
			
		||||
				JSONVersion: 1,
 | 
			
		||||
				ServerUUID:  "uuid",
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			},
 | 
			
		||||
			expected: &models.ScanResult{
 | 
			
		||||
				JSONVersion:     1,
 | 
			
		||||
				ServerUUID:      "uuid",
 | 
			
		||||
				ServerName:      "no-vuln-image:v1 (debian 9.13)",
 | 
			
		||||
				Family:          "debian",
 | 
			
		||||
				ScannedBy:       "trivy",
 | 
			
		||||
				ScannedVia:      "trivy",
 | 
			
		||||
				ScannedCves:     models.VulnInfos{},
 | 
			
		||||
				Packages:        models.Packages{},
 | 
			
		||||
				LibraryScanners: models.LibraryScanners{},
 | 
			
		||||
				Optional:        map[string]interface{}{"trivy-target": "no-vuln-image:v1 (debian 9.13)"},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for testcase, v := range cases {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package report
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
@@ -13,17 +13,53 @@ import (
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cvedb "github.com/kotakanbe/go-cve-dictionary/db"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
	cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveClient is api client of CVE dictionary service.
 | 
			
		||||
var CveClient cvedictClient
 | 
			
		||||
type goCveDictClient struct {
 | 
			
		||||
	cnf    config.VulnDictInterface
 | 
			
		||||
	driver cvedb.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type cvedictClient struct {
 | 
			
		||||
	//  httpProxy string
 | 
			
		||||
	baseURL string
 | 
			
		||||
func newGoCveDictClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goCveDictClient, error) {
 | 
			
		||||
	cvelog.SetLogger(o.Debug, o.Quiet, false, o.LogToFile, o.LogDir)
 | 
			
		||||
 | 
			
		||||
	driver, locked, err := newCveDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &goCveDictClient{cnf: cnf, driver: driver}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api goCveDictClient) closeDB() error {
 | 
			
		||||
	if api.driver == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	if err := api.driver.CloseDB(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to close DB: %+v", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api goCveDictClient) fetchCveDetails(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
 | 
			
		||||
	for _, cveID := range cveIDs {
 | 
			
		||||
		cveDetail, err := api.driver.Get(cveID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fetch CVE. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		if len(cveDetail.CveID) == 0 {
 | 
			
		||||
			cveDetails = append(cveDetails, cvemodels.CveDetail{CveID: cveID})
 | 
			
		||||
		} else {
 | 
			
		||||
			cveDetails = append(cveDetails, *cveDetail)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
@@ -31,28 +67,7 @@ type response struct {
 | 
			
		||||
	CveDetail cvemodels.CveDetail
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
 | 
			
		||||
	if !config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			cveDetail, err := driver.Get(cveID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, xerrors.Errorf("Failed to fetch CVE. err: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
			if len(cveDetail.CveID) == 0 {
 | 
			
		||||
				cveDetails = append(cveDetails, cvemodels.CveDetail{
 | 
			
		||||
					CveID: cveID,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				cveDetails = append(cveDetails, *cveDetail)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
func (api goCveDictClient) fetchCveDetailsViaHTTP(cveIDs []string) (cveDetails []cvemodels.CveDetail, err error) {
 | 
			
		||||
	reqChan := make(chan string, len(cveIDs))
 | 
			
		||||
	resChan := make(chan response, len(cveIDs))
 | 
			
		||||
	errChan := make(chan error, len(cveIDs))
 | 
			
		||||
@@ -72,11 +87,11 @@ func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveD
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case cveID := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(api.baseURL, "cves", cveID)
 | 
			
		||||
				url, err := util.URLPathJoin(api.cnf.GetURL(), "cves", cveID)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					logging.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					api.httpGet(cveID, url, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -108,22 +123,20 @@ func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveD
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
func (api goCveDictClient) httpGet(key, url string, resChan chan<- response, errChan chan<- error) {
 | 
			
		||||
	var body string
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	f := func() (err error) {
 | 
			
		||||
		//  resp, body, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Get(url).End()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %s",
 | 
			
		||||
			return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %+v",
 | 
			
		||||
				url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s",
 | 
			
		||||
			t, err)
 | 
			
		||||
		logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -141,39 +154,37 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) FetchCveDetailsByCpeName(driver cvedb.DB, cpeName string) ([]cvemodels.CveDetail, error) {
 | 
			
		||||
	if config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
		url, err := util.URLPathJoin(api.baseURL, "cpes")
 | 
			
		||||
func (api goCveDictClient) fetchCveDetailsByCpeName(cpeName string) ([]cvemodels.CveDetail, error) {
 | 
			
		||||
	if api.cnf.IsFetchViaHTTP() {
 | 
			
		||||
		url, err := util.URLPathJoin(api.cnf.GetURL(), "cpes")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		query := map[string]string{"name": cpeName}
 | 
			
		||||
		util.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
		logging.Log.Debugf("HTTP Request to %s, query: %#v", url, query)
 | 
			
		||||
		return api.httpPost(cpeName, url, query)
 | 
			
		||||
	}
 | 
			
		||||
	return driver.GetByCpeURI(cpeName)
 | 
			
		||||
	return api.driver.GetByCpeURI(cpeName)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]cvemodels.CveDetail, error) {
 | 
			
		||||
func (api goCveDictClient) httpPost(key, url string, query map[string]string) ([]cvemodels.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().Post(url)
 | 
			
		||||
		req := gorequest.New().Timeout(10 * time.Second).Post(url)
 | 
			
		||||
		for key := range query {
 | 
			
		||||
			req = req.Send(fmt.Sprintf("%s=%s", key, query[key])).Type("json")
 | 
			
		||||
		}
 | 
			
		||||
		resp, body, errs = req.End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
			return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %+v", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		logging.Log.Warnf("Failed to HTTP POST. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -187,3 +198,19 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
	}
 | 
			
		||||
	return cveDetails, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newCveDB(cnf config.VulnDictInterface) (driver cvedb.DB, locked bool, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.GetURL()
 | 
			
		||||
	if cnf.GetType() == "sqlite3" {
 | 
			
		||||
		path = cnf.GetSQLite3Path()
 | 
			
		||||
	}
 | 
			
		||||
	driver, locked, err = cvedb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		err = xerrors.Errorf("Failed to init CVE DB. err: %w, path: %s", err, path)
 | 
			
		||||
		return nil, locked, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										503
									
								
								detector/detector.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										503
									
								
								detector/detector.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,503 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
 | 
			
		||||
	"github.com/future-architect/vuls/cwe"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/reporter"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Detect vulns and fill CVE detailed information
 | 
			
		||||
func Detect(rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
 | 
			
		||||
 | 
			
		||||
	// Use the same reportedAt for all rs
 | 
			
		||||
	reportedAt := time.Now()
 | 
			
		||||
	for i, r := range rs {
 | 
			
		||||
		if !config.Conf.RefreshCve && !needToRefreshCve(r) {
 | 
			
		||||
			logging.Log.Info("No need to refresh")
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !reuseScannedCves(&r) {
 | 
			
		||||
			r.ScannedCves = models.VulnInfos{}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cpeURIs, owaspDCXMLPath := []string{}, ""
 | 
			
		||||
		if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
			cpeURIs = config.Conf.Servers[r.ServerName].CpeNames
 | 
			
		||||
			owaspDCXMLPath = config.Conf.Servers[r.ServerName].OwaspDCXMLPath
 | 
			
		||||
		} else {
 | 
			
		||||
			if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
				if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
					cpeURIs = con.Cpes
 | 
			
		||||
					owaspDCXMLPath = con.OwaspDCXMLPath
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if owaspDCXMLPath != "" {
 | 
			
		||||
			cpes, err := parser.Parse(owaspDCXMLPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w",
 | 
			
		||||
					r.ServerInfo(), owaspDCXMLPath, err)
 | 
			
		||||
			}
 | 
			
		||||
			cpeURIs = append(cpeURIs, cpes...)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DetectLibsCves(&r, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DetectPkgCves(&r, config.Conf.OvalDict, config.Conf.Gost); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DetectCpeURIsCves(&r, cpeURIs, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to detect CVE of `%s`: %w", cpeURIs, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		repos := config.Conf.Servers[r.ServerName].GitHubRepos
 | 
			
		||||
		if err := DetectGitHubCves(&r, repos); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to detect GitHub Cves: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := DetectWordPressCves(&r, config.Conf.WpScan); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to detect WordPress Cves: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with gost: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := FillCvesWithNvdJvn(&r, config.Conf.CveDict, config.Conf.LogOpts); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with CVE: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		nExploitCve, err := FillWithExploit(&r, config.Conf.Exploit)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with exploit: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		logging.Log.Infof("%s: %d PoC are detected", r.FormatServerName(), nExploitCve)
 | 
			
		||||
 | 
			
		||||
		nMetasploitCve, err := FillWithMetasploit(&r, config.Conf.Metasploit)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with metasploit: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		logging.Log.Infof("%s: %d exploits are detected", r.FormatServerName(), nMetasploitCve)
 | 
			
		||||
 | 
			
		||||
		FillCweDict(&r)
 | 
			
		||||
 | 
			
		||||
		r.ReportedBy, _ = os.Hostname()
 | 
			
		||||
		r.Lang = config.Conf.Lang
 | 
			
		||||
		r.ReportedAt = reportedAt
 | 
			
		||||
		r.ReportedVersion = config.Version
 | 
			
		||||
		r.ReportedRevision = config.Revision
 | 
			
		||||
		r.Config.Report = config.Conf
 | 
			
		||||
		r.Config.Report.Servers = map[string]config.ServerInfo{
 | 
			
		||||
			r.ServerName: config.Conf.Servers[r.ServerName],
 | 
			
		||||
		}
 | 
			
		||||
		rs[i] = r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Overwrite the json file every time to clear the fields specified in config.IgnoredJSONKeys
 | 
			
		||||
	for _, r := range rs {
 | 
			
		||||
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
			r = r.ClearFields(s.IgnoredJSONKeys)
 | 
			
		||||
		}
 | 
			
		||||
		//TODO don't call here
 | 
			
		||||
		if err := reporter.OverwriteJSONFile(dir, r); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to write JSON: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.DiffPlus || config.Conf.DiffMinus {
 | 
			
		||||
		prevs, err := loadPrevious(rs, config.Conf.ResultsDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		rs = diff(rs, prevs, config.Conf.DiffPlus, config.Conf.DiffMinus)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, r := range rs {
 | 
			
		||||
		r.ScannedCves = r.ScannedCves.FilterByCvssOver(config.Conf.CvssScoreOver)
 | 
			
		||||
		r.ScannedCves = r.ScannedCves.FilterUnfixed(config.Conf.IgnoreUnfixed)
 | 
			
		||||
 | 
			
		||||
		// IgnoreCves
 | 
			
		||||
		ignoreCves := []string{}
 | 
			
		||||
		if r.Container.Name == "" {
 | 
			
		||||
			ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
 | 
			
		||||
		} else if con, ok := config.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
 | 
			
		||||
			ignoreCves = con.IgnoreCves
 | 
			
		||||
		}
 | 
			
		||||
		r.ScannedCves = r.ScannedCves.FilterIgnoreCves(ignoreCves)
 | 
			
		||||
 | 
			
		||||
		// ignorePkgs
 | 
			
		||||
		ignorePkgsRegexps := []string{}
 | 
			
		||||
		if r.Container.Name == "" {
 | 
			
		||||
			ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
 | 
			
		||||
		} else if s, ok := config.Conf.Servers[r.ServerName].Containers[r.Container.Name]; ok {
 | 
			
		||||
			ignorePkgsRegexps = s.IgnorePkgsRegexp
 | 
			
		||||
		}
 | 
			
		||||
		r.ScannedCves = r.ScannedCves.FilterIgnorePkgs(ignorePkgsRegexps)
 | 
			
		||||
 | 
			
		||||
		// IgnoreUnscored
 | 
			
		||||
		if config.Conf.IgnoreUnscoredCves {
 | 
			
		||||
			r.ScannedCves = r.ScannedCves.FindScoredVulns()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		r.FilterInactiveWordPressLibs(config.Conf.WpScan.DetectInactive)
 | 
			
		||||
		rs[i] = r
 | 
			
		||||
	}
 | 
			
		||||
	return rs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectPkgCves detects OS pkg cves
 | 
			
		||||
// pass 2 configs
 | 
			
		||||
func DetectPkgCves(r *models.ScanResult, ovalCnf config.GovalDictConf, gostCnf config.GostConf) error {
 | 
			
		||||
	// Pkg Scan
 | 
			
		||||
	if r.Release != "" {
 | 
			
		||||
		// OVAL, gost(Debian Security Tracker) does not support Package for Raspbian, so skip it.
 | 
			
		||||
		if r.Family == constant.Raspbian {
 | 
			
		||||
			r = r.RemoveRaspbianPackFromResult()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// OVAL
 | 
			
		||||
		if err := detectPkgsCvesWithOval(ovalCnf, r); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// gost
 | 
			
		||||
		if err := detectPkgsCvesWithGost(gostCnf, r); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to detect CVE with gost: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else if reuseScannedCves(r) {
 | 
			
		||||
		logging.Log.Infof("r.Release is empty. Use CVEs as it as.")
 | 
			
		||||
	} else if r.Family == constant.ServerTypePseudo {
 | 
			
		||||
		logging.Log.Infof("pseudo type. Skip OVAL and gost detection")
 | 
			
		||||
	} else {
 | 
			
		||||
		return xerrors.Errorf("Failed to fill CVEs. r.Release is empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, v := range r.ScannedCves {
 | 
			
		||||
		for j, p := range v.AffectedPackages {
 | 
			
		||||
			if p.NotFixedYet && p.FixState == "" {
 | 
			
		||||
				p.FixState = "Not fixed yet"
 | 
			
		||||
				r.ScannedCves[i].AffectedPackages[j] = p
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// To keep backward compatibility
 | 
			
		||||
	// Newer versions use ListenPortStats,
 | 
			
		||||
	// but older versions of Vuls are set to ListenPorts.
 | 
			
		||||
	// Set ListenPorts to ListenPortStats to allow newer Vuls to report old results.
 | 
			
		||||
	for i, pkg := range r.Packages {
 | 
			
		||||
		for j, proc := range pkg.AffectedProcs {
 | 
			
		||||
			for _, ipPort := range proc.ListenPorts {
 | 
			
		||||
				ps, err := models.NewPortStat(ipPort)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logging.Log.Warnf("Failed to parse ip:port: %s, err:%+v", ipPort, err)
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				r.Packages[i].AffectedProcs[j].ListenPortStats = append(
 | 
			
		||||
					r.Packages[i].AffectedProcs[j].ListenPortStats, *ps)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectGitHubCves fetches CVEs from GitHub Security Alerts
 | 
			
		||||
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]config.GitHubConf) error {
 | 
			
		||||
	if len(githubConfs) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	for ownerRepo, setting := range githubConfs {
 | 
			
		||||
		ss := strings.Split(ownerRepo, "/")
 | 
			
		||||
		if len(ss) != 2 {
 | 
			
		||||
			return xerrors.Errorf("Failed to parse GitHub owner/repo: %s", ownerRepo)
 | 
			
		||||
		}
 | 
			
		||||
		owner, repo := ss[0], ss[1]
 | 
			
		||||
		n, err := DetectGitHubSecurityAlerts(r, owner, repo, setting.Token, setting.IgnoreGitHubDismissed)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		logging.Log.Infof("%s: %d CVEs detected with GHSA %s/%s",
 | 
			
		||||
			r.FormatServerName(), n, owner, repo)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectWordPressCves detects CVEs of WordPress
 | 
			
		||||
func DetectWordPressCves(r *models.ScanResult, wpCnf config.WpScanConf) error {
 | 
			
		||||
	if len(r.WordPressPackages) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Infof("%s: Detect WordPress CVE. Number of pkgs: %d ", r.ServerInfo(), len(r.WordPressPackages))
 | 
			
		||||
	n, err := detectWordPressCves(r, wpCnf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to detect WordPress CVE: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Infof("%s: found %d WordPress CVEs", r.FormatServerName(), n)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCvesWithNvdJvn fills CVE detail with NVD, JVN
 | 
			
		||||
func FillCvesWithNvdJvn(r *models.ScanResult, cnf config.GoCveDictConf, logOpts logging.LogOpts) (err error) {
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for _, v := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := newGoCveDictClient(&cnf, logOpts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := client.closeDB(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	var ds []cvemodels.CveDetail
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		ds, err = client.fetchCveDetailsViaHTTP(cveIDs)
 | 
			
		||||
	} else {
 | 
			
		||||
		ds, err = client.fetchCveDetails(cveIDs)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, d := range ds {
 | 
			
		||||
		nvd, exploits, mitigations := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON)
 | 
			
		||||
		jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
 | 
			
		||||
 | 
			
		||||
		alerts := fillCertAlerts(&d)
 | 
			
		||||
		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 != nil && !con.Empty() {
 | 
			
		||||
						vinfo.CveContents[con.Type] = *con
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				vinfo.AlertDict = alerts
 | 
			
		||||
				vinfo.Exploits = append(vinfo.Exploits, exploits...)
 | 
			
		||||
				vinfo.Mitigations = append(vinfo.Mitigations, mitigations...)
 | 
			
		||||
				r.ScannedCves[cveID] = vinfo
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fillCertAlerts(cvedetail *cvemodels.CveDetail) (dict models.AlertDict) {
 | 
			
		||||
	if cvedetail.NvdJSON != nil {
 | 
			
		||||
		for _, cert := range cvedetail.NvdJSON.Certs {
 | 
			
		||||
			dict.En = append(dict.En, models.Alert{
 | 
			
		||||
				URL:   cert.Link,
 | 
			
		||||
				Title: cert.Title,
 | 
			
		||||
				Team:  "us",
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if cvedetail.Jvn != nil {
 | 
			
		||||
		for _, cert := range cvedetail.Jvn.Certs {
 | 
			
		||||
			dict.Ja = append(dict.Ja, models.Alert{
 | 
			
		||||
				URL:   cert.Link,
 | 
			
		||||
				Title: cert.Title,
 | 
			
		||||
				Team:  "jp",
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return dict
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// detectPkgsCvesWithOval fetches OVAL database
 | 
			
		||||
func detectPkgsCvesWithOval(cnf config.GovalDictConf, r *models.ScanResult) error {
 | 
			
		||||
	ovalClient, err := oval.NewOVALClient(r.Family, cnf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if ovalClient == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf("Check if oval fetched: %s %s", r.Family, r.Release)
 | 
			
		||||
	ok, err := ovalClient.CheckIfOvalFetched(r.Family, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		if r.Family == constant.Debian {
 | 
			
		||||
			logging.Log.Debug("Skip OVAL and Scan with gost alone.")
 | 
			
		||||
			logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), 0)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf("Check if oval fresh: %s %s", r.Family, r.Release)
 | 
			
		||||
	_, err = ovalClient.CheckIfOvalFresh(r.Family, r.Release)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Debugf("Fill with oval: %s %s", r.Family, r.Release)
 | 
			
		||||
	nCVEs, err := ovalClient.FillWithOval(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), nCVEs)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detectPkgsCvesWithGost(cnf config.GostConf, r *models.ScanResult) error {
 | 
			
		||||
	client, err := gost.NewClient(cnf, r.Family)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to new a gost client: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := client.CloseDB(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to close the gost DB. err: %+v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	nCVEs, err := client.DetectCVEs(r, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if r.Family == constant.Debian {
 | 
			
		||||
			return xerrors.Errorf("Failed to detect CVEs with gost: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if r.Family == constant.Debian {
 | 
			
		||||
		logging.Log.Infof("%s: %d CVEs are detected with gost",
 | 
			
		||||
			r.FormatServerName(), nCVEs)
 | 
			
		||||
	} else {
 | 
			
		||||
		logging.Log.Infof("%s: %d unfixed CVEs are detected with gost",
 | 
			
		||||
			r.FormatServerName(), nCVEs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectCpeURIsCves detects CVEs of given CPE-URIs
 | 
			
		||||
func DetectCpeURIsCves(r *models.ScanResult, cpeURIs []string, cnf config.GoCveDictConf, logOpts logging.LogOpts) error {
 | 
			
		||||
	client, err := newGoCveDictClient(&cnf, logOpts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := client.closeDB(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	nCVEs := 0
 | 
			
		||||
	for _, name := range cpeURIs {
 | 
			
		||||
		details, err := client.fetchCveDetailsByCpeName(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, detail := range details {
 | 
			
		||||
			confidence := models.CpeVersionMatch
 | 
			
		||||
			if detail.HasJvn() && !detail.HasNvd() {
 | 
			
		||||
				// In the case of CpeVendorProduct-match, only the JVN is set(Nvd is not set).
 | 
			
		||||
				confidence = models.CpeVendorProductMatch
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if val, ok := r.ScannedCves[detail.CveID]; ok {
 | 
			
		||||
				val.CpeURIs = util.AppendIfMissing(val.CpeURIs, name)
 | 
			
		||||
				val.Confidences.AppendIfMissing(confidence)
 | 
			
		||||
				r.ScannedCves[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:       detail.CveID,
 | 
			
		||||
					CpeURIs:     []string{name},
 | 
			
		||||
					Confidences: models.Confidences{confidence},
 | 
			
		||||
				}
 | 
			
		||||
				r.ScannedCves[detail.CveID] = v
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCweDict fills CWE
 | 
			
		||||
func FillCweDict(r *models.ScanResult) {
 | 
			
		||||
	uniqCweIDMap := map[string]bool{}
 | 
			
		||||
	for _, vinfo := range r.ScannedCves {
 | 
			
		||||
		for _, cont := range vinfo.CveContents {
 | 
			
		||||
			for _, id := range cont.CweIDs {
 | 
			
		||||
				if strings.HasPrefix(id, "CWE-") {
 | 
			
		||||
					id = strings.TrimPrefix(id, "CWE-")
 | 
			
		||||
					uniqCweIDMap[id] = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dict := map[string]models.CweDictEntry{}
 | 
			
		||||
	for id := range uniqCweIDMap {
 | 
			
		||||
		entry := models.CweDictEntry{}
 | 
			
		||||
		if e, ok := cwe.CweDictEn[id]; ok {
 | 
			
		||||
			if rank, ok := cwe.OwaspTopTen2017[id]; ok {
 | 
			
		||||
				entry.OwaspTopTen2017 = rank
 | 
			
		||||
			}
 | 
			
		||||
			if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
 | 
			
		||||
				entry.CweTopTwentyfive2019 = rank
 | 
			
		||||
			}
 | 
			
		||||
			if rank, ok := cwe.SansTopTwentyfive[id]; ok {
 | 
			
		||||
				entry.SansTopTwentyfive = rank
 | 
			
		||||
			}
 | 
			
		||||
			entry.En = &e
 | 
			
		||||
		} else {
 | 
			
		||||
			logging.Log.Debugf("CWE-ID %s is not found in English CWE Dict", id)
 | 
			
		||||
			entry.En = &cwe.Cwe{CweID: id}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.Lang == "ja" {
 | 
			
		||||
			if e, ok := cwe.CweDictJa[id]; ok {
 | 
			
		||||
				if rank, ok := cwe.OwaspTopTen2017[id]; ok {
 | 
			
		||||
					entry.OwaspTopTen2017 = rank
 | 
			
		||||
				}
 | 
			
		||||
				if rank, ok := cwe.CweTopTwentyfive2019[id]; ok {
 | 
			
		||||
					entry.CweTopTwentyfive2019 = rank
 | 
			
		||||
				}
 | 
			
		||||
				if rank, ok := cwe.SansTopTwentyfive[id]; ok {
 | 
			
		||||
					entry.SansTopTwentyfive = rank
 | 
			
		||||
				}
 | 
			
		||||
				entry.Ja = &e
 | 
			
		||||
			} else {
 | 
			
		||||
				logging.Log.Debugf("CWE-ID %s is not found in Japanese CWE Dict", id)
 | 
			
		||||
				entry.Ja = &cwe.Cwe{CweID: id}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		dict[id] = entry
 | 
			
		||||
	}
 | 
			
		||||
	r.CweDict = dict
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										223
									
								
								detector/exploitdb.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								detector/exploitdb.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,223 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	exploitdb "github.com/vulsio/go-exploitdb/db"
 | 
			
		||||
	exploitmodels "github.com/vulsio/go-exploitdb/models"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithExploit fills exploit information that has in Exploit
 | 
			
		||||
func FillWithExploit(r *models.ScanResult, cnf config.ExploitConf) (nExploitCve int, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
		for cveID := range r.ScannedCves {
 | 
			
		||||
			cveIDs = append(cveIDs, cveID)
 | 
			
		||||
		}
 | 
			
		||||
		prefix, _ := util.URLPathJoin(cnf.GetURL(), "cves")
 | 
			
		||||
		responses, err := getCvesViaHTTP(cveIDs, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			exps := []*exploitmodels.Exploit{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &exps); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			exploits := ConvertToModels(exps)
 | 
			
		||||
			v, ok := r.ScannedCves[res.request.cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				v.Exploits = exploits
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[res.request.cveID] = v
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
 | 
			
		||||
		driver, locked, err := newExploitDB(&cnf)
 | 
			
		||||
		if locked {
 | 
			
		||||
			return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
 | 
			
		||||
		} else if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
			if cveID == "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			es := driver.GetExploitByCveID(cveID)
 | 
			
		||||
			if len(es) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			exploits := ConvertToModels(es)
 | 
			
		||||
			vuln.Exploits = exploits
 | 
			
		||||
			r.ScannedCves[cveID] = vuln
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nExploitCve, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModels converts gost model to vuls model
 | 
			
		||||
func ConvertToModels(es []*exploitmodels.Exploit) (exploits []models.Exploit) {
 | 
			
		||||
	for _, e := range es {
 | 
			
		||||
		var documentURL, shellURL *string
 | 
			
		||||
		if e.OffensiveSecurity != nil {
 | 
			
		||||
			os := e.OffensiveSecurity
 | 
			
		||||
			if os.Document != nil {
 | 
			
		||||
				documentURL = &os.Document.DocumentURL
 | 
			
		||||
			}
 | 
			
		||||
			if os.ShellCode != nil {
 | 
			
		||||
				shellURL = &os.ShellCode.ShellCodeURL
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		exploit := models.Exploit{
 | 
			
		||||
			ExploitType:  e.ExploitType,
 | 
			
		||||
			ID:           e.ExploitUniqueID,
 | 
			
		||||
			URL:          e.URL,
 | 
			
		||||
			Description:  e.Description,
 | 
			
		||||
			DocumentURL:  documentURL,
 | 
			
		||||
			ShellCodeURL: shellURL,
 | 
			
		||||
		}
 | 
			
		||||
		exploits = append(exploits, exploit)
 | 
			
		||||
	}
 | 
			
		||||
	return exploits
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type exploitResponse struct {
 | 
			
		||||
	request request
 | 
			
		||||
	json    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
 | 
			
		||||
	responses []exploitResponse, err error) {
 | 
			
		||||
	nReq := len(cveIDs)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan exploitResponse, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				cveID: cveID,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.cveID,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					logging.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			responses = append(responses, res)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, xerrors.New("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	osMajorVersion string
 | 
			
		||||
	packName       string
 | 
			
		||||
	isSrcPack      bool
 | 
			
		||||
	cveID          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, req request, resChan chan<- exploitResponse, 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().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errChan <- xerrors.Errorf("HTTP Error %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- xerrors.New("Retry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resChan <- exploitResponse{
 | 
			
		||||
		request: req,
 | 
			
		||||
		json:    body,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newExploitDB(cnf config.VulnDictInterface) (driver exploitdb.DB, locked bool, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.GetURL()
 | 
			
		||||
	if cnf.GetType() == "sqlite3" {
 | 
			
		||||
		path = cnf.GetSQLite3Path()
 | 
			
		||||
	}
 | 
			
		||||
	if driver, locked, err = exploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL()); err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			return nil, true, xerrors.Errorf("exploitDB is locked. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
package github
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
@@ -9,7 +11,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/errof"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
@@ -17,11 +18,11 @@ import (
 | 
			
		||||
 | 
			
		||||
// DetectGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
 | 
			
		||||
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
 | 
			
		||||
//TODO move to report
 | 
			
		||||
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
 | 
			
		||||
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string, ignoreDismissed bool) (nCVEs int, err error) {
 | 
			
		||||
	src := oauth2.StaticTokenSource(
 | 
			
		||||
		&oauth2.Token{AccessToken: token},
 | 
			
		||||
	)
 | 
			
		||||
	//TODO Proxy
 | 
			
		||||
	httpClient := oauth2.NewClient(context.Background(), src)
 | 
			
		||||
 | 
			
		||||
	// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
 | 
			
		||||
@@ -32,10 +33,12 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string)
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
 | 
			
		||||
		req, err := http.NewRequest("POST",
 | 
			
		||||
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
		req, err := http.NewRequestWithContext(ctx, http.MethodPost,
 | 
			
		||||
			"https://api.github.com/graphql",
 | 
			
		||||
			bytes.NewBuffer([]byte(jsonStr)),
 | 
			
		||||
		)
 | 
			
		||||
		defer cancel()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -71,7 +74,7 @@ func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
 | 
			
		||||
			if config.Conf.IgnoreGitHubDismissed && v.Node.DismissReason != "" {
 | 
			
		||||
			if ignoreDismissed && v.Node.DismissReason != "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
package libmanager
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
@@ -12,13 +14,12 @@ import (
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
	"k8s.io/utils/clock"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DetectLibsCves fills LibraryScanner information
 | 
			
		||||
func DetectLibsCves(r *models.ScanResult) (err error) {
 | 
			
		||||
func DetectLibsCves(r *models.ScanResult, cacheDir string, noProgress bool) (err error) {
 | 
			
		||||
	totalCnt := 0
 | 
			
		||||
	if len(r.LibraryScanners) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
@@ -30,12 +31,12 @@ func DetectLibsCves(r *models.ScanResult) (err error) {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Updating library db...")
 | 
			
		||||
	if err := downloadDB(config.Version, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress, false, false); err != nil {
 | 
			
		||||
	logging.Log.Info("Updating library db...")
 | 
			
		||||
	if err := downloadDB("", cacheDir, noProgress, false, false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db2.Init(config.Conf.TrivyCacheDBDir); err != nil {
 | 
			
		||||
	if err := db2.Init(cacheDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer db2.Close()
 | 
			
		||||
@@ -57,7 +58,7 @@ func DetectLibsCves(r *models.ScanResult) (err error) {
 | 
			
		||||
		totalCnt += len(vinfos)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Infof("%s: %d CVEs are detected with Library",
 | 
			
		||||
	logging.Log.Infof("%s: %d CVEs are detected with Library",
 | 
			
		||||
		r.FormatServerName(), totalCnt)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -72,8 +73,8 @@ func downloadDB(appVersion, cacheDir string, quiet, light, skipUpdate bool) erro
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if needsUpdate {
 | 
			
		||||
		util.Log.Info("Need to update DB")
 | 
			
		||||
		util.Log.Info("Downloading DB...")
 | 
			
		||||
		logging.Log.Info("Need to update DB")
 | 
			
		||||
		logging.Log.Info("Downloading DB...")
 | 
			
		||||
		if err := client.Download(ctx, cacheDir, light); err != nil {
 | 
			
		||||
			return xerrors.Errorf("failed to download vulnerability DB: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
@@ -106,7 +107,7 @@ func showDBInfo(cacheDir string) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("something wrong with DB: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
 | 
			
		||||
	logging.Log.Debugf("DB Schema: %d, Type: %d, UpdatedAt: %s, NextUpdate: %s",
 | 
			
		||||
		metadata.Version, metadata.Type, metadata.UpdatedAt, metadata.NextUpdate)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								detector/msf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								detector/msf.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	metasploitdb "github.com/takuzoo3868/go-msfdb/db"
 | 
			
		||||
	metasploitmodels "github.com/takuzoo3868/go-msfdb/models"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithMetasploit fills metasploit module information that has in module
 | 
			
		||||
func FillWithMetasploit(r *models.ScanResult, cnf config.MetasploitConf) (nMetasploitCve int, err error) {
 | 
			
		||||
 | 
			
		||||
	driver, locked, err := newMetasploitDB(&cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return 0, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := driver.CloseDB(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
		if cveID == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ms := driver.GetModuleByCveID(cveID)
 | 
			
		||||
		if len(ms) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		modules := ConvertToModelsMsf(ms)
 | 
			
		||||
		vuln.Metasploits = modules
 | 
			
		||||
		r.ScannedCves[cveID] = vuln
 | 
			
		||||
		nMetasploitCve++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nMetasploitCve, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModelsMsf converts gost model to vuls model
 | 
			
		||||
func ConvertToModelsMsf(ms []*metasploitmodels.Metasploit) (modules []models.Metasploit) {
 | 
			
		||||
	for _, m := range ms {
 | 
			
		||||
		var links []string
 | 
			
		||||
		if 0 < len(m.References) {
 | 
			
		||||
			for _, u := range m.References {
 | 
			
		||||
				links = append(links, u.Link)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		module := models.Metasploit{
 | 
			
		||||
			Name:        m.Name,
 | 
			
		||||
			Title:       m.Title,
 | 
			
		||||
			Description: m.Description,
 | 
			
		||||
			URLs:        links,
 | 
			
		||||
		}
 | 
			
		||||
		modules = append(modules, module)
 | 
			
		||||
	}
 | 
			
		||||
	return modules
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newMetasploitDB(cnf config.VulnDictInterface) (driver metasploitdb.DB, locked bool, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.GetURL()
 | 
			
		||||
	if cnf.GetType() == "sqlite3" {
 | 
			
		||||
		path = cnf.GetSQLite3Path()
 | 
			
		||||
	}
 | 
			
		||||
	if driver, locked, err = metasploitdb.NewDB(cnf.GetType(), path, cnf.GetDebugSQL(), false); err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			return nil, true, xerrors.Errorf("metasploitDB is locked. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										272
									
								
								detector/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										272
									
								
								detector/util.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,272 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func reuseScannedCves(r *models.ScanResult) bool {
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case constant.FreeBSD, constant.Raspbian:
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if isTrivyResult(r) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isTrivyResult(r *models.ScanResult) bool {
 | 
			
		||||
	_, ok := r.Optional["trivy-target"]
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needToRefreshCve(r models.ScanResult) bool {
 | 
			
		||||
	for _, cve := range r.ScannedCves {
 | 
			
		||||
		if 0 < len(cve.CveContents) {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadPrevious(currs models.ScanResults, resultsDir string) (prevs models.ScanResults, err error) {
 | 
			
		||||
	dirs, err := ListValidJSONDirs(resultsDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, result := range currs {
 | 
			
		||||
		filename := result.ServerName + ".json"
 | 
			
		||||
		if result.Container.Name != "" {
 | 
			
		||||
			filename = fmt.Sprintf("%s@%s.json", result.Container.Name, result.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
		for _, dir := range dirs[1:] {
 | 
			
		||||
			path := filepath.Join(dir, filename)
 | 
			
		||||
			r, err := loadOneServerScanResult(path)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logging.Log.Debugf("%+v", err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if r.Family == result.Family && r.Release == result.Release {
 | 
			
		||||
				prevs = append(prevs, *r)
 | 
			
		||||
				logging.Log.Infof("Previous json found: %s", path)
 | 
			
		||||
				break
 | 
			
		||||
			} else {
 | 
			
		||||
				logging.Log.Infof("Previous json is different family.Release: %s, pre: %s.%s cur: %s.%s",
 | 
			
		||||
					path, r.Family, r.Release, result.Family, result.Release)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return prevs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func diff(curResults, preResults models.ScanResults, isPlus, isMinus bool) (diffed models.ScanResults) {
 | 
			
		||||
	for _, current := range curResults {
 | 
			
		||||
		found := false
 | 
			
		||||
		var previous models.ScanResult
 | 
			
		||||
		for _, r := range preResults {
 | 
			
		||||
			if current.ServerName == r.ServerName && current.Container.Name == r.Container.Name {
 | 
			
		||||
				found = true
 | 
			
		||||
				previous = r
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			diffed = append(diffed, current)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cves := models.VulnInfos{}
 | 
			
		||||
		if isPlus {
 | 
			
		||||
			cves = getPlusDiffCves(previous, current)
 | 
			
		||||
		}
 | 
			
		||||
		if isMinus {
 | 
			
		||||
			minus := getMinusDiffCves(previous, current)
 | 
			
		||||
			if len(cves) == 0 {
 | 
			
		||||
				cves = minus
 | 
			
		||||
			} else {
 | 
			
		||||
				for k, v := range minus {
 | 
			
		||||
					cves[k] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		packages := models.Packages{}
 | 
			
		||||
		for _, s := range cves {
 | 
			
		||||
			for _, affected := range s.AffectedPackages {
 | 
			
		||||
				var p models.Package
 | 
			
		||||
				if s.DiffStatus == models.DiffPlus {
 | 
			
		||||
					p = current.Packages[affected.Name]
 | 
			
		||||
				} else {
 | 
			
		||||
					p = previous.Packages[affected.Name]
 | 
			
		||||
				}
 | 
			
		||||
				packages[affected.Name] = p
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		current.ScannedCves = cves
 | 
			
		||||
		current.Packages = packages
 | 
			
		||||
		diffed = append(diffed, current)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getPlusDiffCves(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) {
 | 
			
		||||
				v.DiffStatus = models.DiffPlus
 | 
			
		||||
				updated[v.CveID] = v
 | 
			
		||||
				logging.Log.Debugf("updated: %s", v.CveID)
 | 
			
		||||
 | 
			
		||||
				// TODO commented out because  a bug of diff logic when multiple oval defs found for a certain CVE-ID and same updated_at
 | 
			
		||||
				// if these OVAL defs have different affected packages, this logic detects as updated.
 | 
			
		||||
				// This logic will be uncomented after integration with gost https://github.com/knqyf263/gost
 | 
			
		||||
				// } else if isCveFixed(v, previous) {
 | 
			
		||||
				// updated[v.CveID] = v
 | 
			
		||||
				// logging.Log.Debugf("fixed: %s", v.CveID)
 | 
			
		||||
 | 
			
		||||
			} else {
 | 
			
		||||
				logging.Log.Debugf("same: %s", v.CveID)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			logging.Log.Debugf("new: %s", v.CveID)
 | 
			
		||||
			v.DiffStatus = models.DiffPlus
 | 
			
		||||
			new[v.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(updated) == 0 && len(new) == 0 {
 | 
			
		||||
		logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for cveID, vuln := range new {
 | 
			
		||||
		updated[cveID] = vuln
 | 
			
		||||
	}
 | 
			
		||||
	return updated
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getMinusDiffCves(previous, current models.ScanResult) models.VulnInfos {
 | 
			
		||||
	currentCveIDsSet := map[string]bool{}
 | 
			
		||||
	for _, currentVulnInfo := range current.ScannedCves {
 | 
			
		||||
		currentCveIDsSet[currentVulnInfo.CveID] = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear := models.VulnInfos{}
 | 
			
		||||
	for _, v := range previous.ScannedCves {
 | 
			
		||||
		if !currentCveIDsSet[v.CveID] {
 | 
			
		||||
			v.DiffStatus = models.DiffMinus
 | 
			
		||||
			clear[v.CveID] = v
 | 
			
		||||
			logging.Log.Debugf("clear: %s", v.CveID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(clear) == 0 {
 | 
			
		||||
		logging.Log.Infof("%s: There are %d vulnerabilities, but no difference between current result and previous one.", current.FormatServerName(), len(current.ScannedCves))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return clear
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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{}
 | 
			
		||||
	preVinfo, ok := previous.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if content, ok := preVinfo.CveContents[cType]; ok {
 | 
			
		||||
			prevLastModified[cType] = content.LastModified
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	curLastModified := map[models.CveContentType]time.Time{}
 | 
			
		||||
	curVinfo, ok := current.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	for _, cType := range cTypes {
 | 
			
		||||
		if content, ok := curVinfo.CveContents[cType]; ok {
 | 
			
		||||
			curLastModified[cType] = content.LastModified
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, t := range cTypes {
 | 
			
		||||
		if !curLastModified[t].Equal(prevLastModified[t]) {
 | 
			
		||||
			logging.Log.Debugf("%s LastModified not equal: \n%s\n%s",
 | 
			
		||||
				cveID, curLastModified[t], prevLastModified[t])
 | 
			
		||||
			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(resultsDir string) (dirs []string, err error) {
 | 
			
		||||
	var dirInfo []os.FileInfo
 | 
			
		||||
	if dirInfo, err = ioutil.ReadDir(resultsDir); err != nil {
 | 
			
		||||
		err = xerrors.Errorf("Failed to read %s: %w",
 | 
			
		||||
			config.Conf.ResultsDir, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	for _, d := range dirInfo {
 | 
			
		||||
		if d.IsDir() && jsonDirPattern.MatchString(d.Name()) {
 | 
			
		||||
			jsonDir := filepath.Join(resultsDir, d.Name())
 | 
			
		||||
			dirs = append(dirs, jsonDir)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(dirs, func(i, j int) bool {
 | 
			
		||||
		return dirs[j] < dirs[i]
 | 
			
		||||
	})
 | 
			
		||||
	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, xerrors.Errorf("Failed to read %s: %w", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	result := &models.ScanResult{}
 | 
			
		||||
	if err := json.Unmarshal(data, result); err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to parse %s: %w", jsonFile, err)
 | 
			
		||||
	}
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,9 @@
 | 
			
		||||
package wordpress
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
@@ -8,8 +11,9 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/errof"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	version "github.com/hashicorp/go-version"
 | 
			
		||||
@@ -30,11 +34,10 @@ type WpCveInfos struct {
 | 
			
		||||
 | 
			
		||||
//WpCveInfo is for wpscan json
 | 
			
		||||
type WpCveInfo struct {
 | 
			
		||||
	ID        string `json:"id"`
 | 
			
		||||
	Title     string `json:"title"`
 | 
			
		||||
	CreatedAt string `json:"created_at"`
 | 
			
		||||
	UpdatedAt string `json:"updated_at"`
 | 
			
		||||
	// PublishedDate string     `json:"published_date"`
 | 
			
		||||
	ID         string     `json:"id"`
 | 
			
		||||
	Title      string     `json:"title"`
 | 
			
		||||
	CreatedAt  time.Time  `json:"created_at"`
 | 
			
		||||
	UpdatedAt  time.Time  `json:"updated_at"`
 | 
			
		||||
	VulnType   string     `json:"vuln_type"`
 | 
			
		||||
	References References `json:"references"`
 | 
			
		||||
	FixedIn    string     `json:"fixed_in"`
 | 
			
		||||
@@ -49,18 +52,18 @@ type References struct {
 | 
			
		||||
 | 
			
		||||
// DetectWordPressCves access to wpscan and fetch scurity alerts and then set to the given ScanResult.
 | 
			
		||||
// https://wpscan.com/
 | 
			
		||||
// TODO move to report
 | 
			
		||||
func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
func detectWordPressCves(r *models.ScanResult, cnf config.WpScanConf) (int, error) {
 | 
			
		||||
	if len(r.WordPressPackages) == 0 {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	// Core
 | 
			
		||||
	ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1)
 | 
			
		||||
	if ver == "" {
 | 
			
		||||
		return 0, xerrors.New("Failed to get WordPress core version")
 | 
			
		||||
		return 0, errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to get WordPress core version."))
 | 
			
		||||
	}
 | 
			
		||||
	url := fmt.Sprintf("https://wpscan.com/api/v3/wordpresses/%s", ver)
 | 
			
		||||
	wpVinfos, err := wpscan(url, ver, cnf.Token)
 | 
			
		||||
	wpVinfos, err := wpscan(url, ver, cnf.Token, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -72,7 +75,7 @@ func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range themes {
 | 
			
		||||
		url := fmt.Sprintf("https://wpscan.com/api/v3/themes/%s", p.Name)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -87,7 +90,7 @@ func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range plugins {
 | 
			
		||||
		url := fmt.Sprintf("https://wpscan.com/api/v3/plugins/%s", p.Name)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -109,14 +112,16 @@ func DetectWordPressCves(r *models.ScanResult, cnf *c.WpScanConf) (int, error) {
 | 
			
		||||
	return len(wpVinfos), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wpscan(url, name, token string) (vinfos []models.VulnInfo, err error) {
 | 
			
		||||
func wpscan(url, name, token string, isCore bool) (vinfos []models.VulnInfo, err error) {
 | 
			
		||||
	body, err := httpRequest(url, token)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.comm. body: %s, err: %s", string(body), err))
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if body == "" {
 | 
			
		||||
		util.Log.Debugf("wpscan.com response body is empty. URL: %s", url)
 | 
			
		||||
		logging.Log.Debugf("wpscan.com response body is empty. URL: %s", url)
 | 
			
		||||
	}
 | 
			
		||||
	if isCore {
 | 
			
		||||
		name = "core"
 | 
			
		||||
	}
 | 
			
		||||
	return convertToVinfos(name, body)
 | 
			
		||||
}
 | 
			
		||||
@@ -126,17 +131,17 @@ func detect(installed models.WpPackage, candidates []models.VulnInfo) (vulns []m
 | 
			
		||||
		for _, fixstat := range v.WpPackageFixStats {
 | 
			
		||||
			ok, err := match(installed.Version, fixstat.FixedIn)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("Failed to compare versions %s installed: %s, fixedIn: %s, v: %+v",
 | 
			
		||||
				logging.Log.Warnf("Failed to compare versions %s installed: %s, fixedIn: %s, v: %+v",
 | 
			
		||||
					installed.Name, installed.Version, fixstat.FixedIn, v)
 | 
			
		||||
				// continue scanning
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if ok {
 | 
			
		||||
				vulns = append(vulns, v)
 | 
			
		||||
				util.Log.Debugf("Affected: %s installed: %s, fixedIn: %s",
 | 
			
		||||
				logging.Log.Debugf("Affected: %s installed: %s, fixedIn: %s",
 | 
			
		||||
					installed.Name, installed.Version, fixstat.FixedIn)
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("Not affected: %s : %s, fixedIn: %s",
 | 
			
		||||
				logging.Log.Debugf("Not affected: %s : %s, fixedIn: %s",
 | 
			
		||||
					installed.Name, installed.Version, fixstat.FixedIn)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
@@ -196,10 +201,12 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
				CveContents: models.NewCveContents(
 | 
			
		||||
					models.CveContent{
 | 
			
		||||
						Type:       models.WpScan,
 | 
			
		||||
						CveID:      cveID,
 | 
			
		||||
						Title:      vulnerability.Title,
 | 
			
		||||
						References: refs,
 | 
			
		||||
						Type:         models.WpScan,
 | 
			
		||||
						CveID:        cveID,
 | 
			
		||||
						Title:        vulnerability.Title,
 | 
			
		||||
						References:   refs,
 | 
			
		||||
						Published:    vulnerability.CreatedAt,
 | 
			
		||||
						LastModified: vulnerability.UpdatedAt,
 | 
			
		||||
					},
 | 
			
		||||
				),
 | 
			
		||||
				VulnType: vulnerability.VulnType,
 | 
			
		||||
@@ -217,21 +224,27 @@ func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnI
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpRequest(url, token string) (string, error) {
 | 
			
		||||
	retry := 1
 | 
			
		||||
	util.Log.Debugf("%s", url)
 | 
			
		||||
	req, err := http.NewRequest("GET", url, nil)
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
 | 
			
		||||
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token))
 | 
			
		||||
loop:
 | 
			
		||||
	resp, err := new(http.Client).Do(req)
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
		return "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode == 200 {
 | 
			
		||||
@@ -239,14 +252,13 @@ loop:
 | 
			
		||||
	} else if resp.StatusCode == 404 {
 | 
			
		||||
		// This package is not in wpscan
 | 
			
		||||
		return "", nil
 | 
			
		||||
	} else if resp.StatusCode == 429 && retry <= 3 {
 | 
			
		||||
		// 429 Too Many Requests
 | 
			
		||||
		util.Log.Debugf("sleep %d min(s): %s", retry, resp.Status)
 | 
			
		||||
		time.Sleep(time.Duration(retry) * time.Minute)
 | 
			
		||||
		retry++
 | 
			
		||||
		goto loop
 | 
			
		||||
	} else if resp.StatusCode == 429 {
 | 
			
		||||
		return "", errof.New(errof.ErrWpScanAPILimitExceeded,
 | 
			
		||||
			fmt.Sprintf("wpscan.com API limit exceeded: %+v", resp.Status))
 | 
			
		||||
	} else {
 | 
			
		||||
		logging.Log.Warnf("wpscan.com unknown status code: %+v", resp.Status)
 | 
			
		||||
		return "", nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeInactives(pkgs models.WordPressPackages) (removed models.WordPressPackages) {
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
package wordpress
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
@@ -19,6 +19,9 @@ var (
 | 
			
		||||
 | 
			
		||||
	// ErrFailedToAccessWpScan is error of wpscan.com api access
 | 
			
		||||
	ErrFailedToAccessWpScan ErrorCode = "ErrFailedToAccessWpScan"
 | 
			
		||||
 | 
			
		||||
	// ErrWpScanAPILimitExceeded is error of wpscan.com api limit exceeded
 | 
			
		||||
	ErrWpScanAPILimitExceeded ErrorCode = "ErrWpScanAPILimitExceeded"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// New :
 | 
			
		||||
 
 | 
			
		||||
@@ -1,85 +0,0 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/vulsio/go-exploitdb/db"
 | 
			
		||||
	exploitmodels "github.com/vulsio/go-exploitdb/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithExploit fills exploit information that has in Exploit
 | 
			
		||||
func FillWithExploit(driver db.DB, r *models.ScanResult, cnf *config.ExploitConf) (nExploitCve int, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
		for cveID := range r.ScannedCves {
 | 
			
		||||
			cveIDs = append(cveIDs, cveID)
 | 
			
		||||
		}
 | 
			
		||||
		prefix, _ := util.URLPathJoin(cnf.URL, "cves")
 | 
			
		||||
		responses, err := getCvesViaHTTP(cveIDs, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			exps := []*exploitmodels.Exploit{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &exps); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			exploits := ConvertToModels(exps)
 | 
			
		||||
			v, ok := r.ScannedCves[res.request.cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				v.Exploits = exploits
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[res.request.cveID] = v
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
			if cveID == "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			es := driver.GetExploitByCveID(cveID)
 | 
			
		||||
			if len(es) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			exploits := ConvertToModels(es)
 | 
			
		||||
			vuln.Exploits = exploits
 | 
			
		||||
			r.ScannedCves[cveID] = vuln
 | 
			
		||||
			nExploitCve++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nExploitCve, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModels converts gost model to vuls model
 | 
			
		||||
func ConvertToModels(es []*exploitmodels.Exploit) (exploits []models.Exploit) {
 | 
			
		||||
	for _, e := range es {
 | 
			
		||||
		var documentURL, shellURL *string
 | 
			
		||||
		if e.OffensiveSecurity != nil {
 | 
			
		||||
			os := e.OffensiveSecurity
 | 
			
		||||
			if os.Document != nil {
 | 
			
		||||
				documentURL = &os.Document.DocumentURL
 | 
			
		||||
			}
 | 
			
		||||
			if os.ShellCode != nil {
 | 
			
		||||
				shellURL = &os.ShellCode.ShellCodeURL
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		exploit := models.Exploit{
 | 
			
		||||
			ExploitType:  e.ExploitType,
 | 
			
		||||
			ID:           e.ExploitUniqueID,
 | 
			
		||||
			URL:          e.URL,
 | 
			
		||||
			Description:  e.Description,
 | 
			
		||||
			DocumentURL:  documentURL,
 | 
			
		||||
			ShellCodeURL: shellURL,
 | 
			
		||||
		}
 | 
			
		||||
		exploits = append(exploits, exploit)
 | 
			
		||||
	}
 | 
			
		||||
	return exploits
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										115
									
								
								exploit/util.go
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								exploit/util.go
									
									
									
									
									
								
							@@ -1,115 +0,0 @@
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type response struct {
 | 
			
		||||
	request request
 | 
			
		||||
	json    string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
 | 
			
		||||
	responses []response, err error) {
 | 
			
		||||
	nReq := len(cveIDs)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
	errChan := make(chan error, nReq)
 | 
			
		||||
	defer close(reqChan)
 | 
			
		||||
	defer close(resChan)
 | 
			
		||||
	defer close(errChan)
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			reqChan <- request{
 | 
			
		||||
				cveID: cveID,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	concurrency := 10
 | 
			
		||||
	tasks := util.GenWorkers(concurrency)
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		tasks <- func() {
 | 
			
		||||
			select {
 | 
			
		||||
			case req := <-reqChan:
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.cveID,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	timeout := time.After(2 * 60 * time.Second)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	for i := 0; i < nReq; i++ {
 | 
			
		||||
		select {
 | 
			
		||||
		case res := <-resChan:
 | 
			
		||||
			responses = append(responses, res)
 | 
			
		||||
		case err := <-errChan:
 | 
			
		||||
			errs = append(errs, err)
 | 
			
		||||
		case <-timeout:
 | 
			
		||||
			return nil, xerrors.New("Timeout Fetching OVAL")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(errs) != 0 {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type request struct {
 | 
			
		||||
	osMajorVersion string
 | 
			
		||||
	packName       string
 | 
			
		||||
	isSrcPack      bool
 | 
			
		||||
	cveID          string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpGet(url string, req request, 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 xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
		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 <- xerrors.Errorf("HTTP Error %w", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if count == retryMax {
 | 
			
		||||
		errChan <- xerrors.New("Retry count exceeded")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
		request: req,
 | 
			
		||||
		json:    body,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								go.mod
									
									
									
									
									
								
							@@ -1,50 +1,66 @@
 | 
			
		||||
module github.com/future-architect/vuls
 | 
			
		||||
 | 
			
		||||
go 1.15
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go v50.2.0+incompatible
 | 
			
		||||
	github.com/BurntSushi/toml v0.3.1
 | 
			
		||||
	github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd
 | 
			
		||||
	github.com/aquasecurity/trivy v0.15.0
 | 
			
		||||
	github.com/aquasecurity/trivy-db v0.0.0-20210121143430-2a5c54036a86
 | 
			
		||||
	github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
 | 
			
		||||
	github.com/Ullaakut/nmap/v2 v2.1.2-0.20210406060955-59a52fe80a4f
 | 
			
		||||
	github.com/VividCortex/ewma v1.2.0 // indirect
 | 
			
		||||
	github.com/aquasecurity/fanal v0.0.0-20210520034323-54c5a82e861f
 | 
			
		||||
	github.com/aquasecurity/trivy v0.18.3
 | 
			
		||||
	github.com/aquasecurity/trivy-db v0.0.0-20210429114658-ae22941a55d0
 | 
			
		||||
	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
 | 
			
		||||
	github.com/aws/aws-sdk-go v1.36.31
 | 
			
		||||
	github.com/boltdb/bolt v1.3.1
 | 
			
		||||
	github.com/briandowns/spinner v1.16.0 // indirect
 | 
			
		||||
	github.com/cenkalti/backoff v2.2.1+incompatible
 | 
			
		||||
	github.com/cheggaaa/pb/v3 v3.0.8 // indirect
 | 
			
		||||
	github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b
 | 
			
		||||
	github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
 | 
			
		||||
	github.com/emersion/go-smtp v0.14.0
 | 
			
		||||
	github.com/fatih/color v1.12.0 // indirect
 | 
			
		||||
	github.com/go-redis/redis/v8 v8.11.0 // indirect
 | 
			
		||||
	github.com/google/subcommands v1.2.0
 | 
			
		||||
	github.com/gosuri/uitable v0.0.4
 | 
			
		||||
	github.com/hashicorp/go-uuid v1.0.2
 | 
			
		||||
	github.com/hashicorp/go-version v1.2.1
 | 
			
		||||
	github.com/hashicorp/go-version v1.3.0
 | 
			
		||||
	github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
 | 
			
		||||
	github.com/jackc/pgproto3/v2 v2.1.0 // indirect
 | 
			
		||||
	github.com/jesseduffield/gocui v0.3.0
 | 
			
		||||
	github.com/k0kubun/pp v3.0.1+incompatible
 | 
			
		||||
	github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
 | 
			
		||||
	github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
 | 
			
		||||
	github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
 | 
			
		||||
	github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
 | 
			
		||||
	github.com/knqyf263/gost v0.1.7
 | 
			
		||||
	github.com/kotakanbe/go-cve-dictionary v0.5.7
 | 
			
		||||
	github.com/knqyf263/gost v0.2.0
 | 
			
		||||
	github.com/kotakanbe/go-cve-dictionary v0.6.2
 | 
			
		||||
	github.com/kotakanbe/go-pingscanner v0.1.0
 | 
			
		||||
	github.com/kotakanbe/goval-dictionary v0.3.1
 | 
			
		||||
	github.com/kotakanbe/goval-dictionary v0.3.6-0.20210625044258-9be85404d7dd
 | 
			
		||||
	github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96
 | 
			
		||||
	github.com/lib/pq v1.10.2 // indirect
 | 
			
		||||
	github.com/mattn/go-isatty v0.0.13 // indirect
 | 
			
		||||
	github.com/mattn/go-runewidth v0.0.13 // indirect
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 | 
			
		||||
	github.com/mitchellh/go-homedir v1.1.0
 | 
			
		||||
	github.com/nlopes/slack v0.6.0
 | 
			
		||||
	github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.4
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5
 | 
			
		||||
	github.com/parnurzeal/gorequest v0.2.16
 | 
			
		||||
	github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
 | 
			
		||||
	github.com/sirupsen/logrus v1.7.0
 | 
			
		||||
	github.com/spf13/afero v1.5.1
 | 
			
		||||
	github.com/spf13/cobra v1.1.1
 | 
			
		||||
	github.com/takuzoo3868/go-msfdb v0.1.3
 | 
			
		||||
	github.com/vulsio/go-exploitdb v0.1.4
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196
 | 
			
		||||
	github.com/sirupsen/logrus v1.8.0
 | 
			
		||||
	github.com/spf13/afero v1.6.0
 | 
			
		||||
	github.com/spf13/cobra v1.1.3
 | 
			
		||||
	github.com/spf13/viper v1.8.1 // indirect
 | 
			
		||||
	github.com/takuzoo3868/go-msfdb v0.1.5
 | 
			
		||||
	github.com/vulsio/go-exploitdb v0.1.8-0.20210625021845-e5081ca67229
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
 | 
			
		||||
	golang.org/x/net v0.0.0-20210716203947-853a461950ff // indirect
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602
 | 
			
		||||
	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
 | 
			
		||||
	gorm.io/driver/mysql v1.1.1 // indirect
 | 
			
		||||
	gorm.io/gorm v1.21.11 // indirect
 | 
			
		||||
	k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								gost/base.go
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								gost/base.go
									
									
									
									
									
								
							@@ -1,17 +0,0 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCVEsWithRedHat fills cve information that has in Gost
 | 
			
		||||
func (b Base) FillCVEsWithRedHat(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
	return RedHat{}.fillCvesWithRedHatAPI(driver, r)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										176
									
								
								gost/debian.go
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								gost/debian.go
									
									
									
									
									
								
							@@ -5,11 +5,12 @@ package gost
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	debver "github.com/knqyf263/go-deb-version"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Debian is Gost client for Debian GNU/Linux
 | 
			
		||||
@@ -21,6 +22,7 @@ type packCves struct {
 | 
			
		||||
	packName  string
 | 
			
		||||
	isSrcPack bool
 | 
			
		||||
	cves      []models.CveContent
 | 
			
		||||
	fixes     models.PackageFixStatuses
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (deb Debian) supported(major string) bool {
 | 
			
		||||
@@ -32,19 +34,18 @@ func (deb Debian) supported(major string) bool {
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectUnfixed fills cve information that has in Gost
 | 
			
		||||
func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (deb Debian) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if !deb.supported(major(r.Release)) {
 | 
			
		||||
		// only logging
 | 
			
		||||
		util.Log.Warnf("Debian %s is not supported yet", r.Release)
 | 
			
		||||
		logging.Log.Warnf("Debian %s is not supported yet", r.Release)
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
	// Add linux and set the version of running kernel to search OVAL.
 | 
			
		||||
	// Add linux and set the version of running kernel to search Gost.
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		newVer := ""
 | 
			
		||||
		if p, ok := r.Packages[linuxImage]; ok {
 | 
			
		||||
		if p, ok := r.Packages["linux-image-"+r.RunningKernel.Release]; ok {
 | 
			
		||||
			newVer = p.NewVersion
 | 
			
		||||
		}
 | 
			
		||||
		r.Packages["linux"] = models.Package{
 | 
			
		||||
@@ -54,18 +55,35 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Debian Security Tracker does not support Package for Raspbian, so skip it.
 | 
			
		||||
	var scanResult models.ScanResult
 | 
			
		||||
	if r.Family != config.Raspbian {
 | 
			
		||||
		scanResult = *r
 | 
			
		||||
	} else {
 | 
			
		||||
		scanResult = r.RemoveRaspbianPackFromResult()
 | 
			
		||||
	stashLinuxPackage := r.Packages["linux"]
 | 
			
		||||
	nFixedCVEs, err := deb.detectCVEsWithFixState(r, "resolved")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.Packages["linux"] = stashLinuxPackage
 | 
			
		||||
	nUnfixedCVEs, err := deb.detectCVEsWithFixState(r, "open")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (nFixedCVEs + nUnfixedCVEs), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (deb Debian) detectCVEsWithFixState(r *models.ScanResult, fixStatus string) (nCVEs int, err error) {
 | 
			
		||||
	if fixStatus != "resolved" && fixStatus != "open" {
 | 
			
		||||
		return 0, xerrors.Errorf(`Failed to detectCVEsWithFixState. fixStatus is not allowed except "open" and "resolved"(actual: fixStatus -> %s).`, fixStatus)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packCvesList := []packCves{}
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.Gost.URL, "debian", major(scanResult.Release), "pkgs")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, url)
 | 
			
		||||
	if deb.DBDriver.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(deb.DBDriver.Cnf.GetURL(), "debian", major(r.Release), "pkgs")
 | 
			
		||||
		s := "unfixed-cves"
 | 
			
		||||
		if s == "resolved" {
 | 
			
		||||
			s = "fixed-cves"
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		responses, err := getCvesWithFixStateViaHTTP(r, url, s)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -76,43 +94,40 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			fixes := []models.PackageFixStatus{}
 | 
			
		||||
			for _, debcve := range debCves {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&debcve))
 | 
			
		||||
				fixes = append(fixes, checkPackageFixStatus(&debcve)...)
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  res.request.packName,
 | 
			
		||||
				isSrcPack: res.request.isSrcPack,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
				fixes:     fixes,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
		if deb.DBDriver.DB == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range scanResult.Packages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
			}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			cves, fixes := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  pack.Name,
 | 
			
		||||
				isSrcPack: false,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
				fixes:     fixes,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// SrcPack
 | 
			
		||||
		for _, pack := range scanResult.SrcPackages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
			}
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			cves, fixes := deb.getCvesDebianWithfixStatus(fixStatus, major(r.Release), pack.Name)
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  pack.Name,
 | 
			
		||||
				isSrcPack: true,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
				fixes:     fixes,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
@@ -120,13 +135,14 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
	delete(r.Packages, "linux")
 | 
			
		||||
 | 
			
		||||
	for _, p := range packCvesList {
 | 
			
		||||
		for _, cve := range p.cves {
 | 
			
		||||
		for i, cve := range p.cves {
 | 
			
		||||
			v, ok := r.ScannedCves[cve.CveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(cve)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.DebianSecurityTracker] = cve
 | 
			
		||||
					v.Confidences = models.Confidences{models.DebianSecurityTrackerMatch}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
@@ -134,6 +150,31 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
					CveContents: models.NewCveContents(cve),
 | 
			
		||||
					Confidences: models.Confidences{models.DebianSecurityTrackerMatch},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if fixStatus == "resolved" {
 | 
			
		||||
					versionRelease := ""
 | 
			
		||||
					if p.isSrcPack {
 | 
			
		||||
						versionRelease = r.SrcPackages[p.packName].Version
 | 
			
		||||
					} else {
 | 
			
		||||
						versionRelease = r.Packages[p.packName].FormatVer()
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if versionRelease == "" {
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					affected, err := isGostDefAffected(versionRelease, p.fixes[i].FixedIn)
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						logging.Log.Debugf("Failed to parse versions: %s, Ver: %s, Gost: %s",
 | 
			
		||||
							err, versionRelease, p.fixes[i].FixedIn)
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if !affected {
 | 
			
		||||
						continue
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -148,25 +189,65 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if p.packName == "linux" {
 | 
			
		||||
					names = append(names, linuxImage)
 | 
			
		||||
					names = append(names, "linux-image-"+r.RunningKernel.Release)
 | 
			
		||||
				} else {
 | 
			
		||||
					names = append(names, p.packName)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, name := range names {
 | 
			
		||||
				v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					FixState:    "open",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				})
 | 
			
		||||
			if fixStatus == "resolved" {
 | 
			
		||||
				for _, name := range names {
 | 
			
		||||
					v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
 | 
			
		||||
						Name:    name,
 | 
			
		||||
						FixedIn: p.fixes[i].FixedIn,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				for _, name := range names {
 | 
			
		||||
					v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
 | 
			
		||||
						Name:        name,
 | 
			
		||||
						FixState:    "open",
 | 
			
		||||
						NotFixedYet: true,
 | 
			
		||||
					})
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			r.ScannedCves[cve.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isGostDefAffected(versionRelease, gostVersion string) (affected bool, err error) {
 | 
			
		||||
	vera, err := debver.NewVersion(versionRelease)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	verb, err := debver.NewVersion(gostVersion)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return vera.LessThan(verb), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (deb Debian) getCvesDebianWithfixStatus(fixStatus, release, pkgName string) (cves []models.CveContent, fixes []models.PackageFixStatus) {
 | 
			
		||||
	var f func(string, string) map[string]gostmodels.DebianCVE
 | 
			
		||||
 | 
			
		||||
	if fixStatus == "resolved" {
 | 
			
		||||
		f = deb.DBDriver.DB.GetFixedCvesDebian
 | 
			
		||||
	} else {
 | 
			
		||||
		f = deb.DBDriver.DB.GetUnfixedCvesDebian
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, cveDeb := range f(release, pkgName) {
 | 
			
		||||
		cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
		fixes = append(fixes, checkPackageFixStatus(&cveDeb)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
 | 
			
		||||
	severity := ""
 | 
			
		||||
@@ -188,3 +269,22 @@ func (deb Debian) ConvertToModel(cve *gostmodels.DebianCVE) *models.CveContent {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func checkPackageFixStatus(cve *gostmodels.DebianCVE) []models.PackageFixStatus {
 | 
			
		||||
	fixes := []models.PackageFixStatus{}
 | 
			
		||||
	for _, p := range cve.Package {
 | 
			
		||||
		for _, r := range p.Release {
 | 
			
		||||
			f := models.PackageFixStatus{Name: p.PackageName}
 | 
			
		||||
 | 
			
		||||
			if r.Status == "open" {
 | 
			
		||||
				f.NotFixedYet = true
 | 
			
		||||
			} else {
 | 
			
		||||
				f.FixedIn = r.FixedVersion
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			fixes = append(fixes, f)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fixes
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										90
									
								
								gost/gost.go
									
									
									
									
									
								
							
							
						
						
									
										90
									
								
								gost/gost.go
									
									
									
									
									
								
							@@ -3,27 +3,95 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DBDriver is a DB Driver
 | 
			
		||||
type DBDriver struct {
 | 
			
		||||
	DB  db.DB
 | 
			
		||||
	Cnf config.VulnDictInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	DetectUnfixed(db.DB, *models.ScanResult, bool) (int, error)
 | 
			
		||||
	FillCVEsWithRedHat(db.DB, *models.ScanResult) error
 | 
			
		||||
	DetectCVEs(*models.ScanResult, bool) (int, error)
 | 
			
		||||
	CloseDB() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	DBDriver DBDriver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CloseDB close a DB connection
 | 
			
		||||
func (b Base) CloseDB() error {
 | 
			
		||||
	if b.DBDriver.DB == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return b.DBDriver.DB.CloseDB()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillCVEsWithRedHat fills CVE detailed with Red Hat Security
 | 
			
		||||
func FillCVEsWithRedHat(r *models.ScanResult, cnf config.GostConf) error {
 | 
			
		||||
	db, locked, err := newGostDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := db.CloseDB(); err != nil {
 | 
			
		||||
			logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
	return RedHat{Base{DBDriver{DB: db, Cnf: &cnf}}}.fillCvesWithRedHatAPI(r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewClient make Client by family
 | 
			
		||||
func NewClient(family string) Client {
 | 
			
		||||
func NewClient(cnf config.GostConf, family string) (Client, error) {
 | 
			
		||||
	db, locked, err := newGostDB(cnf)
 | 
			
		||||
	if locked {
 | 
			
		||||
		return nil, xerrors.Errorf("SQLite3 is locked: %s", cnf.GetSQLite3Path())
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	driver := DBDriver{DB: db, Cnf: &cnf}
 | 
			
		||||
 | 
			
		||||
	switch family {
 | 
			
		||||
	case cnf.RedHat, cnf.CentOS:
 | 
			
		||||
		return RedHat{}
 | 
			
		||||
	case cnf.Debian, cnf.Raspbian:
 | 
			
		||||
		return Debian{}
 | 
			
		||||
	case cnf.Windows:
 | 
			
		||||
		return Microsoft{}
 | 
			
		||||
	case constant.RedHat, constant.CentOS, constant.Rocky:
 | 
			
		||||
		return RedHat{Base{DBDriver: driver}}, nil
 | 
			
		||||
	case constant.Debian, constant.Raspbian:
 | 
			
		||||
		return Debian{Base{DBDriver: driver}}, nil
 | 
			
		||||
	case constant.Ubuntu:
 | 
			
		||||
		return Ubuntu{Base{DBDriver: driver}}, nil
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		return Microsoft{Base{DBDriver: driver}}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return Pseudo{}
 | 
			
		||||
		return Pseudo{Base{DBDriver: driver}}, nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewGostDB returns db client for Gost
 | 
			
		||||
func newGostDB(cnf config.GostConf) (driver db.DB, locked bool, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, false, nil
 | 
			
		||||
	}
 | 
			
		||||
	path := cnf.GetURL()
 | 
			
		||||
	if cnf.GetType() == "sqlite3" {
 | 
			
		||||
		path = cnf.GetSQLite3Path()
 | 
			
		||||
	}
 | 
			
		||||
	if driver, locked, err = db.NewDB(cnf.GetType(), path, cnf.GetDebugSQL()); err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			return nil, true, xerrors.Errorf("gostDB is locked. err: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		return nil, false, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, false, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,6 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -15,16 +14,16 @@ type Microsoft struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectUnfixed fills cve information that has in Gost
 | 
			
		||||
func (ms Microsoft) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if driver == nil {
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (ms Microsoft) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if ms.DBDriver.DB == nil {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for cveID := range r.ScannedCves {
 | 
			
		||||
		cveIDs = append(cveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
	for cveID, msCve := range driver.GetMicrosoftMulti(cveIDs) {
 | 
			
		||||
	for cveID, msCve := range ms.DBDriver.DB.GetMicrosoftMulti(cveIDs) {
 | 
			
		||||
		if _, ok := r.ScannedCves[cveID]; !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
@@ -70,11 +69,10 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
 | 
			
		||||
 | 
			
		||||
	option := map[string]string{}
 | 
			
		||||
	if 0 < len(cve.ExploitStatus) {
 | 
			
		||||
		// TODO: CVE-2020-0739
 | 
			
		||||
		// "exploit_status": "Publicly Disclosed:No;Exploited:No;Latest Software Release:Exploitation Less Likely;Older Software Release:Exploitation Less Likely;DOS:N/A",
 | 
			
		||||
		option["exploit"] = cve.ExploitStatus
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(cve.Workaround) {
 | 
			
		||||
		option["workaround"] = cve.Workaround
 | 
			
		||||
	}
 | 
			
		||||
	kbids := []string{}
 | 
			
		||||
	for _, kbid := range cve.KBIDs {
 | 
			
		||||
		kbids = append(kbids, kbid.KBID)
 | 
			
		||||
@@ -86,13 +84,18 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveCon
 | 
			
		||||
	vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
 | 
			
		||||
	mitigations := []models.Mitigation{}
 | 
			
		||||
	if cve.Mitigation != "" {
 | 
			
		||||
		mitigations = []models.Mitigation{
 | 
			
		||||
			{
 | 
			
		||||
				CveContentType: models.Microsoft,
 | 
			
		||||
				Mitigation:     cve.Mitigation,
 | 
			
		||||
				URL:            vendorURL,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		mitigations = append(mitigations, models.Mitigation{
 | 
			
		||||
			CveContentType: models.Microsoft,
 | 
			
		||||
			Mitigation:     cve.Mitigation,
 | 
			
		||||
			URL:            vendorURL,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	if cve.Workaround != "" {
 | 
			
		||||
		mitigations = append(mitigations, models.Mitigation{
 | 
			
		||||
			CveContentType: models.Microsoft,
 | 
			
		||||
			Mitigation:     cve.Workaround,
 | 
			
		||||
			URL:            vendorURL,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Pseudo is Gost client except for RedHat family and Debian
 | 
			
		||||
@@ -12,7 +11,7 @@ type Pseudo struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectUnfixed fills cve information that has in Gost
 | 
			
		||||
func (pse Pseudo) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (int, error) {
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (pse Pseudo) DetectCVEs(r *models.ScanResult, _ bool) (int, error) {
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -19,15 +18,10 @@ type RedHat struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectUnfixed fills cve information that has in Gost
 | 
			
		||||
func (red RedHat) DetectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
 | 
			
		||||
	return red.detectUnfixed(driver, r, ignoreWillNotFix)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) detectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(config.Conf.Gost.URL,
 | 
			
		||||
			"redhat", major(r.Release), "pkgs")
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (red RedHat) DetectCVEs(r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
 | 
			
		||||
	if red.DBDriver.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(red.DBDriver.Cnf.GetURL(), "redhat", major(r.Release), "pkgs")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
@@ -45,12 +39,12 @@ func (red RedHat) detectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNo
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
		if red.DBDriver.DB == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := driver.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
 | 
			
		||||
			cves := red.DBDriver.DB.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
 | 
			
		||||
					nCVEs++
 | 
			
		||||
@@ -61,7 +55,7 @@ func (red RedHat) detectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNo
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) fillCvesWithRedHatAPI(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error {
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
		if _, ok := vuln.CveContents[models.RedHatAPI]; ok {
 | 
			
		||||
@@ -70,9 +64,8 @@ func (red RedHat) fillCvesWithRedHatAPI(driver db.DB, r *models.ScanResult) erro
 | 
			
		||||
		cveIDs = append(cveIDs, cveID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(config.Conf.Gost.URL,
 | 
			
		||||
			"redhat", "cves")
 | 
			
		||||
	if red.DBDriver.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		prefix, _ := util.URLPathJoin(config.Conf.Gost.URL, "redhat", "cves")
 | 
			
		||||
		responses, err := getCvesViaHTTP(cveIDs, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
@@ -88,10 +81,10 @@ func (red RedHat) fillCvesWithRedHatAPI(driver db.DB, r *models.ScanResult) erro
 | 
			
		||||
			red.setFixedCveToScanResult(&redCve, r)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
		if red.DBDriver.DB == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, redCve := range driver.GetRedhatMulti(cveIDs) {
 | 
			
		||||
		for _, redCve := range red.DBDriver.DB.GetRedhatMulti(cveIDs) {
 | 
			
		||||
			if len(redCve.Name) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										190
									
								
								gost/ubuntu.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								gost/ubuntu.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Ubuntu is Gost client for Ubuntu
 | 
			
		||||
type Ubuntu struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (ubu Ubuntu) supported(version string) bool {
 | 
			
		||||
	_, ok := map[string]string{
 | 
			
		||||
		"1404": "trusty",
 | 
			
		||||
		"1604": "xenial",
 | 
			
		||||
		"1804": "bionic",
 | 
			
		||||
		"2004": "focal",
 | 
			
		||||
		"2010": "groovy",
 | 
			
		||||
		"2104": "hirsute",
 | 
			
		||||
	}[version]
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectCVEs fills cve information that has in Gost
 | 
			
		||||
func (ubu Ubuntu) DetectCVEs(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	ubuReleaseVer := strings.Replace(r.Release, ".", "", 1)
 | 
			
		||||
	if !ubu.supported(ubuReleaseVer) {
 | 
			
		||||
		logging.Log.Warnf("Ubuntu %s is not supported yet", r.Release)
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
	// Add linux and set the version of running kernel to search Gost.
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		newVer := ""
 | 
			
		||||
		if p, ok := r.Packages[linuxImage]; ok {
 | 
			
		||||
			newVer = p.NewVersion
 | 
			
		||||
		}
 | 
			
		||||
		r.Packages["linux"] = models.Package{
 | 
			
		||||
			Name:       "linux",
 | 
			
		||||
			Version:    r.RunningKernel.Version,
 | 
			
		||||
			NewVersion: newVer,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packCvesList := []packCves{}
 | 
			
		||||
	if ubu.DBDriver.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(ubu.DBDriver.Cnf.GetURL(), "ubuntu", ubuReleaseVer, "pkg")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, url)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			ubuCves := map[string]gostmodels.UbuntuCVE{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &ubuCves); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, ubucve := range ubuCves {
 | 
			
		||||
				cves = append(cves, *ubu.ConvertToModel(&ubucve))
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  res.request.packName,
 | 
			
		||||
				isSrcPack: res.request.isSrcPack,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if ubu.DBDriver.DB == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			ubuCves := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, ubucve := range ubuCves {
 | 
			
		||||
				cves = append(cves, *ubu.ConvertToModel(&ubucve))
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  pack.Name,
 | 
			
		||||
				isSrcPack: false,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// SrcPack
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			ubuCves := ubu.DBDriver.DB.GetUnfixedCvesUbuntu(ubuReleaseVer, pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, ubucve := range ubuCves {
 | 
			
		||||
				cves = append(cves, *ubu.ConvertToModel(&ubucve))
 | 
			
		||||
			}
 | 
			
		||||
			packCvesList = append(packCvesList, packCves{
 | 
			
		||||
				packName:  pack.Name,
 | 
			
		||||
				isSrcPack: true,
 | 
			
		||||
				cves:      cves,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	delete(r.Packages, "linux")
 | 
			
		||||
 | 
			
		||||
	for _, p := range packCvesList {
 | 
			
		||||
		for _, cve := range p.cves {
 | 
			
		||||
			v, ok := r.ScannedCves[cve.CveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(cve)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.UbuntuAPI] = cve
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cve.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(cve),
 | 
			
		||||
					Confidences: models.Confidences{models.UbuntuAPIMatch},
 | 
			
		||||
				}
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			names := []string{}
 | 
			
		||||
			if p.isSrcPack {
 | 
			
		||||
				if srcPack, ok := r.SrcPackages[p.packName]; ok {
 | 
			
		||||
					for _, binName := range srcPack.BinaryNames {
 | 
			
		||||
						if _, ok := r.Packages[binName]; ok {
 | 
			
		||||
							names = append(names, binName)
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				if p.packName == "linux" {
 | 
			
		||||
					names = append(names, linuxImage)
 | 
			
		||||
				} else {
 | 
			
		||||
					names = append(names, p.packName)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, name := range names {
 | 
			
		||||
				v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{
 | 
			
		||||
					Name:        name,
 | 
			
		||||
					FixState:    "open",
 | 
			
		||||
					NotFixedYet: true,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[cve.CveID] = v
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (ubu Ubuntu) ConvertToModel(cve *gostmodels.UbuntuCVE) *models.CveContent {
 | 
			
		||||
	references := []models.Reference{}
 | 
			
		||||
	for _, r := range cve.References {
 | 
			
		||||
		if strings.Contains(r.Reference, "https://cve.mitre.org/cgi-bin/cvename.cgi?name=") {
 | 
			
		||||
			references = append(references, models.Reference{Source: "CVE", Link: r.Reference})
 | 
			
		||||
		} else {
 | 
			
		||||
			references = append(references, models.Reference{Link: r.Reference})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, b := range cve.Bugs {
 | 
			
		||||
		references = append(references, models.Reference{Source: "Bug", Link: b.Bug})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, u := range cve.Upstreams {
 | 
			
		||||
		for _, upstreamLink := range u.UpstreamLinks {
 | 
			
		||||
			references = append(references, models.Reference{Source: "UPSTREAM", Link: upstreamLink.Link})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		Type:          models.UbuntuAPI,
 | 
			
		||||
		CveID:         cve.Candidate,
 | 
			
		||||
		Summary:       cve.Description,
 | 
			
		||||
		Cvss2Severity: cve.Priority,
 | 
			
		||||
		Cvss3Severity: cve.Priority,
 | 
			
		||||
		SourceLink:    "https://ubuntu.com/security/" + cve.Candidate,
 | 
			
		||||
		References:    references,
 | 
			
		||||
		Published:     cve.PublicDate,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										137
									
								
								gost/ubuntu_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								gost/ubuntu_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,137 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestUbuntu_Supported(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		ubuReleaseVer string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "14.04 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "1404",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "16.04 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "1604",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "18.04 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "1804",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "20.04 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "2004",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "20.10 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "2010",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "21.04 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "2104",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "empty string is not supported yet",
 | 
			
		||||
			args: args{
 | 
			
		||||
				ubuReleaseVer: "",
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			ubu := Ubuntu{}
 | 
			
		||||
			if got := ubu.supported(tt.args.ubuReleaseVer); got != tt.want {
 | 
			
		||||
				t.Errorf("Ubuntu.Supported() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUbuntuConvertToModel(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		input    gostmodels.UbuntuCVE
 | 
			
		||||
		expected models.CveContent
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "gost Ubuntu.ConvertToModel",
 | 
			
		||||
			input: gostmodels.UbuntuCVE{
 | 
			
		||||
				Candidate:  "CVE-2021-3517",
 | 
			
		||||
				PublicDate: time.Date(2021, 5, 19, 14, 15, 0, 0, time.UTC),
 | 
			
		||||
				References: []gostmodels.UbuntuReference{
 | 
			
		||||
					{Reference: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3517"},
 | 
			
		||||
					{Reference: "https://gitlab.gnome.org/GNOME/libxml2/-/issues/235"},
 | 
			
		||||
					{Reference: "https://gitlab.gnome.org/GNOME/libxml2/-/commit/bf22713507fe1fc3a2c4b525cf0a88c2dc87a3a2"}},
 | 
			
		||||
				Description: "description.",
 | 
			
		||||
				Notes:       []gostmodels.UbuntuNote{},
 | 
			
		||||
				Bugs:        []gostmodels.UbuntuBug{{Bug: "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=987738"}},
 | 
			
		||||
				Priority:    "medium",
 | 
			
		||||
				Patches: []gostmodels.UbuntuPatch{
 | 
			
		||||
					{PackageName: "libxml2", ReleasePatches: []gostmodels.UbuntuReleasePatch{
 | 
			
		||||
						{ReleaseName: "focal", Status: "needed", Note: ""},
 | 
			
		||||
					}},
 | 
			
		||||
				},
 | 
			
		||||
				Upstreams: []gostmodels.UbuntuUpstream{{
 | 
			
		||||
					PackageName: "libxml2", UpstreamLinks: []gostmodels.UbuntuUpstreamLink{
 | 
			
		||||
						{Link: "https://gitlab.gnome.org/GNOME/libxml2/-/commit/50f06b3efb638efb0abd95dc62dca05ae67882c2"},
 | 
			
		||||
					},
 | 
			
		||||
				}},
 | 
			
		||||
			},
 | 
			
		||||
			expected: models.CveContent{
 | 
			
		||||
				Type:          models.UbuntuAPI,
 | 
			
		||||
				CveID:         "CVE-2021-3517",
 | 
			
		||||
				Summary:       "description.",
 | 
			
		||||
				Cvss2Severity: "medium",
 | 
			
		||||
				Cvss3Severity: "medium",
 | 
			
		||||
				SourceLink:    "https://ubuntu.com/security/CVE-2021-3517",
 | 
			
		||||
				References: []models.Reference{
 | 
			
		||||
					{Source: "CVE", Link: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-3517"},
 | 
			
		||||
					{Link: "https://gitlab.gnome.org/GNOME/libxml2/-/issues/235"},
 | 
			
		||||
					{Link: "https://gitlab.gnome.org/GNOME/libxml2/-/commit/bf22713507fe1fc3a2c4b525cf0a88c2dc87a3a2"},
 | 
			
		||||
					{Source: "Bug", Link: "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=987738"},
 | 
			
		||||
					{Source: "UPSTREAM", Link: "https://gitlab.gnome.org/GNOME/libxml2/-/commit/50f06b3efb638efb0abd95dc62dca05ae67882c2"}},
 | 
			
		||||
				Published: time.Date(2021, 5, 19, 14, 15, 0, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			ubu := Ubuntu{}
 | 
			
		||||
			got := ubu.ConvertToModel(&tt.input)
 | 
			
		||||
			if !reflect.DeepEqual(got, &tt.expected) {
 | 
			
		||||
				t.Errorf("Ubuntu.ConvertToModel() = %#v, want %#v", got, &tt.expected)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								gost/util.go
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								gost/util.go
									
									
									
									
									
								
							@@ -1,3 +1,5 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -6,6 +8,7 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cenkalti/backoff"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
@@ -48,7 +51,7 @@ func getCvesViaHTTP(cveIDs []string, urlPrefix string) (
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					logging.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -82,7 +85,10 @@ type request struct {
 | 
			
		||||
 | 
			
		||||
func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) (
 | 
			
		||||
	responses []response, err error) {
 | 
			
		||||
	return getCvesWithFixStateViaHTTP(r, urlPrefix, "unfixed-cves")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCvesWithFixStateViaHTTP(r *models.ScanResult, urlPrefix, fixState string) (responses []response, err error) {
 | 
			
		||||
	nReq := len(r.Packages) + len(r.SrcPackages)
 | 
			
		||||
	reqChan := make(chan request, nReq)
 | 
			
		||||
	resChan := make(chan response, nReq)
 | 
			
		||||
@@ -117,12 +123,12 @@ func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) (
 | 
			
		||||
				url, err := util.URLPathJoin(
 | 
			
		||||
					urlPrefix,
 | 
			
		||||
					req.packName,
 | 
			
		||||
					"unfixed-cves",
 | 
			
		||||
					fixState,
 | 
			
		||||
				)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					errChan <- err
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					logging.Log.Debugf("HTTP Request to %s", url)
 | 
			
		||||
					httpGet(url, req, resChan, errChan)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -154,18 +160,18 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
	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()
 | 
			
		||||
		resp, body, errs = gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			count++
 | 
			
		||||
			if count == retryMax {
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
			return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	notify := func(err error, t time.Duration) {
 | 
			
		||||
		util.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %s", t, err)
 | 
			
		||||
		logging.Log.Warnf("Failed to HTTP GET. retrying in %s seconds. err: %+v", t, err)
 | 
			
		||||
	}
 | 
			
		||||
	err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4231
									
								
								integration/data/lockfile/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										4231
									
								
								integration/data/lockfile/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										311
									
								
								integration/data/lockfile/Gemfile.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								integration/data/lockfile/Gemfile.lock
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,311 @@
 | 
			
		||||
GEM
 | 
			
		||||
  remote: https://rubygems.org/
 | 
			
		||||
  specs:
 | 
			
		||||
    actionmailer (4.2.6)
 | 
			
		||||
      actionpack (= 4.2.6)
 | 
			
		||||
      actionview (= 4.2.6)
 | 
			
		||||
      activejob (= 4.2.6)
 | 
			
		||||
      mail (~> 2.5, >= 2.5.4)
 | 
			
		||||
      rails-dom-testing (~> 1.0, >= 1.0.5)
 | 
			
		||||
    actionpack (4.2.6)
 | 
			
		||||
      actionview (= 4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      rack (~> 1.6)
 | 
			
		||||
      rack-test (~> 0.6.2)
 | 
			
		||||
      rails-dom-testing (~> 1.0, >= 1.0.5)
 | 
			
		||||
      rails-html-sanitizer (~> 1.0, >= 1.0.2)
 | 
			
		||||
    actionpack-action_caching (1.1.1)
 | 
			
		||||
      actionpack (>= 4.0.0, < 5.0)
 | 
			
		||||
    actionpack-xml_parser (1.0.2)
 | 
			
		||||
      actionpack (>= 4.0.0, < 5)
 | 
			
		||||
    actionview (4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      builder (~> 3.1)
 | 
			
		||||
      erubis (~> 2.7.0)
 | 
			
		||||
      rails-dom-testing (~> 1.0, >= 1.0.5)
 | 
			
		||||
      rails-html-sanitizer (~> 1.0, >= 1.0.2)
 | 
			
		||||
    activejob (4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      globalid (>= 0.3.0)
 | 
			
		||||
    activemodel (4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      builder (~> 3.1)
 | 
			
		||||
    activerecord (4.2.6)
 | 
			
		||||
      activemodel (= 4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      arel (~> 6.0)
 | 
			
		||||
    activesupport (4.2.6)
 | 
			
		||||
      i18n (~> 0.7)
 | 
			
		||||
      json (~> 1.7, >= 1.7.7)
 | 
			
		||||
      minitest (~> 5.1)
 | 
			
		||||
      thread_safe (~> 0.3, >= 0.3.4)
 | 
			
		||||
      tzinfo (~> 1.1)
 | 
			
		||||
    addressable (2.4.0)
 | 
			
		||||
    arel (6.0.3)
 | 
			
		||||
    bourbon (4.2.7)
 | 
			
		||||
      sass (~> 3.4)
 | 
			
		||||
      thor (~> 0.19)
 | 
			
		||||
    builder (3.2.2)
 | 
			
		||||
    byebug (8.2.4)
 | 
			
		||||
    capistrano (3.4.1)
 | 
			
		||||
      i18n
 | 
			
		||||
      rake (>= 10.0.0)
 | 
			
		||||
      sshkit (~> 1.3)
 | 
			
		||||
    capistrano-bundler (1.1.4)
 | 
			
		||||
      capistrano (~> 3.1)
 | 
			
		||||
      sshkit (~> 1.2)
 | 
			
		||||
    capistrano-passenger (0.2.0)
 | 
			
		||||
      capistrano (~> 3.0)
 | 
			
		||||
    capistrano-rails (1.1.6)
 | 
			
		||||
      capistrano (~> 3.1)
 | 
			
		||||
      capistrano-bundler (~> 1.1)
 | 
			
		||||
    capybara (2.7.0)
 | 
			
		||||
      addressable
 | 
			
		||||
      mime-types (>= 1.16)
 | 
			
		||||
      nokogiri (>= 1.3.3)
 | 
			
		||||
      rack (>= 1.0.0)
 | 
			
		||||
      rack-test (>= 0.5.4)
 | 
			
		||||
      xpath (~> 2.0)
 | 
			
		||||
    childprocess (0.5.9)
 | 
			
		||||
      ffi (~> 1.0, >= 1.0.11)
 | 
			
		||||
    coderay (1.1.1)
 | 
			
		||||
    concurrent-ruby (1.0.1)
 | 
			
		||||
    css_parser (1.3.7)
 | 
			
		||||
      addressable
 | 
			
		||||
    daemons (1.2.3)
 | 
			
		||||
    database_cleaner (1.5.2)
 | 
			
		||||
    diff-lcs (1.2.5)
 | 
			
		||||
    docile (1.1.5)
 | 
			
		||||
    erubis (2.7.0)
 | 
			
		||||
    eventmachine (1.2.0.1)
 | 
			
		||||
    faraday (0.8.11)
 | 
			
		||||
      multipart-post (~> 1.2.0)
 | 
			
		||||
    faraday_middleware (0.9.2)
 | 
			
		||||
      faraday (>= 0.7.4, < 0.10)
 | 
			
		||||
    ffi (1.9.10)
 | 
			
		||||
    fuubar (2.0.0)
 | 
			
		||||
      rspec (~> 3.0)
 | 
			
		||||
      ruby-progressbar (~> 1.4)
 | 
			
		||||
    gemoji (1.5.0)
 | 
			
		||||
    globalid (0.3.6)
 | 
			
		||||
      activesupport (>= 4.1.0)
 | 
			
		||||
    hashie (1.2.0)
 | 
			
		||||
    headless (2.2.3)
 | 
			
		||||
    htmlentities (4.3.1)
 | 
			
		||||
    i18n (0.7.0)
 | 
			
		||||
    inifile (3.0.0)
 | 
			
		||||
    jquery-rails (3.1.4)
 | 
			
		||||
      railties (>= 3.0, < 5.0)
 | 
			
		||||
      thor (>= 0.14, < 2.0)
 | 
			
		||||
    json (1.8.3)
 | 
			
		||||
    le (2.7.1)
 | 
			
		||||
    loofah (2.0.3)
 | 
			
		||||
      nokogiri (>= 1.5.9)
 | 
			
		||||
    mail (2.6.4)
 | 
			
		||||
      mime-types (>= 1.16, < 4)
 | 
			
		||||
    metaclass (0.0.4)
 | 
			
		||||
    method_source (0.8.2)
 | 
			
		||||
    mime-types (3.0)
 | 
			
		||||
      mime-types-data (~> 3.2015)
 | 
			
		||||
    mime-types-data (3.2016.0221)
 | 
			
		||||
    mini_portile2 (2.0.0)
 | 
			
		||||
    minitest (5.8.4)
 | 
			
		||||
    mocha (1.1.0)
 | 
			
		||||
      metaclass (~> 0.0.1)
 | 
			
		||||
    multi_json (1.11.2)
 | 
			
		||||
    multipart-post (1.2.0)
 | 
			
		||||
    net-ldap (0.12.1)
 | 
			
		||||
    net-scp (1.2.1)
 | 
			
		||||
      net-ssh (>= 2.6.5)
 | 
			
		||||
    net-ssh (3.1.1)
 | 
			
		||||
    nokogiri (1.6.7.2)
 | 
			
		||||
      mini_portile2 (~> 2.0.0.rc2)
 | 
			
		||||
    pg (0.18.4)
 | 
			
		||||
    power_assert (0.2.7)
 | 
			
		||||
    protected_attributes (1.1.3)
 | 
			
		||||
      activemodel (>= 4.0.1, < 5.0)
 | 
			
		||||
    pry (0.10.3)
 | 
			
		||||
      coderay (~> 1.1.0)
 | 
			
		||||
      method_source (~> 0.8.1)
 | 
			
		||||
      slop (~> 3.4)
 | 
			
		||||
    pry-byebug (3.3.0)
 | 
			
		||||
      byebug (~> 8.0)
 | 
			
		||||
      pry (~> 0.10)
 | 
			
		||||
    pry-nav (0.2.4)
 | 
			
		||||
      pry (>= 0.9.10, < 0.11.0)
 | 
			
		||||
    rack (1.6.4)
 | 
			
		||||
    rack-openid (1.4.2)
 | 
			
		||||
      rack (>= 1.1.0)
 | 
			
		||||
      ruby-openid (>= 2.1.8)
 | 
			
		||||
    rack-test (0.6.3)
 | 
			
		||||
      rack (>= 1.0)
 | 
			
		||||
    rails (4.2.6)
 | 
			
		||||
      actionmailer (= 4.2.6)
 | 
			
		||||
      actionpack (= 4.2.6)
 | 
			
		||||
      actionview (= 4.2.6)
 | 
			
		||||
      activejob (= 4.2.6)
 | 
			
		||||
      activemodel (= 4.2.6)
 | 
			
		||||
      activerecord (= 4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      bundler (>= 1.3.0, < 2.0)
 | 
			
		||||
      railties (= 4.2.6)
 | 
			
		||||
      sprockets-rails
 | 
			
		||||
    rails-deprecated_sanitizer (1.0.3)
 | 
			
		||||
      activesupport (>= 4.2.0.alpha)
 | 
			
		||||
    rails-dom-testing (1.0.7)
 | 
			
		||||
      activesupport (>= 4.2.0.beta, < 5.0)
 | 
			
		||||
      nokogiri (~> 1.6.0)
 | 
			
		||||
      rails-deprecated_sanitizer (>= 1.0.1)
 | 
			
		||||
    rails-html-sanitizer (1.0.3)
 | 
			
		||||
      loofah (~> 2.0)
 | 
			
		||||
    railties (4.2.6)
 | 
			
		||||
      actionpack (= 4.2.6)
 | 
			
		||||
      activesupport (= 4.2.6)
 | 
			
		||||
      rake (>= 0.8.7)
 | 
			
		||||
      thor (>= 0.18.1, < 2.0)
 | 
			
		||||
    rake (11.1.2)
 | 
			
		||||
    rbpdf (1.19.0)
 | 
			
		||||
      htmlentities (= 4.3.1)
 | 
			
		||||
      rbpdf-font (~> 1.19.0)
 | 
			
		||||
    rbpdf-font (1.19.0)
 | 
			
		||||
    rdoc (4.2.2)
 | 
			
		||||
      json (~> 1.4)
 | 
			
		||||
    redcarpet (3.3.4)
 | 
			
		||||
    request_store (1.0.5)
 | 
			
		||||
    rmagick (2.15.4)
 | 
			
		||||
    roadie (3.1.1)
 | 
			
		||||
      css_parser (~> 1.3.4)
 | 
			
		||||
      nokogiri (>= 1.5.0, < 1.7.0)
 | 
			
		||||
    roadie-rails (1.1.1)
 | 
			
		||||
      railties (>= 3.0, < 5.1)
 | 
			
		||||
      roadie (~> 3.1)
 | 
			
		||||
    rspec (3.4.0)
 | 
			
		||||
      rspec-core (~> 3.4.0)
 | 
			
		||||
      rspec-expectations (~> 3.4.0)
 | 
			
		||||
      rspec-mocks (~> 3.4.0)
 | 
			
		||||
    rspec-core (3.4.4)
 | 
			
		||||
      rspec-support (~> 3.4.0)
 | 
			
		||||
    rspec-expectations (3.4.0)
 | 
			
		||||
      diff-lcs (>= 1.2.0, < 2.0)
 | 
			
		||||
      rspec-support (~> 3.4.0)
 | 
			
		||||
    rspec-mocks (3.4.1)
 | 
			
		||||
      diff-lcs (>= 1.2.0, < 2.0)
 | 
			
		||||
      rspec-support (~> 3.4.0)
 | 
			
		||||
    rspec-rails (3.4.2)
 | 
			
		||||
      actionpack (>= 3.0, < 4.3)
 | 
			
		||||
      activesupport (>= 3.0, < 4.3)
 | 
			
		||||
      railties (>= 3.0, < 4.3)
 | 
			
		||||
      rspec-core (~> 3.4.0)
 | 
			
		||||
      rspec-expectations (~> 3.4.0)
 | 
			
		||||
      rspec-mocks (~> 3.4.0)
 | 
			
		||||
      rspec-support (~> 3.4.0)
 | 
			
		||||
    rspec-support (3.4.1)
 | 
			
		||||
    ruby-openid (2.3.0)
 | 
			
		||||
    ruby-progressbar (1.7.5)
 | 
			
		||||
    rubyzip (1.2.0)
 | 
			
		||||
    sass (3.4.22)
 | 
			
		||||
    selenium-webdriver (2.53.0)
 | 
			
		||||
      childprocess (~> 0.5)
 | 
			
		||||
      rubyzip (~> 1.0)
 | 
			
		||||
      websocket (~> 1.0)
 | 
			
		||||
    simplecov (0.9.2)
 | 
			
		||||
      docile (~> 1.1.0)
 | 
			
		||||
      multi_json (~> 1.0)
 | 
			
		||||
      simplecov-html (~> 0.9.0)
 | 
			
		||||
    simplecov-html (0.9.0)
 | 
			
		||||
    simplecov-rcov (0.2.3)
 | 
			
		||||
      simplecov (>= 0.4.1)
 | 
			
		||||
    slim (3.0.6)
 | 
			
		||||
      temple (~> 0.7.3)
 | 
			
		||||
      tilt (>= 1.3.3, < 2.1)
 | 
			
		||||
    slop (3.6.0)
 | 
			
		||||
    sprockets (3.6.0)
 | 
			
		||||
      concurrent-ruby (~> 1.0)
 | 
			
		||||
      rack (> 1, < 3)
 | 
			
		||||
    sprockets-rails (3.0.4)
 | 
			
		||||
      actionpack (>= 4.0)
 | 
			
		||||
      activesupport (>= 4.0)
 | 
			
		||||
      sprockets (>= 3.0.0)
 | 
			
		||||
    sshkit (1.9.0)
 | 
			
		||||
      net-scp (>= 1.1.2)
 | 
			
		||||
      net-ssh (>= 2.8.0)
 | 
			
		||||
    temple (0.7.6)
 | 
			
		||||
    test-unit (3.1.8)
 | 
			
		||||
      power_assert
 | 
			
		||||
    thin (1.6.4)
 | 
			
		||||
      daemons (~> 1.0, >= 1.0.9)
 | 
			
		||||
      eventmachine (~> 1.0, >= 1.0.4)
 | 
			
		||||
      rack (~> 1.0)
 | 
			
		||||
    thor (0.19.1)
 | 
			
		||||
    thread_safe (0.3.5)
 | 
			
		||||
    tilt (2.0.2)
 | 
			
		||||
    transifex-ruby-fork-jg (0.1.0)
 | 
			
		||||
      faraday (~> 0.8.0)
 | 
			
		||||
      faraday_middleware (~> 0.9.0)
 | 
			
		||||
      hashie (~> 1.2.0)
 | 
			
		||||
    tzinfo (1.2.2)
 | 
			
		||||
      thread_safe (~> 0.1)
 | 
			
		||||
    websocket (1.2.3)
 | 
			
		||||
    xpath (2.0.0)
 | 
			
		||||
      nokogiri (~> 1.3)
 | 
			
		||||
    yard (0.8.7.6)
 | 
			
		||||
 | 
			
		||||
PLATFORMS
 | 
			
		||||
  ruby
 | 
			
		||||
 | 
			
		||||
DEPENDENCIES
 | 
			
		||||
  actionpack-action_caching
 | 
			
		||||
  actionpack-xml_parser
 | 
			
		||||
  activerecord-jdbc-adapter (~> 1.3.2)
 | 
			
		||||
  activerecord-jdbcpostgresql-adapter
 | 
			
		||||
  bourbon
 | 
			
		||||
  builder (>= 3.0.4)
 | 
			
		||||
  capistrano (~> 3.1)
 | 
			
		||||
  capistrano-bundler (~> 1.1.2)
 | 
			
		||||
  capistrano-passenger
 | 
			
		||||
  capistrano-rails (~> 1.1)
 | 
			
		||||
  capybara
 | 
			
		||||
  coderay (~> 1.1.0)
 | 
			
		||||
  database_cleaner
 | 
			
		||||
  fuubar
 | 
			
		||||
  gemoji (= 1.5.0)
 | 
			
		||||
  headless
 | 
			
		||||
  inifile
 | 
			
		||||
  jquery-rails (~> 3.1.4)
 | 
			
		||||
  le
 | 
			
		||||
  mime-types (~> 3.0)
 | 
			
		||||
  minitest
 | 
			
		||||
  mocha
 | 
			
		||||
  net-ldap (~> 0.12.0)
 | 
			
		||||
  nokogiri (>= 1.6.7.2)
 | 
			
		||||
  pg (~> 0.18.1)
 | 
			
		||||
  protected_attributes
 | 
			
		||||
  pry
 | 
			
		||||
  pry-byebug
 | 
			
		||||
  pry-nav
 | 
			
		||||
  rack-openid
 | 
			
		||||
  rails (= 4.2.6)
 | 
			
		||||
  rails-dom-testing
 | 
			
		||||
  rails-html-sanitizer (>= 1.0.3)
 | 
			
		||||
  rbpdf (~> 1.19.0)
 | 
			
		||||
  rdoc (>= 2.4.2)
 | 
			
		||||
  redcarpet (~> 3.3.2)
 | 
			
		||||
  request_store (= 1.0.5)
 | 
			
		||||
  rmagick (>= 2.14.0)
 | 
			
		||||
  roadie-rails
 | 
			
		||||
  rspec (~> 3.0)
 | 
			
		||||
  rspec-rails
 | 
			
		||||
  ruby-openid (~> 2.3.0)
 | 
			
		||||
  sass
 | 
			
		||||
  selenium-webdriver
 | 
			
		||||
  simplecov (~> 0.9.1)
 | 
			
		||||
  simplecov-rcov
 | 
			
		||||
  slim
 | 
			
		||||
  test-unit
 | 
			
		||||
  thin
 | 
			
		||||
  transifex-ruby-fork-jg (= 0.1.0)
 | 
			
		||||
  tzinfo-data
 | 
			
		||||
  yard
 | 
			
		||||
 | 
			
		||||
BUNDLED WITH
 | 
			
		||||
   1.11.2
 | 
			
		||||
							
								
								
									
										650
									
								
								integration/data/lockfile/Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										650
									
								
								integration/data/lockfile/Pipfile.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,650 @@
 | 
			
		||||
{
 | 
			
		||||
    "_meta": {
 | 
			
		||||
        "hash": {
 | 
			
		||||
            "sha256": "947e36f68d4acdd1ec855ae6f4a38c54c59773bf89725674a97dc4d5d4f512ca"
 | 
			
		||||
        },
 | 
			
		||||
        "pipfile-spec": 6,
 | 
			
		||||
        "requires": {
 | 
			
		||||
            "python_version": "3.9"
 | 
			
		||||
        },
 | 
			
		||||
        "sources": [
 | 
			
		||||
            {
 | 
			
		||||
                "name": "pypi",
 | 
			
		||||
                "url": "https://pypi.org/simple",
 | 
			
		||||
                "verify_ssl": true
 | 
			
		||||
            }
 | 
			
		||||
        ]
 | 
			
		||||
    },
 | 
			
		||||
    "default": {
 | 
			
		||||
        "babel": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5",
 | 
			
		||||
                "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.9.0"
 | 
			
		||||
        },
 | 
			
		||||
        "certifi": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
 | 
			
		||||
                "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2020.12.5"
 | 
			
		||||
        },
 | 
			
		||||
        "chardet": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
 | 
			
		||||
                "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==4.0.0"
 | 
			
		||||
        },
 | 
			
		||||
        "click": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
 | 
			
		||||
                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==7.1.2"
 | 
			
		||||
        },
 | 
			
		||||
        "flask": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:4efa1ae2d7c9865af48986de8aeb8504bf32c7f3d6fdc9353d34b21f4b127060",
 | 
			
		||||
                "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.1.2"
 | 
			
		||||
        },
 | 
			
		||||
        "flask-talisman": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:468131464a249274ed226efc21b372518f442487e58918ccab8357eaa638fd1f",
 | 
			
		||||
                "sha256:eaa754f4b771dfbe473843391d69643b79e3a38c865790011ac5e4179c68e3ec"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.7.0"
 | 
			
		||||
        },
 | 
			
		||||
        "gunicorn": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626",
 | 
			
		||||
                "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==20.0.4"
 | 
			
		||||
        },
 | 
			
		||||
        "idna": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
 | 
			
		||||
                "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==2.10"
 | 
			
		||||
        },
 | 
			
		||||
        "itsdangerous": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19",
 | 
			
		||||
                "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "jinja2": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419",
 | 
			
		||||
                "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==0.11.3"
 | 
			
		||||
        },
 | 
			
		||||
        "markupsafe": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473",
 | 
			
		||||
                "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161",
 | 
			
		||||
                "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235",
 | 
			
		||||
                "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5",
 | 
			
		||||
                "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42",
 | 
			
		||||
                "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f",
 | 
			
		||||
                "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39",
 | 
			
		||||
                "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff",
 | 
			
		||||
                "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b",
 | 
			
		||||
                "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014",
 | 
			
		||||
                "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f",
 | 
			
		||||
                "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1",
 | 
			
		||||
                "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e",
 | 
			
		||||
                "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183",
 | 
			
		||||
                "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66",
 | 
			
		||||
                "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b",
 | 
			
		||||
                "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1",
 | 
			
		||||
                "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15",
 | 
			
		||||
                "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1",
 | 
			
		||||
                "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85",
 | 
			
		||||
                "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1",
 | 
			
		||||
                "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e",
 | 
			
		||||
                "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b",
 | 
			
		||||
                "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905",
 | 
			
		||||
                "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850",
 | 
			
		||||
                "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0",
 | 
			
		||||
                "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735",
 | 
			
		||||
                "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d",
 | 
			
		||||
                "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb",
 | 
			
		||||
                "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e",
 | 
			
		||||
                "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d",
 | 
			
		||||
                "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c",
 | 
			
		||||
                "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1",
 | 
			
		||||
                "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2",
 | 
			
		||||
                "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21",
 | 
			
		||||
                "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2",
 | 
			
		||||
                "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5",
 | 
			
		||||
                "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7",
 | 
			
		||||
                "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b",
 | 
			
		||||
                "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8",
 | 
			
		||||
                "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6",
 | 
			
		||||
                "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193",
 | 
			
		||||
                "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f",
 | 
			
		||||
                "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b",
 | 
			
		||||
                "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f",
 | 
			
		||||
                "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2",
 | 
			
		||||
                "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5",
 | 
			
		||||
                "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c",
 | 
			
		||||
                "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032",
 | 
			
		||||
                "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7",
 | 
			
		||||
                "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be",
 | 
			
		||||
                "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.1.1"
 | 
			
		||||
        },
 | 
			
		||||
        "omise": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:15d5f0ae466d6d5fda7d53f99fd92c08be86d3b4e8162ae7e75ff2246e35d57c",
 | 
			
		||||
                "sha256:d4fa58da2aae4e08ece622db8b27fe24158a7ecb2d50acf90b5496d7bdd3a73f"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.11.0"
 | 
			
		||||
        },
 | 
			
		||||
        "py-money": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:6c0f3597022a7d16fe65273c046614b7f30dd63aa0a0765ac7044092e2959014",
 | 
			
		||||
                "sha256:e2ba7fe399a2986913753735874063c5cb816941bba737db7ec1353a04321338"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.5.0"
 | 
			
		||||
        },
 | 
			
		||||
        "python-dotenv": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e",
 | 
			
		||||
                "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.15.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pytz": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
 | 
			
		||||
                "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2021.1"
 | 
			
		||||
        },
 | 
			
		||||
        "requests": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
 | 
			
		||||
                "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==2.25.1"
 | 
			
		||||
        },
 | 
			
		||||
        "six": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
 | 
			
		||||
                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.15.0"
 | 
			
		||||
        },
 | 
			
		||||
        "urllib3": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80",
 | 
			
		||||
                "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
 | 
			
		||||
            "version": "==0.26.3"
 | 
			
		||||
        },
 | 
			
		||||
        "werkzeug": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43",
 | 
			
		||||
                "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==1.0.1"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    "develop": {
 | 
			
		||||
        "appdirs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41",
 | 
			
		||||
                "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.4.4"
 | 
			
		||||
        },
 | 
			
		||||
        "astroid": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
 | 
			
		||||
                "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.5'",
 | 
			
		||||
            "version": "==2.4.2"
 | 
			
		||||
        },
 | 
			
		||||
        "attrs": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
 | 
			
		||||
                "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==20.3.0"
 | 
			
		||||
        },
 | 
			
		||||
        "autopep8": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9e136c472c475f4ee4978b51a88a494bfcd4e3ed17950a44a988d9e434837bea",
 | 
			
		||||
                "sha256:cae4bc0fb616408191af41d062d7ec7ef8679c7f27b068875ca3a9e2878d5443"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==1.5.5"
 | 
			
		||||
        },
 | 
			
		||||
        "black": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6'",
 | 
			
		||||
            "version": "==20.8b1"
 | 
			
		||||
        },
 | 
			
		||||
        "click": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
 | 
			
		||||
                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
 | 
			
		||||
            "version": "==7.1.2"
 | 
			
		||||
        },
 | 
			
		||||
        "flake8": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839",
 | 
			
		||||
                "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==3.8.4"
 | 
			
		||||
        },
 | 
			
		||||
        "iniconfig": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3",
 | 
			
		||||
                "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.1.1"
 | 
			
		||||
        },
 | 
			
		||||
        "isort": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:c729845434366216d320e936b8ad6f9d681aab72dc7cbc2d51bedc3582f3ad1e",
 | 
			
		||||
                "sha256:fff4f0c04e1825522ce6949973e83110a6e907750cd92d128b0d14aaaadbffdc"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.6' and python_version < '4.0'",
 | 
			
		||||
            "version": "==5.7.0"
 | 
			
		||||
        },
 | 
			
		||||
        "jedi": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20",
 | 
			
		||||
                "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.17.2"
 | 
			
		||||
        },
 | 
			
		||||
        "lazy-object-proxy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
 | 
			
		||||
                "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
 | 
			
		||||
                "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
 | 
			
		||||
                "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
 | 
			
		||||
                "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
 | 
			
		||||
                "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
 | 
			
		||||
                "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
 | 
			
		||||
                "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
 | 
			
		||||
                "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
 | 
			
		||||
                "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
 | 
			
		||||
                "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
 | 
			
		||||
                "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
 | 
			
		||||
                "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
 | 
			
		||||
                "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
 | 
			
		||||
                "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
 | 
			
		||||
                "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
 | 
			
		||||
                "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
 | 
			
		||||
                "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
 | 
			
		||||
                "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
 | 
			
		||||
                "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
 | 
			
		||||
                "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "mccabe": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
 | 
			
		||||
                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.6.1"
 | 
			
		||||
        },
 | 
			
		||||
        "mypy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0d2fc8beb99cd88f2d7e20d69131353053fbecea17904ee6f0348759302c52fa",
 | 
			
		||||
                "sha256:2b216eacca0ec0ee124af9429bfd858d5619a0725ee5f88057e6e076f9eb1a7b",
 | 
			
		||||
                "sha256:319ee5c248a7c3f94477f92a729b7ab06bf8a6d04447ef3aa8c9ba2aa47c6dcf",
 | 
			
		||||
                "sha256:3e0c159a7853e3521e3f582adb1f3eac66d0b0639d434278e2867af3a8c62653",
 | 
			
		||||
                "sha256:5615785d3e2f4f03ab7697983d82c4b98af5c321614f51b8f1034eb9ebe48363",
 | 
			
		||||
                "sha256:5ff616787122774f510caeb7b980542a7cc2222be3f00837a304ea85cd56e488",
 | 
			
		||||
                "sha256:6f8425fecd2ba6007e526209bb985ce7f49ed0d2ac1cc1a44f243380a06a84fb",
 | 
			
		||||
                "sha256:74f5aa50d0866bc6fb8e213441c41e466c86678c800700b87b012ed11c0a13e0",
 | 
			
		||||
                "sha256:90b6f46dc2181d74f80617deca611925d7e63007cf416397358aa42efb593e07",
 | 
			
		||||
                "sha256:947126195bfe4709c360e89b40114c6746ae248f04d379dca6f6ab677aa07641",
 | 
			
		||||
                "sha256:a301da58d566aca05f8f449403c710c50a9860782148332322decf73a603280b",
 | 
			
		||||
                "sha256:aa9d4901f3ee1a986a3a79fe079ffbf7f999478c281376f48faa31daaa814e86",
 | 
			
		||||
                "sha256:b9150db14a48a8fa114189bfe49baccdff89da8c6639c2717750c7ae62316738",
 | 
			
		||||
                "sha256:b95068a3ce3b50332c40e31a955653be245666a4bc7819d3c8898aa9fb9ea496",
 | 
			
		||||
                "sha256:ca7ad5aed210841f1e77f5f2f7d725b62c78fa77519312042c719ed2ab937876",
 | 
			
		||||
                "sha256:d16c54b0dffb861dc6318a8730952265876d90c5101085a4bc56913e8521ba19",
 | 
			
		||||
                "sha256:e0202e37756ed09daf4b0ba64ad2c245d357659e014c3f51d8cd0681ba66940a",
 | 
			
		||||
                "sha256:e1c84c65ff6d69fb42958ece5b1255394714e0aac4df5ffe151bc4fe19c7600a",
 | 
			
		||||
                "sha256:e32b7b282c4ed4e378bba8b8dfa08e1cfa6f6574067ef22f86bee5b1039de0c9",
 | 
			
		||||
                "sha256:e3b8432f8df19e3c11235c4563a7250666dc9aa7cdda58d21b4177b20256ca9f",
 | 
			
		||||
                "sha256:e497a544391f733eca922fdcb326d19e894789cd4ff61d48b4b195776476c5cf",
 | 
			
		||||
                "sha256:f5fdf935a46aa20aa937f2478480ebf4be9186e98e49cc3843af9a5795a49a25"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.5'",
 | 
			
		||||
            "version": "==0.800"
 | 
			
		||||
        },
 | 
			
		||||
        "mypy-extensions": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
 | 
			
		||||
                "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "packaging": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
 | 
			
		||||
                "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==20.9"
 | 
			
		||||
        },
 | 
			
		||||
        "parso": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea",
 | 
			
		||||
                "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.7.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pathspec": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd",
 | 
			
		||||
                "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.8.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pluggy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
 | 
			
		||||
                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.13.1"
 | 
			
		||||
        },
 | 
			
		||||
        "py": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
 | 
			
		||||
                "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.10.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pycodestyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367",
 | 
			
		||||
                "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.6.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pydocstyle": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:19b86fa8617ed916776a11cd8bc0197e5b9856d5433b777f51a3defe13075325",
 | 
			
		||||
                "sha256:aca749e190a01726a4fb472dd4ef23b5c9da7b9205c0a7857c06533de13fd678"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==5.1.1"
 | 
			
		||||
        },
 | 
			
		||||
        "pyflakes": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92",
 | 
			
		||||
                "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pylint": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:bb4a908c9dadbc3aac18860550e870f58e1a02c9f2c204fdf5693d73be061210",
 | 
			
		||||
                "sha256:bfe68f020f8a0fece830a22dd4d5dddb4ecc6137db04face4c3420a46a52239f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.6.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pyls-black": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:33700e5ed605636ea7ba39188a1362d2f8602f7301f8f2b8544773886f965663",
 | 
			
		||||
                "sha256:8f5fb8fed503588c10435d2d48e2c3751437f1bdb8116134b05a4591c4899940"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.4.6"
 | 
			
		||||
        },
 | 
			
		||||
        "pyls-isort": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:a6c292332746d3dc690f2a3dcdb9a01d913b9ee8444defe3cbffcddb7e3874eb"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.2.0"
 | 
			
		||||
        },
 | 
			
		||||
        "pyls-mypy": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3fd83028961f0ca9eb3048b7a01cf42a9e3d46d8ea4935c1424c33da22c3eb03"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.1.8"
 | 
			
		||||
        },
 | 
			
		||||
        "pyparsing": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
 | 
			
		||||
                "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==2.4.7"
 | 
			
		||||
        },
 | 
			
		||||
        "pytest": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
 | 
			
		||||
                "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==6.2.2"
 | 
			
		||||
        },
 | 
			
		||||
        "python-jsonrpc-server": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:62c543e541f101ec5b57dc654efc212d2c2e3ea47ff6f54b2e7dcb36ecf20595",
 | 
			
		||||
                "sha256:e5a908ff182e620aac07db5f57887eeb0afe33993008f57dc1b85b594cea250c"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.4.0"
 | 
			
		||||
        },
 | 
			
		||||
        "python-language-server": {
 | 
			
		||||
            "extras": [
 | 
			
		||||
                "all"
 | 
			
		||||
            ],
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:9984c84a67ee2c5102c8e703215f407fcfa5e62b0ae86c9572d0ada8c4b417b0",
 | 
			
		||||
                "sha256:a0ad0aca03f4a20c1c40f4f230c6773eac82c9b7cdb026cb09ba10237f4815d5"
 | 
			
		||||
            ],
 | 
			
		||||
            "index": "pypi",
 | 
			
		||||
            "version": "==0.36.2"
 | 
			
		||||
        },
 | 
			
		||||
        "regex": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538",
 | 
			
		||||
                "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4",
 | 
			
		||||
                "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc",
 | 
			
		||||
                "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa",
 | 
			
		||||
                "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444",
 | 
			
		||||
                "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1",
 | 
			
		||||
                "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af",
 | 
			
		||||
                "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8",
 | 
			
		||||
                "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9",
 | 
			
		||||
                "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88",
 | 
			
		||||
                "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba",
 | 
			
		||||
                "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364",
 | 
			
		||||
                "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e",
 | 
			
		||||
                "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7",
 | 
			
		||||
                "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0",
 | 
			
		||||
                "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31",
 | 
			
		||||
                "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683",
 | 
			
		||||
                "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee",
 | 
			
		||||
                "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b",
 | 
			
		||||
                "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884",
 | 
			
		||||
                "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c",
 | 
			
		||||
                "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e",
 | 
			
		||||
                "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562",
 | 
			
		||||
                "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85",
 | 
			
		||||
                "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c",
 | 
			
		||||
                "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6",
 | 
			
		||||
                "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d",
 | 
			
		||||
                "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b",
 | 
			
		||||
                "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70",
 | 
			
		||||
                "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b",
 | 
			
		||||
                "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b",
 | 
			
		||||
                "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f",
 | 
			
		||||
                "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0",
 | 
			
		||||
                "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5",
 | 
			
		||||
                "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5",
 | 
			
		||||
                "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f",
 | 
			
		||||
                "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e",
 | 
			
		||||
                "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512",
 | 
			
		||||
                "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d",
 | 
			
		||||
                "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917",
 | 
			
		||||
                "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2020.11.13"
 | 
			
		||||
        },
 | 
			
		||||
        "rope": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:786b5c38c530d4846aa68a42604f61b4e69a493390e3ca11b88df0fbfdc3ed04"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.18.0"
 | 
			
		||||
        },
 | 
			
		||||
        "six": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
 | 
			
		||||
                "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==1.15.0"
 | 
			
		||||
        },
 | 
			
		||||
        "snowballstemmer": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2",
 | 
			
		||||
                "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==2.1.0"
 | 
			
		||||
        },
 | 
			
		||||
        "toml": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
 | 
			
		||||
                "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
 | 
			
		||||
            "version": "==0.10.2"
 | 
			
		||||
        },
 | 
			
		||||
        "typed-ast": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1",
 | 
			
		||||
                "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d",
 | 
			
		||||
                "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6",
 | 
			
		||||
                "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd",
 | 
			
		||||
                "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37",
 | 
			
		||||
                "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151",
 | 
			
		||||
                "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07",
 | 
			
		||||
                "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440",
 | 
			
		||||
                "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70",
 | 
			
		||||
                "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496",
 | 
			
		||||
                "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea",
 | 
			
		||||
                "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400",
 | 
			
		||||
                "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc",
 | 
			
		||||
                "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606",
 | 
			
		||||
                "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc",
 | 
			
		||||
                "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581",
 | 
			
		||||
                "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412",
 | 
			
		||||
                "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a",
 | 
			
		||||
                "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2",
 | 
			
		||||
                "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787",
 | 
			
		||||
                "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f",
 | 
			
		||||
                "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937",
 | 
			
		||||
                "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64",
 | 
			
		||||
                "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487",
 | 
			
		||||
                "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b",
 | 
			
		||||
                "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41",
 | 
			
		||||
                "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a",
 | 
			
		||||
                "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3",
 | 
			
		||||
                "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166",
 | 
			
		||||
                "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version < '3.8' and implementation_name == 'cpython'",
 | 
			
		||||
            "version": "==1.4.2"
 | 
			
		||||
        },
 | 
			
		||||
        "typing-extensions": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918",
 | 
			
		||||
                "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c",
 | 
			
		||||
                "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version < '3.8'",
 | 
			
		||||
            "version": "==3.7.4.3"
 | 
			
		||||
        },
 | 
			
		||||
        "ujson": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:0190d26c0e990c17ad072ec8593647218fe1c675d11089cd3d1440175b568967",
 | 
			
		||||
                "sha256:0ea07fe57f9157118ca689e7f6db72759395b99121c0ff038d2e38649c626fb1",
 | 
			
		||||
                "sha256:30962467c36ff6de6161d784cd2a6aac1097f0128b522d6e9291678e34fb2b47",
 | 
			
		||||
                "sha256:4d6d061563470cac889c0a9fd367013a5dbd8efc36ad01ab3e67a57e56cad720",
 | 
			
		||||
                "sha256:5e1636b94c7f1f59a8ead4c8a7bab1b12cc52d4c21ababa295ffec56b445fd2a",
 | 
			
		||||
                "sha256:7333e8bc45ea28c74ae26157eacaed5e5629dbada32e0103c23eb368f93af108",
 | 
			
		||||
                "sha256:84b1dca0d53b0a8d58835f72ea2894e4d6cf7a5dd8f520ab4cbd698c81e49737",
 | 
			
		||||
                "sha256:91396a585ba51f84dc71c8da60cdc86de6b60ba0272c389b6482020a1fac9394",
 | 
			
		||||
                "sha256:a214ba5a21dad71a43c0f5aef917cd56a2d70bc974d845be211c66b6742a471c",
 | 
			
		||||
                "sha256:aad6d92f4d71e37ea70e966500f1951ecd065edca3a70d3861b37b176dd6702c",
 | 
			
		||||
                "sha256:b3a6dcc660220539aa718bcc9dbd6dedf2a01d19c875d1033f028f212e36d6bb",
 | 
			
		||||
                "sha256:b5c70704962cf93ec6ea3271a47d952b75ae1980d6c56b8496cec2a722075939",
 | 
			
		||||
                "sha256:c615a9e9e378a7383b756b7e7a73c38b22aeb8967a8bfbffd4741f7ffd043c4d",
 | 
			
		||||
                "sha256:d3a87888c40b5bfcf69b4030427cd666893e826e82cc8608d1ba8b4b5e04ea99",
 | 
			
		||||
                "sha256:e2cadeb0ddc98e3963bea266cc5b884e5d77d73adf807f0bda9eca64d1c509d5",
 | 
			
		||||
                "sha256:e390df0dcc7897ffb98e17eae1f4c442c39c91814c298ad84d935a3c5c7a32fa",
 | 
			
		||||
                "sha256:e6e90330670c78e727d6637bb5a215d3e093d8e3570d439fd4922942f88da361",
 | 
			
		||||
                "sha256:eb6b25a7670c7537a5998e695fa62ff13c7f9c33faf82927adf4daa460d5f62e",
 | 
			
		||||
                "sha256:f273a875c0b42c2a019c337631bc1907f6fdfbc84210cc0d1fff0e2019bbfaec",
 | 
			
		||||
                "sha256:f8aded54c2bc554ce20b397f72101737dd61ee7b81c771684a7dd7805e6cca0c",
 | 
			
		||||
                "sha256:fc51e545d65689c398161f07fd405104956ec27f22453de85898fa088b2cd4bb"
 | 
			
		||||
            ],
 | 
			
		||||
            "markers": "python_version >= '3.1'",
 | 
			
		||||
            "version": "==4.0.2"
 | 
			
		||||
        },
 | 
			
		||||
        "wrapt": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==1.12.1"
 | 
			
		||||
        },
 | 
			
		||||
        "yapf": {
 | 
			
		||||
            "hashes": [
 | 
			
		||||
                "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427",
 | 
			
		||||
                "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"
 | 
			
		||||
            ],
 | 
			
		||||
            "version": "==0.30.0"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6229
									
								
								integration/data/lockfile/composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6229
									
								
								integration/data/lockfile/composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										707
									
								
								integration/data/lockfile/go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										707
									
								
								integration/data/lockfile/go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,707 @@
 | 
			
		||||
 | 
			
		||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 | 
			
		||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
 | 
			
		||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
 | 
			
		||||
cloud.google.com/go v0.40.0/go.mod h1:Tk58MuI9rbLMKlAjeO/bDnteAx7tX2gJIXw4T5Jwlro=
 | 
			
		||||
cloud.google.com/go v0.41.0/go.mod h1:OauMR7DV8fzvZIl2qg6rkaIhD/vmgk4iwEw/h6ercmg=
 | 
			
		||||
cloud.google.com/go v0.42.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
 | 
			
		||||
cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg=
 | 
			
		||||
cloud.google.com/go v0.44.0/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 | 
			
		||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
 | 
			
		||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 | 
			
		||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
 | 
			
		||||
cloud.google.com/go v0.45.0/go.mod h1:452BcPOeI9AZfbvDw0Tbo7D32wA+WX9WME8AZwMEDZU=
 | 
			
		||||
cloud.google.com/go/bigquery v1.0.0/go.mod h1:W6nZUO55RX1ze8f54muIveLNA7ouiqcTlNELudKtFaM=
 | 
			
		||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 | 
			
		||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
 | 
			
		||||
code.gitea.io/gitea v1.9.0-dev/go.mod h1:wWyKwhnrzHgqiqguunHKA6yzZXYsLSC7V6WvI+GlOx8=
 | 
			
		||||
code.gitea.io/gitea v1.9.0-rc1/go.mod h1:WJbOBnfoAP54J4mP5ylCEKYxytCh8SMZBeSOBdcZBkw=
 | 
			
		||||
code.gitea.io/gitea v1.9.0-rc2/go.mod h1:3yZ+sXUqEshMeUwfr8bB3SvttSBcstgk2zXgePfDx4Y=
 | 
			
		||||
code.gitea.io/gitea v1.9.0/go.mod h1:HzXskRRacnLWs4z/B6Bt6gFpCl6cicdHM0GfZMTEmtI=
 | 
			
		||||
code.gitea.io/gitea v1.9.1/go.mod h1:HzXskRRacnLWs4z/B6Bt6gFpCl6cicdHM0GfZMTEmtI=
 | 
			
		||||
code.gitea.io/gitea v1.9.2/go.mod h1:HzXskRRacnLWs4z/B6Bt6gFpCl6cicdHM0GfZMTEmtI=
 | 
			
		||||
code.gitea.io/gitea v1.9.3/go.mod h1:HzXskRRacnLWs4z/B6Bt6gFpCl6cicdHM0GfZMTEmtI=
 | 
			
		||||
code.gitea.io/gitea v1.9.4/go.mod h1:nwqMi+nJMcJC7r+SdGt5RdDNLFkWwHZ+GpLKV13WifE=
 | 
			
		||||
code.gitea.io/gitea v1.9.5/go.mod h1:nwqMi+nJMcJC7r+SdGt5RdDNLFkWwHZ+GpLKV13WifE=
 | 
			
		||||
code.gitea.io/gitea v1.9.6/go.mod h1:mkxMeXN4KE+t6JLCNzKaFrM8SOOWZusNcuG3p5RI+f4=
 | 
			
		||||
code.gitea.io/gitea v1.10.0-dev/go.mod h1:WJbOBnfoAP54J4mP5ylCEKYxytCh8SMZBeSOBdcZBkw=
 | 
			
		||||
code.gitea.io/gitea v1.10.0-rc1/go.mod h1:Z/ysRJuQTNdT5BysAUhfPcKU7cv4X9h1qFrFN359cgw=
 | 
			
		||||
code.gitea.io/gitea v1.10.0-rc2/go.mod h1:Z/ysRJuQTNdT5BysAUhfPcKU7cv4X9h1qFrFN359cgw=
 | 
			
		||||
code.gitea.io/gitea v1.10.0/go.mod h1:Z/ysRJuQTNdT5BysAUhfPcKU7cv4X9h1qFrFN359cgw=
 | 
			
		||||
code.gitea.io/gitea v1.10.1/go.mod h1:DIJZcrFaYaSmWR2f2eSKO6j2n1mPSD2zVO7A/tdWxbM=
 | 
			
		||||
code.gitea.io/gitea v1.10.2/go.mod h1:DIJZcrFaYaSmWR2f2eSKO6j2n1mPSD2zVO7A/tdWxbM=
 | 
			
		||||
code.gitea.io/gitea v1.10.3/go.mod h1:DIJZcrFaYaSmWR2f2eSKO6j2n1mPSD2zVO7A/tdWxbM=
 | 
			
		||||
gitea.com/lunny/levelqueue v0.1.0/go.mod h1:G7hVb908t0Bl0uk7zGSg14fyzNtxgtD9Shf04wkMK7s=
 | 
			
		||||
gitea.com/macaron/binding v0.0.0-20190822013154-a5f53841ed2b/go.mod h1:Cxadig6POWpPYYSfg23E7jo35Yf0yvsdC1lifoKWmPo=
 | 
			
		||||
gitea.com/macaron/cache v0.0.0-20190822004001-a6e7fee4ee76/go.mod h1:NFHb9Of+LUnU86bU20CiXXg6ZlgCJ4XytP14UsHOXFs=
 | 
			
		||||
gitea.com/macaron/captcha v0.0.0-20190822015246-daa973478bae/go.mod h1:J5h3N+1nKTXtU1x4GxexaQKgAz8UiWecNwi/CfX7CtQ=
 | 
			
		||||
gitea.com/macaron/cors v0.0.0-20190821152825-7dcef4a17175/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A=
 | 
			
		||||
gitea.com/macaron/cors v0.0.0-20190826180238-95aec09ea8b4/go.mod h1:rtOK4J20kpMD9XcNsnO5YA843YSTe/MUMbDj/TJ/Q7A=
 | 
			
		||||
gitea.com/macaron/csrf v0.0.0-20190822024205-3dc5a4474439/go.mod h1:IsQPHx73HnnqFBYiVHjg87q4XBZyGXXu77xANukvZuk=
 | 
			
		||||
gitea.com/macaron/i18n v0.0.0-20190822004228-474e714e2223/go.mod h1:+qsc10s4hBsHKU/9luGGumFh4m5FFVc7uih+8/mM1NY=
 | 
			
		||||
gitea.com/macaron/inject v0.0.0-20190803172902-8375ba841591/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
 | 
			
		||||
gitea.com/macaron/inject v0.0.0-20190805023432-d4c86e31027a/go.mod h1:h6E4kLao1Yko6DOU6QDnQPcuoNzvbZqzj2mtPcEn1aM=
 | 
			
		||||
gitea.com/macaron/macaron v1.3.2/go.mod h1:x30d38SbJFBUEO2Mgz7loekCzr87U9UaUDNbSAOxg5k=
 | 
			
		||||
gitea.com/macaron/macaron v1.3.3-0.20190803174002-53e005ff4827/go.mod h1:/rvxMjIkOq4BM8uPUb+VHuU02ZfAO6R4+wD//tiCiRw=
 | 
			
		||||
gitea.com/macaron/macaron v1.3.3-0.20190821202302-9646c0587edb/go.mod h1:0coI+mSPSwbsyAbOuFllVS38awuk9mevhLD52l50Gjs=
 | 
			
		||||
gitea.com/macaron/macaron v1.4.0/go.mod h1:P7hfDbQjcW22lkYkXlxdRIfWOXxH2+K4EogN4Q0UlLY=
 | 
			
		||||
gitea.com/macaron/session v0.0.0-20190821211443-122c47c5f705/go.mod h1:1ujH0jD6Ca4iK9NL0Q2a7fG2chvXx5hVa7hBfABwpkA=
 | 
			
		||||
gitea.com/macaron/session v0.0.0-20191207215012-613cebf0674d/go.mod h1:FanKy3WjWb5iw/iZBPk4ggoQT9FcM6bkBPvmDmsH6tY=
 | 
			
		||||
gitea.com/macaron/toolbox v0.0.0-20190822013122-05ff0fc766b7/go.mod h1:kgsbFPPS4P+acDYDOPDa3N4IWWOuDJt5/INKRUz7aks=
 | 
			
		||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 | 
			
		||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 | 
			
		||||
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
 | 
			
		||||
github.com/PuerkitoBio/goquery v0.0.0-20170324135448-ed7d758e9a34/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
 | 
			
		||||
github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
 | 
			
		||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 | 
			
		||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 | 
			
		||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 | 
			
		||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
 | 
			
		||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
 | 
			
		||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
 | 
			
		||||
github.com/Unknwon/cae v0.0.0-20160715032808-c6aac99ea2ca/go.mod h1:IRSre9/SEhVuy972TVuJLyaPTS73+8Owhe0Y0l9NXHc=
 | 
			
		||||
github.com/Unknwon/com v0.0.0-20190321035513-0fed4efef755/go.mod h1:voKvFVpXBJxdIPeqjoJuLK+UVcRlo/JLjeToGxPYu68=
 | 
			
		||||
github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966/go.mod h1:SFtfq0zFPsENI7DpE87QM2hcYu5QQ0fRdCgP+P1Hrqo=
 | 
			
		||||
github.com/Unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:fw0McLecf/G5NFwddCRmDckU6yovtk1YsgWIoepMbYo=
 | 
			
		||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 | 
			
		||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
 | 
			
		||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 | 
			
		||||
github.com/andybalholm/cascadia v0.0.0-20161224141413-349dd0209470/go.mod h1:3I+3V7B6gTBYfdpYgIG2ymALS9H+5VDKUl3lHH7ToM4=
 | 
			
		||||
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
 | 
			
		||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 | 
			
		||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 | 
			
		||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
 | 
			
		||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 | 
			
		||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
 | 
			
		||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
			
		||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
			
		||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
			
		||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
 | 
			
		||||
github.com/blevesearch/bleve v0.0.0-20190214220507-05d86ea8f6e3/go.mod h1:Y2lmIkzV6mcNfAnAdOd+ZxHkHchhBfU/xroGIp61wfw=
 | 
			
		||||
github.com/blevesearch/blevex v0.0.0-20180227211930-4b158bb555a3/go.mod h1:WH+MU2F4T0VmSdaPX+Wu5GYoZBrYWdOZWSjzvYcDmqQ=
 | 
			
		||||
github.com/blevesearch/go-porterstemmer v0.0.0-20141230013033-23a2c8e5cf1f/go.mod h1:haWQqFT3RdOGz7PJuM3or/pWNJS1pKkoZJWCkWu0DVA=
 | 
			
		||||
github.com/blevesearch/segment v0.0.0-20160105220820-db70c57796cc/go.mod h1:IInt5XRvpiGE09KOk9mmCMLjHhydIhNPKPPFLFBB7L8=
 | 
			
		||||
github.com/boombuler/barcode v0.0.0-20161226211916-fe0f26ff6d26/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
 | 
			
		||||
github.com/bradfitz/gomemcache v0.0.0-20160117192205-fb1f79c6b65a/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
 | 
			
		||||
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 | 
			
		||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
 | 
			
		||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
			
		||||
github.com/chaseadamsio/goorgeous v0.0.0-20170901132237-098da33fde5f/go.mod h1:6QaC0vFoKWYDth94dHFNgRT2YkT5FHdQp/Yx15aAAi0=
 | 
			
		||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
			
		||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 | 
			
		||||
github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs=
 | 
			
		||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 | 
			
		||||
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 | 
			
		||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 | 
			
		||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
 | 
			
		||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
			
		||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 | 
			
		||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 | 
			
		||||
github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
 | 
			
		||||
github.com/couchbase/gomemcached v0.0.0-20190515232915-c4b4ca0eb21d/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
 | 
			
		||||
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 | 
			
		||||
github.com/couchbase/goutils v0.0.0-20190315194238-f9d42b11473b/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 | 
			
		||||
github.com/couchbase/goutils v0.0.0-20191018232750-b49639060d85/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 | 
			
		||||
github.com/couchbase/vellum v0.0.0-20190111184608-e91b68ff3efe/go.mod h1:prYTC8EgTu3gwbqJihkud9zRXISvyulAplQ6exdCo1g=
 | 
			
		||||
github.com/couchbaselabs/go-couchbase v0.0.0-20190117181324-d904413d884d/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
 | 
			
		||||
github.com/couchbaselabs/go-couchbase v0.0.0-20190708161019-23e7ca2ce2b7/go.mod h1:mby/05p8HE5yHEAKiIH/555NoblMs7PtW6NrYshDruc=
 | 
			
		||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 | 
			
		||||
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
 | 
			
		||||
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
 | 
			
		||||
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
 | 
			
		||||
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.0.0-20190121005146-b04fd42d9952/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.0.0-20190724012636-11b2859924c1/go.mod h1:uU0N10vx1abI4qeVe79CxepBP6PPREVTgMS5Gx6/mOk=
 | 
			
		||||
github.com/denisenkom/go-mssqldb v0.0.0-20190924004331-208c0a498538/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
 | 
			
		||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
			
		||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 | 
			
		||||
github.com/dgryski/go-sip13 v0.0.0-20190329191031-25c5027a8c7b/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 | 
			
		||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 | 
			
		||||
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 | 
			
		||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
 | 
			
		||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
 | 
			
		||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
 | 
			
		||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 | 
			
		||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
 | 
			
		||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 | 
			
		||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
 | 
			
		||||
github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
 | 
			
		||||
github.com/ethantkoenig/rupture v0.0.0-20180203182544-0a76f03a811a/go.mod h1:MkKY/CB98aVE4VxO63X5vTQKUgcn+3XP15LMASe3lYs=
 | 
			
		||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
 | 
			
		||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
 | 
			
		||||
github.com/facebookgo/freeport v0.0.0-20150612182905-d4adf43b75b9/go.mod h1:uPmAp6Sws4L7+Q/OokbWDAK1ibXYhB3PXFP1kol5hPg=
 | 
			
		||||
github.com/facebookgo/grace v0.0.0-20160926231715-5729e484473f/go.mod h1:KigFdumBXUPSwzLDbeuzyt0elrL7+CP7TKuhrhT4bcU=
 | 
			
		||||
github.com/facebookgo/httpdown v0.0.0-20160323221027-a3b1354551a2/go.mod h1:TUV/fX3XrTtBQb5+ttSUJzcFgLNpILONFTKmBuk5RSw=
 | 
			
		||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
 | 
			
		||||
github.com/facebookgo/stats v0.0.0-20151006221625-1b76add642e4/go.mod h1:vsJz7uE339KUCpBXx3JAJzSRH7Uk4iGGyJzR529qDIA=
 | 
			
		||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
 | 
			
		||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
			
		||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 | 
			
		||||
github.com/gliderlabs/ssh v0.1.3/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 | 
			
		||||
github.com/gliderlabs/ssh v0.1.4/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 | 
			
		||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 | 
			
		||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 | 
			
		||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
 | 
			
		||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
 | 
			
		||||
github.com/glycerine/goconvey v0.0.0-20190315024820-982ee783a72e/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
 | 
			
		||||
github.com/go-gitea/gitea v1.2.3 h1:L0SC8kIr3+UnxNAte9M9bmdQ8Bdrc6I5b4Zuz/T+NCw=
 | 
			
		||||
github.com/go-gitea/gitea v1.2.3/go.mod h1:g8iUbfFNyuJp8u7GsSggxI8NQyuxeGTyqxogl3imbQM=
 | 
			
		||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 | 
			
		||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 | 
			
		||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 | 
			
		||||
github.com/go-macaron/binding v0.0.0-20160711225916-9440f336b443/go.mod h1:u+H6rwW+HQwUL+w5uaEJSpIlVZDye1o9MB4Su0JfRfM=
 | 
			
		||||
github.com/go-macaron/cache v0.0.0-20151013081102-561735312776/go.mod h1:hHAsZm/oBZVcY+S7qdQL6Vbg5VrXF6RuKGuqsszt3Ok=
 | 
			
		||||
github.com/go-macaron/captcha v0.0.0-20151123225153-8aa5919789ab/go.mod h1:j9TJ+0nwUOWBvNnm0bheHIPFf3cC62EQo7n7O6PbjZA=
 | 
			
		||||
github.com/go-macaron/captcha v0.0.0-20190710000913-8dc5911259df/go.mod h1:j9TJ+0nwUOWBvNnm0bheHIPFf3cC62EQo7n7O6PbjZA=
 | 
			
		||||
github.com/go-macaron/cors v0.0.0-20190418220122-6fd6a9bfe14e/go.mod h1:utmMRnVIrXPSfA9MFcpIYKEpKawjKxf62vv62k4707E=
 | 
			
		||||
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw=
 | 
			
		||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
 | 
			
		||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 | 
			
		||||
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 | 
			
		||||
github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 | 
			
		||||
github.com/go-openapi/analysis v0.19.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
 | 
			
		||||
github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
 | 
			
		||||
github.com/go-openapi/analysis v0.19.3/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
 | 
			
		||||
github.com/go-openapi/analysis v0.19.4/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk=
 | 
			
		||||
github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU=
 | 
			
		||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 | 
			
		||||
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 | 
			
		||||
github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
 | 
			
		||||
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
 | 
			
		||||
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
 | 
			
		||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 | 
			
		||||
github.com/go-openapi/jsonpointer v0.17.2/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 | 
			
		||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
 | 
			
		||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
 | 
			
		||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 | 
			
		||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 | 
			
		||||
github.com/go-openapi/jsonreference v0.17.2/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 | 
			
		||||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
 | 
			
		||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
 | 
			
		||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
 | 
			
		||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 | 
			
		||||
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 | 
			
		||||
github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 | 
			
		||||
github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
 | 
			
		||||
github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs=
 | 
			
		||||
github.com/go-openapi/loads v0.19.3/go.mod h1:YVfqhUCdahYwR3f3iiwQLhicVRvLlU/WO5WPaZvcvSI=
 | 
			
		||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
 | 
			
		||||
github.com/go-openapi/runtime v0.18.0/go.mod h1:uI6pHuxWYTy94zZxgcwJkUWa9wbIlhteGfloI10GD4U=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.2/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.3/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4=
 | 
			
		||||
github.com/go-openapi/runtime v0.19.5/go.mod h1:WIH6IYPXOrtgTClTV8xzdrD20jBlrK25D0aQbdSlqp8=
 | 
			
		||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
 | 
			
		||||
github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY=
 | 
			
		||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 | 
			
		||||
github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 | 
			
		||||
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 | 
			
		||||
github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
 | 
			
		||||
github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY=
 | 
			
		||||
github.com/go-openapi/strfmt v0.19.2/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
 | 
			
		||||
github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU=
 | 
			
		||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 | 
			
		||||
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 | 
			
		||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
 | 
			
		||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 | 
			
		||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 | 
			
		||||
github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 | 
			
		||||
github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 | 
			
		||||
github.com/go-openapi/validate v0.19.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
 | 
			
		||||
github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA=
 | 
			
		||||
github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo=
 | 
			
		||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 | 
			
		||||
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 | 
			
		||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 | 
			
		||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 | 
			
		||||
github.com/go-swagger/go-swagger v0.19.0/go.mod h1:fOcXeMI1KPNv3uk4u7cR4VSyq0NyrYx4SS1/ajuTWDg=
 | 
			
		||||
github.com/go-swagger/go-swagger v0.20.0/go.mod h1:ylaOr/j+CVsLUsIEhQA49ewFKvVwVSQqVCdDdALNcCw=
 | 
			
		||||
github.com/go-swagger/go-swagger v0.20.1/go.mod h1:LoTpv6FHYXUvYnECHNLvi/qYNybk0d9wkJGH1cTANWE=
 | 
			
		||||
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
 | 
			
		||||
github.com/go-xorm/builder v0.3.3/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk=
 | 
			
		||||
github.com/go-xorm/core v0.6.2/go.mod h1:bwPIfLdm/FzWgVUH8WPVlr+uJhscvNGFcaZKXsI3n2c=
 | 
			
		||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.3/go.mod h1:npNkX0GgFcODSSKHj7nhJPobHwa5E7usBBZUFaxCsXA=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.4/go.mod h1:vpza5fydeRgt+stvo9qgMhSNohYqmNt0I1/D6hkCekA=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.5/go.mod h1:nqz2TAsuOHWH2yk4FYWtacCGgdbrcdZ5mF1XadqEHls=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.6/go.mod h1:nqz2TAsuOHWH2yk4FYWtacCGgdbrcdZ5mF1XadqEHls=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.7/go.mod h1:BS8F0smoUxtyUqKnAtvoQecDRNs8SruHci62u9lRAJQ=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.8/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
 | 
			
		||||
github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
 | 
			
		||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 | 
			
		||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 | 
			
		||||
github.com/gogits/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:YgYOrVn3Nj9Tq0EvjmFbphRytDj7JNRoWSStJZWDJTQ=
 | 
			
		||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 | 
			
		||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 | 
			
		||||
github.com/gogs/cron v0.0.0-20171120032916-9f6c956d3e14/go.mod h1:jPoNZLWDAqA5N3G5amEoiNbhVrmM+ZQEcnQvNQ2KaZk=
 | 
			
		||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 | 
			
		||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
			
		||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
			
		||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 | 
			
		||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
 | 
			
		||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
			
		||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 | 
			
		||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 | 
			
		||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 | 
			
		||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 | 
			
		||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 | 
			
		||||
github.com/google/go-github/v24 v24.0.1/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M=
 | 
			
		||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 | 
			
		||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
			
		||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
 | 
			
		||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
 | 
			
		||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 | 
			
		||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 | 
			
		||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 | 
			
		||||
github.com/gophish/gophish v0.1.2 h1:OWsIzbGf+JbkCNOokbY1sS+nkArDs+9G9kPzRBJz4c4=
 | 
			
		||||
github.com/gophish/gophish v0.1.2/go.mod h1:3nVgumCxriDReEVZ47/9PK5JtN43TcCE9TXt++zFJe8=
 | 
			
		||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
 | 
			
		||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
 | 
			
		||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 | 
			
		||||
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
 | 
			
		||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 | 
			
		||||
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
 | 
			
		||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 | 
			
		||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 | 
			
		||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 | 
			
		||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
			
		||||
github.com/grpc-ecosystem/grpc-gateway v1.9.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 | 
			
		||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 | 
			
		||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 | 
			
		||||
github.com/issue9/assert v1.3.2/go.mod h1:9Ger+iz8X7r1zMYYwEhh++2wMGWcNN2oVI+zIQXxcio=
 | 
			
		||||
github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c/go.mod h1:5mTb/PQNkqmq2x3IxlQZE0aSnTksJg7fg/oWmJ5SKXQ=
 | 
			
		||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
 | 
			
		||||
github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 | 
			
		||||
github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
 | 
			
		||||
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
 | 
			
		||||
github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
 | 
			
		||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
 | 
			
		||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
 | 
			
		||||
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
 | 
			
		||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
 | 
			
		||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
			
		||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 | 
			
		||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
			
		||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
			
		||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
			
		||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 | 
			
		||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
 | 
			
		||||
github.com/kevinburke/ssh_config v0.0.0-20180830205328-81db2a75821e/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
			
		||||
github.com/kevinburke/ssh_config v0.0.0-20190724205821-6cfae18c12b8/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
			
		||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
 | 
			
		||||
github.com/keybase/go-crypto v0.0.0-20170605145657-00ac4db533f6/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M=
 | 
			
		||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 | 
			
		||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
 | 
			
		||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 | 
			
		||||
github.com/klauspost/compress v0.0.0-20161025140425-8df558b6cb6f/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 | 
			
		||||
github.com/klauspost/cpuid v0.0.0-20160302075316-09cded8978dc/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 | 
			
		||||
github.com/klauspost/crc32 v0.0.0-20161016154125-cb6bfca970f6/go.mod h1:+ZoRqAPRLkC4NPOvfYeR5KNOrY6TD+/sAC3HXPZgDYg=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 | 
			
		||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
 | 
			
		||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 | 
			
		||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/lafriks/xormstore v1.0.0/go.mod h1:dD8vHNRfEp3Uy+JvX9cMi2SXcRKJ0x4pYKsZuy843Ic=
 | 
			
		||||
github.com/lafriks/xormstore v1.1.0/go.mod h1:wqtf8B94a8EtE463Ka1MaUT9ZDRl8FICA0nr65xr2wM=
 | 
			
		||||
github.com/lafriks/xormstore v1.2.0/go.mod h1:g47/cl3RfWykO5c4nw/Io3N0R+JuDqiD2YY7NzfWDoU=
 | 
			
		||||
github.com/lafriks/xormstore v1.3.0/go.mod h1:RAhtOztWBjK9xeZpXwKq59rhUxoRgo1zfYl0H1mtK7A=
 | 
			
		||||
github.com/lafriks/xormstore v1.3.1/go.mod h1:qALRD4Vto2Ic7/A5eplMpu5V62mugtSqFysRwz8FETs=
 | 
			
		||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 | 
			
		||||
github.com/lunny/dingtalk_webhook v0.0.0-20171025031554-e3534c89ef96/go.mod h1:mmIfjCSQlGYXmJ95jFN84AkQFnVABtKuJL8IrzwvUKQ=
 | 
			
		||||
github.com/lunny/log v0.0.0-20160921050905-7887c61bf0de/go.mod h1:3q8WtuPQsoRbatJuy3nvq/hRSvuBJrHHr+ybPPiNvHQ=
 | 
			
		||||
github.com/lunny/nodb v0.0.0-20160621015157-fc1ef06ad4af/go.mod h1:Cqz6pqow14VObJ7peltM+2n3PWOz7yTrfUuGbVFkzN0=
 | 
			
		||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
			
		||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
			
		||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
			
		||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
			
		||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 | 
			
		||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
 | 
			
		||||
github.com/markbates/goth v1.56.0/go.mod h1:zZmAw0Es0Dpm7TT/4AdN14QrkiWLMrrU9Xei1o+/mdA=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
			
		||||
github.com/mattn/go-oci8 v0.0.0-20190320171441-14ba190cf52d/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
 | 
			
		||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
			
		||||
github.com/mcuadros/go-version v0.0.0-20190308113854-92cdf37c5b75/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 | 
			
		||||
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo=
 | 
			
		||||
github.com/microcosm-cc/bluemonday v0.0.0-20161012083705-f77f16ffc87a/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 | 
			
		||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM=
 | 
			
		||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 | 
			
		||||
github.com/msteinert/pam v0.0.0-20151204160544-02ccfbfaf0cc/go.mod h1:np1wUFZ6tyoke22qDJZY40URn9Ae51gX7ljIWXN5TJs=
 | 
			
		||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
			
		||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
			
		||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 | 
			
		||||
github.com/oliamb/cutter v0.2.2/go.mod h1:4BenG2/4GuRBDbVm/OPahDVqbrOemzpPiG5mi1iryBU=
 | 
			
		||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 | 
			
		||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
			
		||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 | 
			
		||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
 | 
			
		||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
 | 
			
		||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
 | 
			
		||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 | 
			
		||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
 | 
			
		||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 | 
			
		||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 | 
			
		||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
 | 
			
		||||
github.com/pquerna/otp v0.0.0-20160912161815-54653902c20e/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
 | 
			
		||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 | 
			
		||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 | 
			
		||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
			
		||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 | 
			
		||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 | 
			
		||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
			
		||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
			
		||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
 | 
			
		||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
 | 
			
		||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
			
		||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
 | 
			
		||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
			
		||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
 | 
			
		||||
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 | 
			
		||||
github.com/prometheus/procfs v0.0.4/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
 | 
			
		||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
 | 
			
		||||
github.com/prometheus/tsdb v0.10.0/go.mod h1:oi49uRhEe9dPUTlS3JRZOwJuVi6tmh10QSgwXEyGCt4=
 | 
			
		||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 | 
			
		||||
github.com/remyoudompheng/bigfft v0.0.0-20190321074620-2f0d2b0e0001/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 | 
			
		||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 | 
			
		||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
 | 
			
		||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 | 
			
		||||
github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 | 
			
		||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
 | 
			
		||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 | 
			
		||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 | 
			
		||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 | 
			
		||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
 | 
			
		||||
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20160918041101-1dba4b3954bc/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
 | 
			
		||||
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
 | 
			
		||||
github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY=
 | 
			
		||||
github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg=
 | 
			
		||||
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 | 
			
		||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 | 
			
		||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 | 
			
		||||
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
 | 
			
		||||
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 | 
			
		||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
 | 
			
		||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
			
		||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 | 
			
		||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
 | 
			
		||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
 | 
			
		||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 | 
			
		||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 | 
			
		||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 | 
			
		||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 | 
			
		||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 | 
			
		||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
 | 
			
		||||
github.com/steveyen/gtreap v0.0.0-20150807155958-0abe01ef9be2/go.mod h1:mjqs7N0Q6m5HpR7QfXVBZXZWSqTjQLeTujjA/xUp2uw=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 | 
			
		||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
			
		||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
 | 
			
		||||
github.com/tecbot/gorocksdb v0.0.0-20181010114359-8752a9433481/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
 | 
			
		||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 | 
			
		||||
github.com/tinylib/msgp v0.0.0-20180516164116-c8cf64dff200/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 | 
			
		||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 | 
			
		||||
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
 | 
			
		||||
github.com/tstranex/u2f v1.0.0/go.mod h1:eahSLaqAS0zsIEv80+vXT7WanXs7MQQDg3j3wGBSayo=
 | 
			
		||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 | 
			
		||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 | 
			
		||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 | 
			
		||||
github.com/unknwon/cae v0.0.0-20190822084630-55a0b64484a1/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU=
 | 
			
		||||
github.com/unknwon/cae v1.0.0/go.mod h1:QaSeRctcea9fK6piJpAMCCPKxzJ01+xFcr2k1m3WRPU=
 | 
			
		||||
github.com/unknwon/com v0.0.0-20181010210213-41959bdd855f/go.mod h1:7l5Mh6tAHnDUu0AqU0g7Sm0dgGkYZLRGxJqMYXXBlok=
 | 
			
		||||
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
 | 
			
		||||
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
 | 
			
		||||
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
 | 
			
		||||
github.com/unknwon/paginater v0.0.0-20151104151617-7748a72e0141/go.mod h1:TBwoao3Q4Eb/cp+dHbXDfRTrZSsj/k7kLr2j1oWRWC0=
 | 
			
		||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 | 
			
		||||
github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
 | 
			
		||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
 | 
			
		||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 | 
			
		||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 | 
			
		||||
github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53/go.mod h1:f6elajwZV+xceiaqgRL090YzLEDGSbqr3poGL3ZgXYo=
 | 
			
		||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
 | 
			
		||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
			
		||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 | 
			
		||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
 | 
			
		||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 | 
			
		||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 | 
			
		||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 | 
			
		||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 | 
			
		||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 | 
			
		||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190122013713-64072686203f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 | 
			
		||||
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
 | 
			
		||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
			
		||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
			
		||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 | 
			
		||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 | 
			
		||||
golang.org/x/mobile v0.0.0-20190814143026-e8b3e6111d02/go.mod h1:z5wpDCy2wbnXyFdvEuY3LhY9gBUL86/IOILm+Hsjx+E=
 | 
			
		||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
 | 
			
		||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 | 
			
		||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 | 
			
		||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
 | 
			
		||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20190909003024-a7b16738d86b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 | 
			
		||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 | 
			
		||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190624190245-7f2218787638/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190820033707-85edb9ef3283/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.0.0-20190910221609-7f5965fd7709/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
 | 
			
		||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 | 
			
		||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
 | 
			
		||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
 | 
			
		||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
			
		||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
 | 
			
		||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 | 
			
		||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 | 
			
		||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
google.golang.org/appengine v1.6.3/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 | 
			
		||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
 | 
			
		||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 | 
			
		||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 | 
			
		||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 | 
			
		||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
 | 
			
		||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 | 
			
		||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 | 
			
		||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 | 
			
		||||
gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 | 
			
		||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
			
		||||
gopkg.in/editorconfig/editorconfig-core-go.v1 v1.3.0/go.mod h1:s2mQFI9McjArkyCwyEwU//+luQENTnD/Lfb/7Sj3/kQ=
 | 
			
		||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 | 
			
		||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 | 
			
		||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
 | 
			
		||||
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg=
 | 
			
		||||
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 | 
			
		||||
gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
 | 
			
		||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 | 
			
		||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 | 
			
		||||
gopkg.in/src-d/go-billy.v4 v4.2.1/go.mod h1:tm33zBoOwxjYHZIE+OV8bxTWFMJLrconzFMd38aARFk=
 | 
			
		||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
 | 
			
		||||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
 | 
			
		||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
 | 
			
		||||
gopkg.in/src-d/go-git.v4 v4.11.0/go.mod h1:Vtut8izDyrM8BUVQnzJ+YvmNcem2J89EmfZYCkLokZk=
 | 
			
		||||
gopkg.in/src-d/go-git.v4 v4.12.0/go.mod h1:zjlNnzc1Wjn43v3Mtii7RVxiReNP0fIu9npcXKzuNp4=
 | 
			
		||||
gopkg.in/src-d/go-git.v4 v4.13.0/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
 | 
			
		||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
 | 
			
		||||
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
 | 
			
		||||
gopkg.in/testfixtures.v2 v2.5.0/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
 | 
			
		||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 | 
			
		||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
honnef.co/go/tools v0.0.0-2019.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
			
		||||
honnef.co/go/tools v0.0.0-2019.2.1/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 | 
			
		||||
mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E=
 | 
			
		||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 | 
			
		||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251/go.mod h1:FJGmPh3vz9jSos1L/F91iAgnC/aejc0wIIrF2ZwJxdY=
 | 
			
		||||
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
 | 
			
		||||
xorm.io/core v0.7.2-0.20190928055935-90aeac8d08eb/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
 | 
			
		||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
 | 
			
		||||
							
								
								
									
										2440
									
								
								integration/data/lockfile/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										2440
									
								
								integration/data/lockfile/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1091
									
								
								integration/data/lockfile/poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1091
									
								
								integration/data/lockfile/poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6144
									
								
								integration/data/lockfile/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6144
									
								
								integration/data/lockfile/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6534
									
								
								integration/data/results/amazon_2.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6534
									
								
								integration/data/results/amazon_2.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5634
									
								
								integration/data/results/centos_7.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5634
									
								
								integration/data/results/centos_7.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5631
									
								
								integration/data/results/debian_10.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5631
									
								
								integration/data/results/debian_10.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										3031
									
								
								integration/data/results/oracle.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3031
									
								
								integration/data/results/oracle.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5158
									
								
								integration/data/results/rhel_71.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										5158
									
								
								integration/data/results/rhel_71.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										6926
									
								
								integration/data/results/rhel_8.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6926
									
								
								integration/data/results/rhel_8.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										8609
									
								
								integration/data/results/ubuntu_1804.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8609
									
								
								integration/data/results/ubuntu_1804.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										8559
									
								
								integration/data/results/ubuntu_2004.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8559
									
								
								integration/data/results/ubuntu_2004.json
									
									
									
									
									
										Executable file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										64
									
								
								integration/int-config.toml
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										64
									
								
								integration/int-config.toml
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
[cveDict]
 | 
			
		||||
  Type = "sqlite3"
 | 
			
		||||
  SQLite3Path = "/home/ubuntu/vulsctl/docker/cve.sqlite3"
 | 
			
		||||
 | 
			
		||||
[ovalDict]
 | 
			
		||||
  Type = "sqlite3"
 | 
			
		||||
  SQLite3Path = "/home/ubuntu/vulsctl/docker/oval.sqlite3"
 | 
			
		||||
 | 
			
		||||
[gost]
 | 
			
		||||
  Type = "sqlite3"
 | 
			
		||||
  SQLite3Path = "/home/ubuntu/vulsctl/docker/gost.sqlite3"
 | 
			
		||||
 | 
			
		||||
[exploit]
 | 
			
		||||
  Type = "sqlite3"
 | 
			
		||||
  SQLite3Path = "/home/ubuntu/vulsctl/docker/go-exploitdb.sqlite3"
 | 
			
		||||
 | 
			
		||||
[metasploit]
 | 
			
		||||
  type = "sqlite3"
 | 
			
		||||
  SQLite3Path = "/home/ubuntu/vulsctl/docker/go-msfdb.sqlite3"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.rails]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
cpeNames = [ "cpe:/a:rubyonrails:rails:3.0.1" ]
 | 
			
		||||
 | 
			
		||||
[servers.cpe_vendor_product_match]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
cpeNames = ["cpe:/a:hitachi_abb_power_grids:afs660"]
 | 
			
		||||
 | 
			
		||||
[servers.gemfile]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Gemfile.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.pipfile]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Pipfile.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.poetry]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/poetry.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.composer]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/composer.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.packagelock]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/package-lock.json"]
 | 
			
		||||
 | 
			
		||||
[servers.yarn]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/yarn.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.cargo]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Cargo.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.gomod]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/go.sum"]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										64
									
								
								integration/int-redis-config.toml
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										64
									
								
								integration/int-redis-config.toml
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
[cveDict]
 | 
			
		||||
Type = "redis"
 | 
			
		||||
Url = "redis://127.0.0.1/3"
 | 
			
		||||
 | 
			
		||||
[ovalDict]
 | 
			
		||||
  Type = "redis"
 | 
			
		||||
  Url = "redis://127.0.0.1/1"
 | 
			
		||||
 | 
			
		||||
[gost]
 | 
			
		||||
  Type = "redis"
 | 
			
		||||
  Url = "redis://127.0.0.1/2"
 | 
			
		||||
 | 
			
		||||
[exploit]
 | 
			
		||||
  Type = "redis"
 | 
			
		||||
  Url = "redis://127.0.0.1/4"
 | 
			
		||||
 | 
			
		||||
[metasploit]
 | 
			
		||||
  Type = "redis"
 | 
			
		||||
  Url = "redis://127.0.0.1/5"
 | 
			
		||||
 | 
			
		||||
[default]
 | 
			
		||||
 | 
			
		||||
[servers]
 | 
			
		||||
 | 
			
		||||
[servers.rails]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
cpeNames = [ "cpe:/a:rubyonrails:rails:3.0.1" ]
 | 
			
		||||
 | 
			
		||||
[servers.cpe_vendor_product_match]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
cpeNames = ["cpe:/a:hitachi_abb_power_grids:afs660"]
 | 
			
		||||
 | 
			
		||||
[servers.gemfile]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Gemfile.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.pipfile]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Pipfile.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.poetry]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/poetry.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.composer]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/composer.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.packagelock]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/package-lock.json"]
 | 
			
		||||
 | 
			
		||||
[servers.yarn]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/yarn.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.cargo]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/Cargo.lock"]
 | 
			
		||||
 | 
			
		||||
[servers.gomod]
 | 
			
		||||
type = "pseudo"
 | 
			
		||||
lockfiles = ["./integration/data/lockfile/go.sum"]
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								logging/logutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								logging/logutil.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
package logging
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"github.com/rifflock/lfshook"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
	formatter "github.com/kotakanbe/logrus-prefixed-formatter"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//LogOpts has options for logging
 | 
			
		||||
type LogOpts struct {
 | 
			
		||||
	Debug     bool   `json:"debug,omitempty"`
 | 
			
		||||
	DebugSQL  bool   `json:"debugSQL,omitempty"`
 | 
			
		||||
	LogToFile bool   `json:"logToFile,omitempty"`
 | 
			
		||||
	LogDir    string `json:"logDir,omitempty"`
 | 
			
		||||
	Quiet     bool   `json:"quiet,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Log for localhost
 | 
			
		||||
var Log Logger
 | 
			
		||||
 | 
			
		||||
// Logger has logrus entry
 | 
			
		||||
type Logger struct {
 | 
			
		||||
	logrus.Entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Out = ioutil.Discard
 | 
			
		||||
	fields := logrus.Fields{"prefix": ""}
 | 
			
		||||
	Log = Logger{Entry: *log.WithFields(fields)}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewNormalLogger creates normal logger
 | 
			
		||||
func NewNormalLogger() Logger {
 | 
			
		||||
	return Logger{Entry: logrus.Entry{Logger: logrus.New()}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCustomLogger creates logrus
 | 
			
		||||
func NewCustomLogger(debug, quiet, logToFile bool, logDir, logMsgAnsiColor, serverName string) Logger {
 | 
			
		||||
	log := logrus.New()
 | 
			
		||||
	log.Formatter = &formatter.TextFormatter{MsgAnsiColor: logMsgAnsiColor}
 | 
			
		||||
	log.Level = logrus.InfoLevel
 | 
			
		||||
	if debug {
 | 
			
		||||
		log.Level = logrus.DebugLevel
 | 
			
		||||
		pp.ColoringEnabled = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if flag.Lookup("test.v") != nil {
 | 
			
		||||
		return Logger{Entry: *logrus.NewEntry(log)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	whereami := "localhost"
 | 
			
		||||
	if serverName != "" {
 | 
			
		||||
		whereami = serverName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if logToFile {
 | 
			
		||||
		dir := GetDefaultLogDir()
 | 
			
		||||
		if logDir != "" {
 | 
			
		||||
			dir = logDir
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := os.Stat(dir); os.IsNotExist(err) {
 | 
			
		||||
			if err := os.Mkdir(dir, 0700); err != nil {
 | 
			
		||||
				log.Errorf("Failed to create log directory. path: %s, err: %+v", dir, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logFile := dir + "/vuls.log"
 | 
			
		||||
		if file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
 | 
			
		||||
			log.Out = io.MultiWriter(os.Stderr, file)
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Out = os.Stderr
 | 
			
		||||
			log.Errorf("Failed to create log file. path: %s, err: %+v", logFile, err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if _, err := os.Stat(dir); err == nil {
 | 
			
		||||
			path := filepath.Join(dir, fmt.Sprintf("%s.log", whereami))
 | 
			
		||||
			if _, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); err == nil {
 | 
			
		||||
				log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{
 | 
			
		||||
					logrus.DebugLevel: path,
 | 
			
		||||
					logrus.InfoLevel:  path,
 | 
			
		||||
					logrus.WarnLevel:  path,
 | 
			
		||||
					logrus.ErrorLevel: path,
 | 
			
		||||
					logrus.FatalLevel: path,
 | 
			
		||||
					logrus.PanicLevel: path,
 | 
			
		||||
				}, nil))
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Errorf("Failed to create log file. path: %s, err: %+v", path, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else if quiet {
 | 
			
		||||
		log.Out = io.Discard
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Out = os.Stderr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := log.WithFields(logrus.Fields{"prefix": whereami})
 | 
			
		||||
	return Logger{Entry: *entry}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDefaultLogDir returns default log directory
 | 
			
		||||
func GetDefaultLogDir() string {
 | 
			
		||||
	defaultLogDir := "/var/log/vuls"
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		defaultLogDir = filepath.Join(os.Getenv("APPDATA"), "vuls")
 | 
			
		||||
	}
 | 
			
		||||
	return defaultLogDir
 | 
			
		||||
}
 | 
			
		||||
@@ -233,7 +233,7 @@ func NewCveContentType(name string) CveContentType {
 | 
			
		||||
		return Nvd
 | 
			
		||||
	case "jvn":
 | 
			
		||||
		return Jvn
 | 
			
		||||
	case "redhat", "centos":
 | 
			
		||||
	case "redhat", "centos", "rocky":
 | 
			
		||||
		return RedHat
 | 
			
		||||
	case "oracle":
 | 
			
		||||
		return Oracle
 | 
			
		||||
@@ -245,6 +245,8 @@ func NewCveContentType(name string) CveContentType {
 | 
			
		||||
		return RedHatAPI
 | 
			
		||||
	case "debian_security_tracker":
 | 
			
		||||
		return DebianSecurityTracker
 | 
			
		||||
	case "ubuntu_api":
 | 
			
		||||
		return UbuntuAPI
 | 
			
		||||
	case "microsoft":
 | 
			
		||||
		return Microsoft
 | 
			
		||||
	case "wordpress":
 | 
			
		||||
@@ -282,6 +284,9 @@ const (
 | 
			
		||||
	// Ubuntu is Ubuntu
 | 
			
		||||
	Ubuntu CveContentType = "ubuntu"
 | 
			
		||||
 | 
			
		||||
	// UbuntuAPI is Ubuntu
 | 
			
		||||
	UbuntuAPI CveContentType = "ubuntu_api"
 | 
			
		||||
 | 
			
		||||
	// Oracle is Oracle Linux
 | 
			
		||||
	Oracle CveContentType = "oracle"
 | 
			
		||||
 | 
			
		||||
@@ -317,10 +322,11 @@ var AllCveContetTypes = CveContentTypes{
 | 
			
		||||
	RedHat,
 | 
			
		||||
	RedHatAPI,
 | 
			
		||||
	Debian,
 | 
			
		||||
	DebianSecurityTracker,
 | 
			
		||||
	Ubuntu,
 | 
			
		||||
	UbuntuAPI,
 | 
			
		||||
	Amazon,
 | 
			
		||||
	SUSE,
 | 
			
		||||
	DebianSecurityTracker,
 | 
			
		||||
	WpScan,
 | 
			
		||||
	Trivy,
 | 
			
		||||
	GitHub,
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,9 @@ import (
 | 
			
		||||
	"github.com/aquasecurity/trivy-db/pkg/db"
 | 
			
		||||
	trivyDBTypes "github.com/aquasecurity/trivy-db/pkg/types"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/detector/library"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
	// "github.com/aquasecurity/go-dep-parser/pkg/types"
 | 
			
		||||
)
 | 
			
		||||
@@ -40,13 +40,14 @@ func (lss LibraryScanners) Total() (total int) {
 | 
			
		||||
 | 
			
		||||
// LibraryScanner has libraries information
 | 
			
		||||
type LibraryScanner struct {
 | 
			
		||||
	Type string
 | 
			
		||||
	Path string
 | 
			
		||||
	Libs []types.Library
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Scan : scan target library
 | 
			
		||||
func (s LibraryScanner) Scan() ([]VulnInfo, error) {
 | 
			
		||||
	scanner, err := library.DriverFactory{}.NewDriver(filepath.Base(string(s.Path)))
 | 
			
		||||
	scanner, err := library.NewDriver(s.Type)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to new a library driver: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -71,7 +72,7 @@ func (s LibraryScanner) convertFanalToVuln(tvulns []types.DetectedVulnerability)
 | 
			
		||||
	for _, tvuln := range tvulns {
 | 
			
		||||
		vinfo, err := s.getVulnDetail(tvuln)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Debugf("failed to getVulnDetail. err: %s, tvuln: %#v", err, tvuln)
 | 
			
		||||
			logging.Log.Debugf("failed to getVulnDetail. err: %+v, tvuln: %#v", err, tvuln)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		vulns = append(vulns, vinfo)
 | 
			
		||||
@@ -87,15 +88,13 @@ func (s LibraryScanner) getVulnDetail(tvuln types.DetectedVulnerability) (vinfo
 | 
			
		||||
 | 
			
		||||
	vinfo.CveID = tvuln.VulnerabilityID
 | 
			
		||||
	vinfo.CveContents = getCveContents(tvuln.VulnerabilityID, vul)
 | 
			
		||||
	if tvuln.FixedVersion != "" {
 | 
			
		||||
		vinfo.LibraryFixedIns = []LibraryFixedIn{
 | 
			
		||||
			{
 | 
			
		||||
				Key:     s.GetLibraryKey(),
 | 
			
		||||
				Name:    tvuln.PkgName,
 | 
			
		||||
				FixedIn: tvuln.FixedVersion,
 | 
			
		||||
				Path:    s.Path,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	vinfo.LibraryFixedIns = []LibraryFixedIn{
 | 
			
		||||
		{
 | 
			
		||||
			Key:     s.GetLibraryKey(),
 | 
			
		||||
			Name:    tvuln.PkgName,
 | 
			
		||||
			FixedIn: tvuln.FixedVersion,
 | 
			
		||||
			Path:    s.Path,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	return vinfo, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -128,6 +127,7 @@ var LibraryMap = map[string]string{
 | 
			
		||||
	"composer.lock":     "php",
 | 
			
		||||
	"Pipfile.lock":      "python",
 | 
			
		||||
	"poetry.lock":       "python",
 | 
			
		||||
	"go.sum":            "gomod",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLibraryKey returns target library key
 | 
			
		||||
 
 | 
			
		||||
@@ -63,13 +63,13 @@ func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindByFQPN search a package by Fully-Qualified-Package-Name
 | 
			
		||||
func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) {
 | 
			
		||||
func (ps Packages) FindByFQPN(nameVerRel string) (*Package, error) {
 | 
			
		||||
	for _, p := range ps {
 | 
			
		||||
		if nameVerRelArc == p.FQPN() {
 | 
			
		||||
		if nameVerRel == p.FQPN() {
 | 
			
		||||
			return &p, nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRelArc)
 | 
			
		||||
	return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Package has installed binary packages.
 | 
			
		||||
@@ -96,9 +96,6 @@ func (p Package) FQPN() string {
 | 
			
		||||
	if p.Release != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf("-%s", p.Release)
 | 
			
		||||
	}
 | 
			
		||||
	if p.Arch != "" {
 | 
			
		||||
		fqpn += fmt.Sprintf(".%s", p.Arch)
 | 
			
		||||
	}
 | 
			
		||||
	return fqpn
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -382,7 +382,7 @@ func Test_IsRaspbianPackage(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_parseListenPorts(t *testing.T) {
 | 
			
		||||
func Test_NewPortStat(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name   string
 | 
			
		||||
		args   string
 | 
			
		||||
@@ -423,7 +423,7 @@ func Test_parseListenPorts(t *testing.T) {
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Errorf("unexpected error occurred: %s", err)
 | 
			
		||||
			} else if !reflect.DeepEqual(*listenPort, tt.expect) {
 | 
			
		||||
				t.Errorf("base.parseListenPorts() = %v, want %v", *listenPort, tt.expect)
 | 
			
		||||
				t.Errorf("base.NewPortStat() = %v, want %v", *listenPort, tt.expect)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,13 +4,14 @@ import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/cwe"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanResults is a slide of ScanResult
 | 
			
		||||
@@ -18,31 +19,31 @@ type ScanResults []ScanResult
 | 
			
		||||
 | 
			
		||||
// ScanResult has the result of scanned CVE information.
 | 
			
		||||
type ScanResult struct {
 | 
			
		||||
	JSONVersion      int                   `json:"jsonVersion"`
 | 
			
		||||
	Lang             string                `json:"lang"`
 | 
			
		||||
	ServerUUID       string                `json:"serverUUID"`
 | 
			
		||||
	ServerName       string                `json:"serverName"` // TOML Section key
 | 
			
		||||
	Family           string                `json:"family"`
 | 
			
		||||
	Release          string                `json:"release"`
 | 
			
		||||
	Container        Container             `json:"container"`
 | 
			
		||||
	Platform         Platform              `json:"platform"`
 | 
			
		||||
	IPv4Addrs        []string              `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	IPv6Addrs        []string              `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	IPSIdentifiers   map[config.IPS]string `json:"ipsIdentifiers,omitempty"`
 | 
			
		||||
	ScannedAt        time.Time             `json:"scannedAt"`
 | 
			
		||||
	ScanMode         string                `json:"scanMode"`
 | 
			
		||||
	ScannedVersion   string                `json:"scannedVersion"`
 | 
			
		||||
	ScannedRevision  string                `json:"scannedRevision"`
 | 
			
		||||
	ScannedBy        string                `json:"scannedBy"`
 | 
			
		||||
	ScannedVia       string                `json:"scannedVia"`
 | 
			
		||||
	ScannedIPv4Addrs []string              `json:"scannedIpv4Addrs,omitempty"`
 | 
			
		||||
	ScannedIPv6Addrs []string              `json:"scannedIpv6Addrs,omitempty"`
 | 
			
		||||
	ReportedAt       time.Time             `json:"reportedAt"`
 | 
			
		||||
	ReportedVersion  string                `json:"reportedVersion"`
 | 
			
		||||
	ReportedRevision string                `json:"reportedRevision"`
 | 
			
		||||
	ReportedBy       string                `json:"reportedBy"`
 | 
			
		||||
	Errors           []string              `json:"errors"`
 | 
			
		||||
	Warnings         []string              `json:"warnings"`
 | 
			
		||||
	JSONVersion      int               `json:"jsonVersion"`
 | 
			
		||||
	Lang             string            `json:"lang"`
 | 
			
		||||
	ServerUUID       string            `json:"serverUUID"`
 | 
			
		||||
	ServerName       string            `json:"serverName"` // TOML Section key
 | 
			
		||||
	Family           string            `json:"family"`
 | 
			
		||||
	Release          string            `json:"release"`
 | 
			
		||||
	Container        Container         `json:"container"`
 | 
			
		||||
	Platform         Platform          `json:"platform"`
 | 
			
		||||
	IPv4Addrs        []string          `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	IPv6Addrs        []string          `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
 | 
			
		||||
	IPSIdentifiers   map[string]string `json:"ipsIdentifiers,omitempty"`
 | 
			
		||||
	ScannedAt        time.Time         `json:"scannedAt"`
 | 
			
		||||
	ScanMode         string            `json:"scanMode"`
 | 
			
		||||
	ScannedVersion   string            `json:"scannedVersion"`
 | 
			
		||||
	ScannedRevision  string            `json:"scannedRevision"`
 | 
			
		||||
	ScannedBy        string            `json:"scannedBy"`
 | 
			
		||||
	ScannedVia       string            `json:"scannedVia"`
 | 
			
		||||
	ScannedIPv4Addrs []string          `json:"scannedIpv4Addrs,omitempty"`
 | 
			
		||||
	ScannedIPv6Addrs []string          `json:"scannedIpv6Addrs,omitempty"`
 | 
			
		||||
	ReportedAt       time.Time         `json:"reportedAt"`
 | 
			
		||||
	ReportedVersion  string            `json:"reportedVersion"`
 | 
			
		||||
	ReportedRevision string            `json:"reportedRevision"`
 | 
			
		||||
	ReportedBy       string            `json:"reportedBy"`
 | 
			
		||||
	Errors           []string          `json:"errors"`
 | 
			
		||||
	Warnings         []string          `json:"warnings"`
 | 
			
		||||
 | 
			
		||||
	ScannedCves       VulnInfos              `json:"scannedCves"`
 | 
			
		||||
	RunningKernel     Kernel                 `json:"runningKernel"`
 | 
			
		||||
@@ -59,63 +60,19 @@ type ScanResult struct {
 | 
			
		||||
	} `json:"config"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweDict is a dictionary for CWE
 | 
			
		||||
type CweDict map[string]CweDictEntry
 | 
			
		||||
 | 
			
		||||
// Get the name, url, top10URL for the specified cweID, lang
 | 
			
		||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL string) {
 | 
			
		||||
	cweNum := strings.TrimPrefix(cweID, "CWE-")
 | 
			
		||||
	switch config.Conf.Lang {
 | 
			
		||||
	case "ja":
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
 | 
			
		||||
			top10Rank = dict.OwaspTopTen2017
 | 
			
		||||
			top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
 | 
			
		||||
			cweTop25Rank = dict.CweTopTwentyfive2019
 | 
			
		||||
			cweTop25URL = cwe.CweTopTwentyfive2019URL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
 | 
			
		||||
			sansTop25Rank = dict.SansTopTwentyfive
 | 
			
		||||
			sansTop25URL = cwe.SansTopTwentyfiveURL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := cwe.CweDictJa[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
			url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
			
		||||
		} else {
 | 
			
		||||
			if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
				name = dict.Name
 | 
			
		||||
			}
 | 
			
		||||
			url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
 | 
			
		||||
			top10Rank = dict.OwaspTopTen2017
 | 
			
		||||
			top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
 | 
			
		||||
			cweTop25Rank = dict.CweTopTwentyfive2019
 | 
			
		||||
			cweTop25URL = cwe.CweTopTwentyfive2019URL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
 | 
			
		||||
			sansTop25Rank = dict.SansTopTwentyfive
 | 
			
		||||
			sansTop25URL = cwe.SansTopTwentyfiveURL
 | 
			
		||||
		}
 | 
			
		||||
		url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
			
		||||
		if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string `json:"containerID"`
 | 
			
		||||
	Name        string `json:"name"`
 | 
			
		||||
	Image       string `json:"image"`
 | 
			
		||||
	Type        string `json:"type"`
 | 
			
		||||
	UUID        string `json:"uuid"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweDictEntry is a entry of CWE
 | 
			
		||||
type CweDictEntry struct {
 | 
			
		||||
	En                   *cwe.Cwe `json:"en,omitempty"`
 | 
			
		||||
	Ja                   *cwe.Cwe `json:"ja,omitempty"`
 | 
			
		||||
	OwaspTopTen2017      string   `json:"owaspTopTen2017"`
 | 
			
		||||
	CweTopTwentyfive2019 string   `json:"cweTopTwentyfive2019"`
 | 
			
		||||
	SansTopTwentyfive    string   `json:"sansTopTwentyfive"`
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string `json:"name"` // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string `json:"instanceID"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Kernel has the Release, version and whether need restart
 | 
			
		||||
@@ -125,131 +82,10 @@ type Kernel struct {
 | 
			
		||||
	RebootRequired bool   `json:"rebootRequired"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver is filter function.
 | 
			
		||||
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		if over <= v.MaxCvssScore().Value.Score {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnoreCves is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnoreCves() ScanResult {
 | 
			
		||||
	ignoreCves := []string{}
 | 
			
		||||
	if len(r.Container.Name) == 0 {
 | 
			
		||||
		//TODO pass by args
 | 
			
		||||
		ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
 | 
			
		||||
	} else {
 | 
			
		||||
		//TODO pass by args
 | 
			
		||||
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
			if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
				ignoreCves = con.IgnoreCves
 | 
			
		||||
			} else {
 | 
			
		||||
				return r
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
			return r
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		for _, c := range ignoreCves {
 | 
			
		||||
			if v.CveID == c {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterUnfixed is filter function.
 | 
			
		||||
func (r ScanResult) FilterUnfixed(ignoreUnfixed bool) ScanResult {
 | 
			
		||||
	if !ignoreUnfixed {
 | 
			
		||||
		return r
 | 
			
		||||
	}
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		// Report cves detected by CPE because Vuls can't know 'fixed' or 'unfixed'
 | 
			
		||||
		if len(v.CpeURIs) != 0 {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		NotFixedAll := true
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			NotFixedAll = NotFixedAll && p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		return !NotFixedAll
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnorePkgs is filter function.
 | 
			
		||||
func (r ScanResult) FilterIgnorePkgs() ScanResult {
 | 
			
		||||
	var ignorePkgsRegexps []string
 | 
			
		||||
	if len(r.Container.Name) == 0 {
 | 
			
		||||
		//TODO pass by args
 | 
			
		||||
		ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
 | 
			
		||||
	} else {
 | 
			
		||||
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
 | 
			
		||||
			if con, ok := s.Containers[r.Container.Name]; ok {
 | 
			
		||||
				ignorePkgsRegexps = con.IgnorePkgsRegexp
 | 
			
		||||
			} else {
 | 
			
		||||
				return r
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Errorf("%s is not found in config.toml",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
			return r
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	regexps := []*regexp.Regexp{}
 | 
			
		||||
	for _, pkgRegexp := range ignorePkgsRegexps {
 | 
			
		||||
		re, err := regexp.Compile(pkgRegexp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to parse %s. err: %+v", pkgRegexp, err)
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			regexps = append(regexps, re)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(regexps) == 0 {
 | 
			
		||||
		return r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
		if len(v.AffectedPackages) == 0 {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			match := false
 | 
			
		||||
			for _, re := range regexps {
 | 
			
		||||
				if re.MatchString(p.Name) {
 | 
			
		||||
					match = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !match {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterInactiveWordPressLibs is filter function.
 | 
			
		||||
func (r ScanResult) FilterInactiveWordPressLibs(detectInactive bool) ScanResult {
 | 
			
		||||
func (r *ScanResult) FilterInactiveWordPressLibs(detectInactive bool) {
 | 
			
		||||
	if detectInactive {
 | 
			
		||||
		return r
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
 | 
			
		||||
@@ -262,17 +98,19 @@ func (r ScanResult) FilterInactiveWordPressLibs(detectInactive bool) ScanResult
 | 
			
		||||
				if p.Status != Inactive {
 | 
			
		||||
					return true
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				logging.Log.Warnf("Failed to find the WordPress pkg: %+s", wp.Name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
	r.ScannedCves = filtered
 | 
			
		||||
	return r
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReportFileName returns the filename on localhost without extension
 | 
			
		||||
func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		return fmt.Sprintf("%s", r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
 | 
			
		||||
@@ -281,7 +119,7 @@ func (r ScanResult) ReportFileName() (name string) {
 | 
			
		||||
// ReportKeyName returns the name of key on S3, Azure-Blob without extension
 | 
			
		||||
func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
	timestr := r.ScannedAt.Format(time.RFC3339)
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
 | 
			
		||||
@@ -289,7 +127,7 @@ func (r ScanResult) ReportKeyName() (name string) {
 | 
			
		||||
 | 
			
		||||
// ServerInfo returns server name one line
 | 
			
		||||
func (r ScanResult) ServerInfo() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		return fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.FormatServerName(), r.Family, r.Release)
 | 
			
		||||
	}
 | 
			
		||||
@@ -304,7 +142,7 @@ func (r ScanResult) ServerInfo() string {
 | 
			
		||||
 | 
			
		||||
// ServerInfoTui returns server information for TUI sidebar
 | 
			
		||||
func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		line := fmt.Sprintf("%s (%s%s)",
 | 
			
		||||
			r.ServerName, r.Family, r.Release)
 | 
			
		||||
		if len(r.Warnings) != 0 {
 | 
			
		||||
@@ -325,7 +163,7 @@ func (r ScanResult) ServerInfoTui() string {
 | 
			
		||||
 | 
			
		||||
// FormatServerName returns server and container name
 | 
			
		||||
func (r ScanResult) FormatServerName() (name string) {
 | 
			
		||||
	if len(r.Container.ContainerID) == 0 {
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
		name = r.ServerName
 | 
			
		||||
	} else {
 | 
			
		||||
		name = fmt.Sprintf("%s@%s",
 | 
			
		||||
@@ -344,7 +182,7 @@ func (r ScanResult) FormatTextReportHeader() string {
 | 
			
		||||
		buf.WriteString("=")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pkgs := r.FormatUpdatablePacksSummary()
 | 
			
		||||
	pkgs := r.FormatUpdatablePkgsSummary()
 | 
			
		||||
	if 0 < len(r.WordPressPackages) {
 | 
			
		||||
		pkgs = fmt.Sprintf("%s, %d WordPress pkgs", pkgs, len(r.WordPressPackages))
 | 
			
		||||
	}
 | 
			
		||||
@@ -352,7 +190,7 @@ func (r ScanResult) FormatTextReportHeader() string {
 | 
			
		||||
		pkgs = fmt.Sprintf("%s, %d libs", pkgs, r.LibraryScanners.Total())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s\n%s\n",
 | 
			
		||||
	return fmt.Sprintf("%s\n%s\n%s\n%s, %s, %s, %s\n%s\n",
 | 
			
		||||
		r.ServerInfo(),
 | 
			
		||||
		buf.String(),
 | 
			
		||||
		r.ScannedCves.FormatCveSummary(),
 | 
			
		||||
@@ -363,9 +201,10 @@ func (r ScanResult) FormatTextReportHeader() string {
 | 
			
		||||
		pkgs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
 | 
			
		||||
func (r ScanResult) FormatUpdatablePacksSummary() string {
 | 
			
		||||
	if !r.isDisplayUpdatableNum() {
 | 
			
		||||
// FormatUpdatablePkgsSummary returns a summary of updatable packages
 | 
			
		||||
func (r ScanResult) FormatUpdatablePkgsSummary() string {
 | 
			
		||||
	mode := r.Config.Scan.Servers[r.ServerName].Mode
 | 
			
		||||
	if !r.isDisplayUpdatableNum(mode) {
 | 
			
		||||
		return fmt.Sprintf("%d installed", len(r.Packages))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -420,16 +259,11 @@ func (r ScanResult) FormatAlertSummary() string {
 | 
			
		||||
	return fmt.Sprintf("en: %d, ja: %d alerts", enCnt, jaCnt)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r ScanResult) isDisplayUpdatableNum() bool {
 | 
			
		||||
	if r.Family == config.FreeBSD {
 | 
			
		||||
func (r ScanResult) isDisplayUpdatableNum(mode config.ScanMode) bool {
 | 
			
		||||
	if r.Family == constant.FreeBSD {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var mode config.ScanMode
 | 
			
		||||
	//TODO pass by args
 | 
			
		||||
	s, _ := config.Conf.Servers[r.ServerName]
 | 
			
		||||
	mode = s.Mode
 | 
			
		||||
 | 
			
		||||
	if mode.IsOffline() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
@@ -438,11 +272,11 @@ func (r ScanResult) isDisplayUpdatableNum() bool {
 | 
			
		||||
	}
 | 
			
		||||
	if mode.IsFast() {
 | 
			
		||||
		switch r.Family {
 | 
			
		||||
		case config.RedHat,
 | 
			
		||||
			config.Oracle,
 | 
			
		||||
			config.Debian,
 | 
			
		||||
			config.Ubuntu,
 | 
			
		||||
			config.Raspbian:
 | 
			
		||||
		case constant.RedHat,
 | 
			
		||||
			constant.Oracle,
 | 
			
		||||
			constant.Debian,
 | 
			
		||||
			constant.Ubuntu,
 | 
			
		||||
			constant.Raspbian:
 | 
			
		||||
			return false
 | 
			
		||||
		default:
 | 
			
		||||
			return true
 | 
			
		||||
@@ -456,38 +290,12 @@ func (r ScanResult) IsContainer() bool {
 | 
			
		||||
	return 0 < len(r.Container.ContainerID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsDeepScanMode checks if the scan mode is deep scan mode.
 | 
			
		||||
func (r ScanResult) IsDeepScanMode() bool {
 | 
			
		||||
	for _, s := range r.Config.Scan.Servers {
 | 
			
		||||
		if ok := s.Mode.IsDeep(); ok {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Container has Container information
 | 
			
		||||
type Container struct {
 | 
			
		||||
	ContainerID string `json:"containerID"`
 | 
			
		||||
	Name        string `json:"name"`
 | 
			
		||||
	Image       string `json:"image"`
 | 
			
		||||
	Type        string `json:"type"`
 | 
			
		||||
	UUID        string `json:"uuid"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Platform has platform information
 | 
			
		||||
type Platform struct {
 | 
			
		||||
	Name       string `json:"name"` // aws or azure or gcp or other...
 | 
			
		||||
	InstanceID string `json:"instanceID"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveRaspbianPackFromResult is for Raspberry Pi and removes the Raspberry Pi dedicated package from ScanResult.
 | 
			
		||||
func (r ScanResult) RemoveRaspbianPackFromResult() ScanResult {
 | 
			
		||||
	if r.Family != config.Raspbian {
 | 
			
		||||
		return r
 | 
			
		||||
func (r ScanResult) RemoveRaspbianPackFromResult() *ScanResult {
 | 
			
		||||
	if r.Family != constant.Raspbian {
 | 
			
		||||
		return &r
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result := r
 | 
			
		||||
	packs := make(Packages)
 | 
			
		||||
	for _, pack := range r.Packages {
 | 
			
		||||
		if !IsRaspbianPackage(pack.Name, pack.Version) {
 | 
			
		||||
@@ -502,10 +310,10 @@ func (r ScanResult) RemoveRaspbianPackFromResult() ScanResult {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result.Packages = packs
 | 
			
		||||
	result.SrcPackages = srcPacks
 | 
			
		||||
	r.Packages = packs
 | 
			
		||||
	r.SrcPackages = srcPacks
 | 
			
		||||
 | 
			
		||||
	return result
 | 
			
		||||
	return &r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClearFields clears a given fields of ScanResult
 | 
			
		||||
@@ -528,3 +336,166 @@ func (r ScanResult) ClearFields(targetTagNames []string) ScanResult {
 | 
			
		||||
	}
 | 
			
		||||
	return r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckEOL checks the EndOfLife of the OS
 | 
			
		||||
func (r *ScanResult) CheckEOL() {
 | 
			
		||||
	switch r.Family {
 | 
			
		||||
	case constant.ServerTypePseudo, constant.Raspbian:
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	eol, found := config.GetEOL(r.Family, r.Release)
 | 
			
		||||
	if !found {
 | 
			
		||||
		r.Warnings = append(r.Warnings,
 | 
			
		||||
			fmt.Sprintf("Failed to check EOL. Register the issue to https://github.com/future-architect/vuls/issues with the information in `Family: %s Release: %s`",
 | 
			
		||||
				r.Family, r.Release))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	if eol.IsStandardSupportEnded(now) {
 | 
			
		||||
		r.Warnings = append(r.Warnings, "Standard OS support is EOL(End-of-Life). Purchase extended support if available or Upgrading your OS is strongly recommended.")
 | 
			
		||||
		if eol.ExtendedSupportUntil.IsZero() {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if !eol.IsExtendedSuppportEnded(now) {
 | 
			
		||||
			r.Warnings = append(r.Warnings,
 | 
			
		||||
				fmt.Sprintf("Extended support available until %s. Check the vendor site.",
 | 
			
		||||
					eol.ExtendedSupportUntil.Format("2006-01-02")))
 | 
			
		||||
		} else {
 | 
			
		||||
			r.Warnings = append(r.Warnings,
 | 
			
		||||
				"Extended support is also EOL. There are many Vulnerabilities that are not detected, Upgrading your OS strongly recommended.")
 | 
			
		||||
		}
 | 
			
		||||
	} else if !eol.StandardSupportUntil.IsZero() &&
 | 
			
		||||
		now.AddDate(0, 3, 0).After(eol.StandardSupportUntil) {
 | 
			
		||||
		r.Warnings = append(r.Warnings,
 | 
			
		||||
			fmt.Sprintf("Standard OS support will be end in 3 months. EOL date: %s",
 | 
			
		||||
				eol.StandardSupportUntil.Format("2006-01-02")))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SortForJSONOutput sort list elements in the ScanResult to diff in integration-test
 | 
			
		||||
func (r *ScanResult) SortForJSONOutput() {
 | 
			
		||||
	for k, v := range r.Packages {
 | 
			
		||||
		sort.Slice(v.AffectedProcs, func(i, j int) bool {
 | 
			
		||||
			return v.AffectedProcs[i].PID < v.AffectedProcs[j].PID
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.NeedRestartProcs, func(i, j int) bool {
 | 
			
		||||
			return v.NeedRestartProcs[i].PID < v.NeedRestartProcs[j].PID
 | 
			
		||||
		})
 | 
			
		||||
		r.Packages[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	for i, v := range r.LibraryScanners {
 | 
			
		||||
		sort.Slice(v.Libs, func(i, j int) bool {
 | 
			
		||||
			switch strings.Compare(v.Libs[i].Name, v.Libs[j].Name) {
 | 
			
		||||
			case -1:
 | 
			
		||||
				return true
 | 
			
		||||
			case 1:
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
			return v.Libs[i].Version < v.Libs[j].Version
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
		r.LibraryScanners[i] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for k, v := range r.ScannedCves {
 | 
			
		||||
		sort.Slice(v.AffectedPackages, func(i, j int) bool {
 | 
			
		||||
			return v.AffectedPackages[i].Name < v.AffectedPackages[j].Name
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.DistroAdvisories, func(i, j int) bool {
 | 
			
		||||
			return v.DistroAdvisories[i].AdvisoryID < v.DistroAdvisories[j].AdvisoryID
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.Exploits, func(i, j int) bool {
 | 
			
		||||
			return v.Exploits[i].URL < v.Exploits[j].URL
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.Metasploits, func(i, j int) bool {
 | 
			
		||||
			return v.Metasploits[i].Name < v.Metasploits[j].Name
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.Mitigations, func(i, j int) bool {
 | 
			
		||||
			return v.Mitigations[i].URL < v.Mitigations[j].URL
 | 
			
		||||
		})
 | 
			
		||||
		for kk, vv := range v.CveContents {
 | 
			
		||||
			sort.Slice(vv.References, func(i, j int) bool {
 | 
			
		||||
				return vv.References[i].Link < vv.References[j].Link
 | 
			
		||||
			})
 | 
			
		||||
			sort.Slice(vv.CweIDs, func(i, j int) bool {
 | 
			
		||||
				return vv.CweIDs[i] < vv.CweIDs[j]
 | 
			
		||||
			})
 | 
			
		||||
			for kkk, vvv := range vv.References {
 | 
			
		||||
				// sort v.CveContents[].References[].Tags
 | 
			
		||||
				sort.Slice(vvv.Tags, func(i, j int) bool {
 | 
			
		||||
					return vvv.Tags[i] < vvv.Tags[j]
 | 
			
		||||
				})
 | 
			
		||||
				vv.References[kkk] = vvv
 | 
			
		||||
			}
 | 
			
		||||
			v.CveContents[kk] = vv
 | 
			
		||||
		}
 | 
			
		||||
		sort.Slice(v.AlertDict.En, func(i, j int) bool {
 | 
			
		||||
			return v.AlertDict.En[i].Title < v.AlertDict.En[j].Title
 | 
			
		||||
		})
 | 
			
		||||
		sort.Slice(v.AlertDict.Ja, func(i, j int) bool {
 | 
			
		||||
			return v.AlertDict.Ja[i].Title < v.AlertDict.Ja[j].Title
 | 
			
		||||
		})
 | 
			
		||||
		r.ScannedCves[k] = v
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweDict is a dictionary for CWE
 | 
			
		||||
type CweDict map[string]CweDictEntry
 | 
			
		||||
 | 
			
		||||
// Get the name, url, top10URL for the specified cweID, lang
 | 
			
		||||
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL string) {
 | 
			
		||||
	cweNum := strings.TrimPrefix(cweID, "CWE-")
 | 
			
		||||
	switch lang {
 | 
			
		||||
	case "ja":
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
 | 
			
		||||
			top10Rank = dict.OwaspTopTen2017
 | 
			
		||||
			top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
 | 
			
		||||
			cweTop25Rank = dict.CweTopTwentyfive2019
 | 
			
		||||
			cweTop25URL = cwe.CweTopTwentyfive2019URL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
 | 
			
		||||
			sansTop25Rank = dict.SansTopTwentyfive
 | 
			
		||||
			sansTop25URL = cwe.SansTopTwentyfiveURL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := cwe.CweDictJa[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
			url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
 | 
			
		||||
		} else {
 | 
			
		||||
			if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
				name = dict.Name
 | 
			
		||||
			}
 | 
			
		||||
			url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
 | 
			
		||||
			top10Rank = dict.OwaspTopTen2017
 | 
			
		||||
			top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.CweTopTwentyfive2019 != "" {
 | 
			
		||||
			cweTop25Rank = dict.CweTopTwentyfive2019
 | 
			
		||||
			cweTop25URL = cwe.CweTopTwentyfive2019URL
 | 
			
		||||
		}
 | 
			
		||||
		if dict, ok := c[cweNum]; ok && dict.SansTopTwentyfive != "" {
 | 
			
		||||
			sansTop25Rank = dict.SansTopTwentyfive
 | 
			
		||||
			sansTop25URL = cwe.SansTopTwentyfiveURL
 | 
			
		||||
		}
 | 
			
		||||
		url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
 | 
			
		||||
		if dict, ok := cwe.CweDictEn[cweNum]; ok {
 | 
			
		||||
			name = dict.Name
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CweDictEntry is a entry of CWE
 | 
			
		||||
type CweDictEntry struct {
 | 
			
		||||
	En                   *cwe.Cwe `json:"en,omitempty"`
 | 
			
		||||
	Ja                   *cwe.Cwe `json:"ja,omitempty"`
 | 
			
		||||
	OwaspTopTen2017      string   `json:"owaspTopTen2017"`
 | 
			
		||||
	CweTopTwentyfive2019 string   `json:"cweTopTwentyfive2019"`
 | 
			
		||||
	SansTopTwentyfive    string   `json:"sansTopTwentyfive"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -3,11 +3,12 @@ package models
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	exploitmodels "github.com/vulsio/go-exploitdb/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +27,81 @@ func (v VulnInfos) Find(f func(VulnInfo) bool) VulnInfos {
 | 
			
		||||
	return filtered
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterByCvssOver return scored vulnerabilities
 | 
			
		||||
func (v VulnInfos) FilterByCvssOver(over float64) VulnInfos {
 | 
			
		||||
	return v.Find(func(v VulnInfo) bool {
 | 
			
		||||
		if over <= v.MaxCvssScore().Value.Score {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnoreCves filter function.
 | 
			
		||||
func (v VulnInfos) FilterIgnoreCves(ignoreCveIDs []string) VulnInfos {
 | 
			
		||||
	return v.Find(func(v VulnInfo) bool {
 | 
			
		||||
		for _, c := range ignoreCveIDs {
 | 
			
		||||
			if v.CveID == c {
 | 
			
		||||
				return false
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterUnfixed filter unfixed CVE-IDs
 | 
			
		||||
func (v VulnInfos) FilterUnfixed(ignoreUnfixed bool) VulnInfos {
 | 
			
		||||
	if !ignoreUnfixed {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
	return v.Find(func(v VulnInfo) bool {
 | 
			
		||||
		// Report cves detected by CPE because Vuls can't know 'fixed' or 'unfixed'
 | 
			
		||||
		if len(v.CpeURIs) != 0 {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		NotFixedAll := true
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			NotFixedAll = NotFixedAll && p.NotFixedYet
 | 
			
		||||
		}
 | 
			
		||||
		return !NotFixedAll
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FilterIgnorePkgs is filter function.
 | 
			
		||||
func (v VulnInfos) FilterIgnorePkgs(ignorePkgsRegexps []string) VulnInfos {
 | 
			
		||||
	regexps := []*regexp.Regexp{}
 | 
			
		||||
	for _, pkgRegexp := range ignorePkgsRegexps {
 | 
			
		||||
		re, err := regexp.Compile(pkgRegexp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logging.Log.Warnf("Failed to parse %s. err: %+v", pkgRegexp, err)
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			regexps = append(regexps, re)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(regexps) == 0 {
 | 
			
		||||
		return v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return v.Find(func(v VulnInfo) bool {
 | 
			
		||||
		if len(v.AffectedPackages) == 0 {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
		for _, p := range v.AffectedPackages {
 | 
			
		||||
			match := false
 | 
			
		||||
			for _, re := range regexps {
 | 
			
		||||
				if re.MatchString(p.Name) {
 | 
			
		||||
					match = true
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !match {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindScoredVulns return scored vulnerabilities
 | 
			
		||||
func (v VulnInfos) FindScoredVulns() VulnInfos {
 | 
			
		||||
	return v.Find(func(vv VulnInfo) bool {
 | 
			
		||||
@@ -80,14 +156,15 @@ func (v VulnInfos) CountGroupBySeverity() map[string]int {
 | 
			
		||||
// 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 (Critical:%d High:%d Medium:%d Low:%d)",
 | 
			
		||||
			m["High"]+m["Medium"]+m["Low"], m["Critical"], m["High"], m["Medium"], m["Low"])
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
	line := fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
 | 
			
		||||
		m["Critical"]+m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
 | 
			
		||||
		m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
 | 
			
		||||
 | 
			
		||||
	nPlus, nMinus := v.CountDiff()
 | 
			
		||||
	if 0 < nPlus || 0 < nMinus {
 | 
			
		||||
		line = fmt.Sprintf("%s +%d -%d", line, nPlus, nMinus)
 | 
			
		||||
	}
 | 
			
		||||
	return line
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FormatFixedStatus summarize the number of cves are fixed.
 | 
			
		||||
@@ -105,6 +182,18 @@ func (v VulnInfos) FormatFixedStatus(packs Packages) string {
 | 
			
		||||
	return fmt.Sprintf("%d/%d Fixed", fixed, total)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountDiff counts the number of added/removed CVE-ID
 | 
			
		||||
func (v VulnInfos) CountDiff() (nPlus int, nMinus int) {
 | 
			
		||||
	for _, vInfo := range v {
 | 
			
		||||
		if vInfo.DiffStatus == DiffPlus {
 | 
			
		||||
			nPlus++
 | 
			
		||||
		} else if vInfo.DiffStatus == DiffMinus {
 | 
			
		||||
			nMinus++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageFixStatuses is a list of PackageStatus
 | 
			
		||||
type PackageFixStatuses []PackageFixStatus
 | 
			
		||||
 | 
			
		||||
@@ -159,8 +248,8 @@ type VulnInfo struct {
 | 
			
		||||
	GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
 | 
			
		||||
	WpPackageFixStats    WpPackageFixStats    `json:"wpPackageFixStats,omitempty"`
 | 
			
		||||
	LibraryFixedIns      LibraryFixedIns      `json:"libraryFixedIns,omitempty"`
 | 
			
		||||
 | 
			
		||||
	VulnType string `json:"vulnType,omitempty"`
 | 
			
		||||
	VulnType             string               `json:"vulnType,omitempty"`
 | 
			
		||||
	DiffStatus           DiffStatus           `json:"diffStatus,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Alert has CERT alert information
 | 
			
		||||
@@ -236,6 +325,25 @@ func (g WpPackages) Add(pkg WpPackage) WpPackages {
 | 
			
		||||
	return append(g, pkg)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DiffStatus keeps a comparison with the previous detection results for this CVE
 | 
			
		||||
type DiffStatus string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// DiffPlus is newly detected CVE
 | 
			
		||||
	DiffPlus = DiffStatus("+")
 | 
			
		||||
 | 
			
		||||
	// DiffMinus is resolved CVE
 | 
			
		||||
	DiffMinus = DiffStatus("-")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CveIDDiffFormat format CVE-ID for diff mode
 | 
			
		||||
func (v VulnInfo) CveIDDiffFormat() string {
 | 
			
		||||
	if v.DiffStatus != "" {
 | 
			
		||||
		return fmt.Sprintf("%s %s", v.DiffStatus, v.CveID)
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s", v.CveID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Titles returns title (TUI)
 | 
			
		||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
 | 
			
		||||
	if lang == "ja" {
 | 
			
		||||
@@ -530,6 +638,7 @@ func (c Cvss) Format() string {
 | 
			
		||||
	return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SeverityToCvssScoreRange returns CVSS score range
 | 
			
		||||
func (c Cvss) SeverityToCvssScoreRange() string {
 | 
			
		||||
	return severityToCvssScoreRange(c.Severity)
 | 
			
		||||
}
 | 
			
		||||
@@ -654,14 +763,10 @@ type AlertDict struct {
 | 
			
		||||
 | 
			
		||||
// FormatSource returns which source has this alert
 | 
			
		||||
func (a AlertDict) FormatSource() string {
 | 
			
		||||
	s := []string{}
 | 
			
		||||
	if len(a.En) != 0 {
 | 
			
		||||
		s = append(s, "USCERT")
 | 
			
		||||
	if len(a.En) != 0 || len(a.Ja) != 0 {
 | 
			
		||||
		return "CERT"
 | 
			
		||||
	}
 | 
			
		||||
	if len(a.Ja) != 0 {
 | 
			
		||||
		s = append(s, "JPCERT")
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Join(s, "/")
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Confidences is a list of Confidence
 | 
			
		||||
@@ -703,8 +808,11 @@ func (c Confidence) String() string {
 | 
			
		||||
type DetectionMethod string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// CpeNameMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeNameMatchStr = "CpeNameMatch"
 | 
			
		||||
	// CpeVersionMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeVersionMatchStr = "CpeVersionMatch"
 | 
			
		||||
 | 
			
		||||
	// CpeVendorProductMatchStr is a String representation of CpeNameMatch
 | 
			
		||||
	CpeVendorProductMatchStr = "CpeVendorProductMatch"
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatchStr is a String representation of YumUpdateSecurityMatch
 | 
			
		||||
	YumUpdateSecurityMatchStr = "YumUpdateSecurityMatch"
 | 
			
		||||
@@ -721,6 +829,9 @@ const (
 | 
			
		||||
	// DebianSecurityTrackerMatchStr is a String representation of DebianSecurityTrackerMatch
 | 
			
		||||
	DebianSecurityTrackerMatchStr = "DebianSecurityTrackerMatch"
 | 
			
		||||
 | 
			
		||||
	// UbuntuAPIMatchStr is a String representation of UbuntuAPIMatch
 | 
			
		||||
	UbuntuAPIMatchStr = "UbuntuAPIMatch"
 | 
			
		||||
 | 
			
		||||
	// TrivyMatchStr is a String representation of Trivy
 | 
			
		||||
	TrivyMatchStr = "TrivyMatch"
 | 
			
		||||
 | 
			
		||||
@@ -744,8 +855,8 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// CpeNameMatch is a ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	CpeNameMatch = Confidence{100, CpeNameMatchStr, 1}
 | 
			
		||||
	// CpeVersionMatch is a ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	CpeVersionMatch = Confidence{100, CpeVersionMatchStr, 1}
 | 
			
		||||
 | 
			
		||||
	// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr, 2}
 | 
			
		||||
@@ -762,6 +873,9 @@ var (
 | 
			
		||||
	// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
	// UbuntuAPIMatch ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	UbuntuAPIMatch = Confidence{100, UbuntuAPIMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
	// TrivyMatch ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	TrivyMatch = Confidence{100, TrivyMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
@@ -776,4 +890,7 @@ var (
 | 
			
		||||
 | 
			
		||||
	// WpScanMatch is a ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	WpScanMatch = Confidence{100, WpScanMatchStr, 0}
 | 
			
		||||
 | 
			
		||||
	// CpeVendorProductMatch is a ranking how confident the CVE-ID was detected correctly
 | 
			
		||||
	CpeVendorProductMatch = Confidence{10, CpeVendorProductMatchStr, 9}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package models
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestTitles(t *testing.T) {
 | 
			
		||||
@@ -1036,20 +1037,20 @@ func TestAppendIfMissing(t *testing.T) {
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
			arg: CpeNameMatch,
 | 
			
		||||
			arg: CpeVersionMatch,
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
			arg: ChangelogExactMatch,
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
				ChangelogExactMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
@@ -1070,21 +1071,21 @@ func TestSortByConfident(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: Confidences{
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
			},
 | 
			
		||||
			out: Confidences{
 | 
			
		||||
				OvalMatch,
 | 
			
		||||
				CpeNameMatch,
 | 
			
		||||
				CpeVersionMatch,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -1240,3 +1241,372 @@ func TestVulnInfo_AttackVector(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVulnInfos_FilterByCvssOver(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		over float64
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		v    VulnInfos
 | 
			
		||||
		args args
 | 
			
		||||
		want VulnInfos
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "over 7.0",
 | 
			
		||||
			args: args{over: 7.0},
 | 
			
		||||
			v: 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{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: 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{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "over high",
 | 
			
		||||
			args: args{over: 7.0},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							CveID:         "CVE-2017-0001",
 | 
			
		||||
							Cvss3Severity: "HIGH",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          Debian,
 | 
			
		||||
							CveID:         "CVE-2017-0002",
 | 
			
		||||
							Cvss3Severity: "CRITICAL",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          GitHub,
 | 
			
		||||
							CveID:         "CVE-2017-0003",
 | 
			
		||||
							Cvss3Severity: "IMPORTANT",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          Ubuntu,
 | 
			
		||||
							CveID:         "CVE-2017-0001",
 | 
			
		||||
							Cvss3Severity: "HIGH",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          Debian,
 | 
			
		||||
							CveID:         "CVE-2017-0002",
 | 
			
		||||
							Cvss3Severity: "CRITICAL",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					CveContents: NewCveContents(
 | 
			
		||||
						CveContent{
 | 
			
		||||
							Type:          GitHub,
 | 
			
		||||
							CveID:         "CVE-2017-0003",
 | 
			
		||||
							Cvss3Severity: "IMPORTANT",
 | 
			
		||||
							LastModified:  time.Time{},
 | 
			
		||||
						},
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.v.FilterByCvssOver(tt.args.over); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("VulnInfos.FindByCvssOver() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVulnInfos_FilterIgnoreCves(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		ignoreCveIDs []string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		v    VulnInfos
 | 
			
		||||
		args args
 | 
			
		||||
		want VulnInfos
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "filter ignored",
 | 
			
		||||
			args: args{ignoreCveIDs: []string{"CVE-2017-0002"}},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.v.FilterIgnoreCves(tt.args.ignoreCveIDs); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("VulnInfos.FindIgnoreCves() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVulnInfos_FilterUnfixed(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		ignoreUnfixed bool
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		v    VulnInfos
 | 
			
		||||
		args args
 | 
			
		||||
		want VulnInfos
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "filter ok",
 | 
			
		||||
			args: args{ignoreUnfixed: true},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "a",
 | 
			
		||||
							NotFixedYet: true,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "b",
 | 
			
		||||
							NotFixedYet: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "c",
 | 
			
		||||
							NotFixedYet: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "d",
 | 
			
		||||
							NotFixedYet: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "b",
 | 
			
		||||
							NotFixedYet: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0003": {
 | 
			
		||||
					CveID: "CVE-2017-0003",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "c",
 | 
			
		||||
							NotFixedYet: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:        "d",
 | 
			
		||||
							NotFixedYet: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.v.FilterUnfixed(tt.args.ignoreUnfixed); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("VulnInfos.FilterUnfixed() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestVulnInfos_FilterIgnorePkgs(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		ignorePkgsRegexps []string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		v    VulnInfos
 | 
			
		||||
		args args
 | 
			
		||||
		want VulnInfos
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "filter pkgs 1",
 | 
			
		||||
			args: args{ignorePkgsRegexps: []string{"^kernel"}},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{Name: "kernel"},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{
 | 
			
		||||
				"CVE-2017-0002": {
 | 
			
		||||
					CveID: "CVE-2017-0002",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "filter pkgs 2",
 | 
			
		||||
			args: args{ignorePkgsRegexps: []string{"^kernel"}},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{Name: "kernel"},
 | 
			
		||||
						{Name: "vim"},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{Name: "kernel"},
 | 
			
		||||
						{Name: "vim"},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "filter pkgs 3",
 | 
			
		||||
			args: args{ignorePkgsRegexps: []string{"^kernel", "^vim", "^bind"}},
 | 
			
		||||
			v: VulnInfos{
 | 
			
		||||
				"CVE-2017-0001": {
 | 
			
		||||
					CveID: "CVE-2017-0001",
 | 
			
		||||
					AffectedPackages: PackageFixStatuses{
 | 
			
		||||
						{Name: "kernel"},
 | 
			
		||||
						{Name: "vim"},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			want: VulnInfos{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.v.FilterIgnorePkgs(tt.args.ignorePkgsRegexps); !reflect.DeepEqual(got, tt.want) {
 | 
			
		||||
				t.Errorf("VulnInfos.FilterIgnorePkgs() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package msf
 | 
			
		||||
							
								
								
									
										51
									
								
								msf/msf.go
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								msf/msf.go
									
									
									
									
									
								
							@@ -1,51 +0,0 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package msf
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/takuzoo3868/go-msfdb/db"
 | 
			
		||||
	metasploitmodels "github.com/takuzoo3868/go-msfdb/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithMetasploit fills metasploit module information that has in module
 | 
			
		||||
func FillWithMetasploit(driver db.DB, r *models.ScanResult) (nMetasploitCve int, err error) {
 | 
			
		||||
	if driver == nil {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
		if cveID == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		ms := driver.GetModuleByCveID(cveID)
 | 
			
		||||
		if len(ms) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		modules := ConvertToModels(ms)
 | 
			
		||||
		vuln.Metasploits = modules
 | 
			
		||||
		r.ScannedCves[cveID] = vuln
 | 
			
		||||
		nMetasploitCve++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nMetasploitCve, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModels converts gost model to vuls model
 | 
			
		||||
func ConvertToModels(ms []*metasploitmodels.Metasploit) (modules []models.Metasploit) {
 | 
			
		||||
	for _, m := range ms {
 | 
			
		||||
		var links []string
 | 
			
		||||
		if 0 < len(m.References) {
 | 
			
		||||
			for _, u := range m.References {
 | 
			
		||||
				links = append(links, u.Link)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		module := models.Metasploit{
 | 
			
		||||
			Name:        m.Name,
 | 
			
		||||
			Title:       m.Title,
 | 
			
		||||
			Description: m.Description,
 | 
			
		||||
			URLs:        links,
 | 
			
		||||
		}
 | 
			
		||||
		modules = append(modules, module)
 | 
			
		||||
	}
 | 
			
		||||
	return modules
 | 
			
		||||
}
 | 
			
		||||
@@ -4,9 +4,9 @@ package oval
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Alpine is the struct of Alpine Linux
 | 
			
		||||
@@ -15,22 +15,33 @@ type Alpine struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAlpine creates OVAL client for SUSE
 | 
			
		||||
func NewAlpine() Alpine {
 | 
			
		||||
func NewAlpine(cnf config.VulnDictInterface) Alpine {
 | 
			
		||||
	return Alpine{
 | 
			
		||||
		Base{
 | 
			
		||||
			family: config.Alpine,
 | 
			
		||||
			family: constant.Alpine,
 | 
			
		||||
			Cnf:    cnf,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Alpine) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
func (o Alpine) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
	if o.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		driver, err := newOvalDB(o.Cnf, r.Family)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -46,7 +57,7 @@ func (o Alpine) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	cveID := defPacks.def.Advisory.Cves[0].CveID
 | 
			
		||||
	vinfo, ok := r.ScannedCves[cveID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		util.Log.Debugf("%s is newly detected by OVAL", cveID)
 | 
			
		||||
		logging.Log.Debugf("%s is newly detected by OVAL", cveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       cveID,
 | 
			
		||||
			Confidences: []models.Confidence{models.OvalMatch},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								oval/debian.go
									
									
									
									
									
								
							
							
						
						
									
										128
									
								
								oval/debian.go
									
									
									
									
									
								
							@@ -7,9 +7,10 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +24,7 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
	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)
 | 
			
		||||
		logging.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Debian.CveID)
 | 
			
		||||
		vinfo = models.VulnInfo{
 | 
			
		||||
			CveID:       defPacks.def.Debian.CveID,
 | 
			
		||||
			Confidences: []models.Confidence{models.OvalMatch},
 | 
			
		||||
@@ -33,14 +34,14 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
 | 
			
		||||
		cveContents := vinfo.CveContents
 | 
			
		||||
		ctype := models.NewCveContentType(o.family)
 | 
			
		||||
		if _, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
			util.Log.Debugf("%s OVAL will be overwritten",
 | 
			
		||||
			logging.Log.Debugf("%s OVAL will be overwritten",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Debugf("%s is also detected by OVAL",
 | 
			
		||||
			logging.Log.Debugf("%s is also detected by OVAL",
 | 
			
		||||
				defPacks.def.Debian.CveID)
 | 
			
		||||
			cveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		if r.Family != config.Raspbian {
 | 
			
		||||
		if r.Family != constant.Raspbian {
 | 
			
		||||
			vinfo.Confidences.AppendIfMissing(models.OvalMatch)
 | 
			
		||||
		} else {
 | 
			
		||||
			if len(vinfo.Confidences) == 0 {
 | 
			
		||||
@@ -109,18 +110,19 @@ type Debian struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewDebian creates OVAL client for Debian
 | 
			
		||||
func NewDebian() Debian {
 | 
			
		||||
func NewDebian(cnf config.VulnDictInterface) Debian {
 | 
			
		||||
	return Debian{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Debian,
 | 
			
		||||
				family: constant.Debian,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Debian) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
func (o Debian) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
 | 
			
		||||
	//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
@@ -139,29 +141,23 @@ func (o Debian) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if r.Family != config.Raspbian {
 | 
			
		||||
			if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// OVAL does not support Package for Raspbian, so skip it.
 | 
			
		||||
			result := r.RemoveRaspbianPackFromResult()
 | 
			
		||||
			if relatedDefs, err = getDefsByPackNameViaHTTP(&result); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
	if o.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if r.Family != config.Raspbian {
 | 
			
		||||
			if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			// OVAL does not support Package for Raspbian, so skip it.
 | 
			
		||||
			result := r.RemoveRaspbianPackFromResult()
 | 
			
		||||
			if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, &result); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
		driver, err := newOvalDB(o.Cnf, r.Family)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -199,18 +195,19 @@ type Ubuntu struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewUbuntu creates OVAL client for Debian
 | 
			
		||||
func NewUbuntu() Ubuntu {
 | 
			
		||||
func NewUbuntu(cnf config.VulnDictInterface) Ubuntu {
 | 
			
		||||
	return Ubuntu{
 | 
			
		||||
		DebianBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Ubuntu,
 | 
			
		||||
				family: constant.Ubuntu,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
func (o Ubuntu) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	switch util.Major(r.Release) {
 | 
			
		||||
	case "14":
 | 
			
		||||
		kernelNamesInOval := []string{
 | 
			
		||||
@@ -226,7 +223,7 @@ func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
 | 
			
		||||
			"linux-signed-lts-xenial",
 | 
			
		||||
			"linux",
 | 
			
		||||
		}
 | 
			
		||||
		return o.fillWithOval(driver, r, kernelNamesInOval)
 | 
			
		||||
		return o.fillWithOval(r, kernelNamesInOval)
 | 
			
		||||
	case "16":
 | 
			
		||||
		kernelNamesInOval := []string{
 | 
			
		||||
			"linux-aws",
 | 
			
		||||
@@ -261,7 +258,7 @@ func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
 | 
			
		||||
			"linux-snapdragon",
 | 
			
		||||
			"linux",
 | 
			
		||||
		}
 | 
			
		||||
		return o.fillWithOval(driver, r, kernelNamesInOval)
 | 
			
		||||
		return o.fillWithOval(r, kernelNamesInOval)
 | 
			
		||||
	case "18":
 | 
			
		||||
		kernelNamesInOval := []string{
 | 
			
		||||
			"linux-aws",
 | 
			
		||||
@@ -316,7 +313,7 @@ func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
 | 
			
		||||
			"linux-snapdragon",
 | 
			
		||||
			"linux",
 | 
			
		||||
		}
 | 
			
		||||
		return o.fillWithOval(driver, r, kernelNamesInOval)
 | 
			
		||||
		return o.fillWithOval(r, kernelNamesInOval)
 | 
			
		||||
	case "20":
 | 
			
		||||
		kernelNamesInOval := []string{
 | 
			
		||||
			"linux-aws",
 | 
			
		||||
@@ -344,12 +341,53 @@ func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err
 | 
			
		||||
			"linux-signed-oracle",
 | 
			
		||||
			"linux",
 | 
			
		||||
		}
 | 
			
		||||
		return o.fillWithOval(driver, r, kernelNamesInOval)
 | 
			
		||||
		return o.fillWithOval(r, kernelNamesInOval)
 | 
			
		||||
	case "21":
 | 
			
		||||
		kernelNamesInOval := []string{
 | 
			
		||||
			"linux-aws",
 | 
			
		||||
			"linux-base-sgx",
 | 
			
		||||
			"linux-base",
 | 
			
		||||
			"linux-cloud-tools-common",
 | 
			
		||||
			"linux-cloud-tools-generic",
 | 
			
		||||
			"linux-cloud-tools-lowlatency",
 | 
			
		||||
			"linux-cloud-tools-virtual",
 | 
			
		||||
			"linux-gcp",
 | 
			
		||||
			"linux-generic",
 | 
			
		||||
			"linux-gke",
 | 
			
		||||
			"linux-headers-aws",
 | 
			
		||||
			"linux-headers-gcp",
 | 
			
		||||
			"linux-headers-gke",
 | 
			
		||||
			"linux-headers-oracle",
 | 
			
		||||
			"linux-image-aws",
 | 
			
		||||
			"linux-image-extra-virtual",
 | 
			
		||||
			"linux-image-gcp",
 | 
			
		||||
			"linux-image-generic",
 | 
			
		||||
			"linux-image-gke",
 | 
			
		||||
			"linux-image-lowlatency",
 | 
			
		||||
			"linux-image-oracle",
 | 
			
		||||
			"linux-image-virtual",
 | 
			
		||||
			"linux-lowlatency",
 | 
			
		||||
			"linux-modules-extra-aws",
 | 
			
		||||
			"linux-modules-extra-gcp",
 | 
			
		||||
			"linux-modules-extra-gke",
 | 
			
		||||
			"linux-oracle",
 | 
			
		||||
			"linux-tools-aws",
 | 
			
		||||
			"linux-tools-common",
 | 
			
		||||
			"linux-tools-gcp",
 | 
			
		||||
			"linux-tools-generic",
 | 
			
		||||
			"linux-tools-gke",
 | 
			
		||||
			"linux-tools-host",
 | 
			
		||||
			"linux-tools-lowlatency",
 | 
			
		||||
			"linux-tools-oracle",
 | 
			
		||||
			"linux-tools-virtual",
 | 
			
		||||
			"linux-virtual",
 | 
			
		||||
		}
 | 
			
		||||
		return o.fillWithOval(r, kernelNamesInOval)
 | 
			
		||||
	}
 | 
			
		||||
	return 0, fmt.Errorf("Ubuntu %s is not support for now", r.Release)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOval []string) (nCVEs int, err error) {
 | 
			
		||||
func (o Ubuntu) fillWithOval(r *models.ScanResult, kernelNamesInOval []string) (nCVEs int, err error) {
 | 
			
		||||
	linuxImage := "linux-image-" + r.RunningKernel.Release
 | 
			
		||||
	runningKernelVersion := ""
 | 
			
		||||
	kernelPkgInOVAL := ""
 | 
			
		||||
@@ -361,7 +399,7 @@ func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOv
 | 
			
		||||
		if v, ok := r.Packages[linuxImage]; ok {
 | 
			
		||||
			runningKernelVersion = v.Version
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Warnf("Unable to detect vulns of running kernel because the version of the running kernel is unknown. server: %s",
 | 
			
		||||
			logging.Log.Warnf("Unable to detect vulns of running kernel because the version of the running kernel is unknown. server: %s",
 | 
			
		||||
				r.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -400,7 +438,7 @@ func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOv
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if kernelPkgInOVAL == "" {
 | 
			
		||||
			util.Log.Warnf("The OVAL name of the running kernel image %+v is not found. So vulns of `linux` wll be detected. server: %s",
 | 
			
		||||
			logging.Log.Warnf("The OVAL name of the running kernel image %+v is not found. So vulns of `linux` wll be detected. server: %s",
 | 
			
		||||
				r.RunningKernel, r.ServerName)
 | 
			
		||||
			kernelPkgInOVAL = "linux"
 | 
			
		||||
			isOVALKernelPkgAdded = true
 | 
			
		||||
@@ -415,11 +453,21 @@ func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOv
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
	if o.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		driver, err := newOvalDB(o.Cnf, r.Family)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@ 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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -56,7 +54,7 @@ func TestPackNamesOfUpdateDebian(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	// util.Log = util.NewCustomLogger()
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		Debian{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +0,0 @@
 | 
			
		||||
package oval
 | 
			
		||||
							
								
								
									
										101
									
								
								oval/oval.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								oval/oval.go
									
									
									
									
									
								
							@@ -6,7 +6,8 @@ import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
@@ -16,50 +17,81 @@ import (
 | 
			
		||||
 | 
			
		||||
// Client is the interface of OVAL client.
 | 
			
		||||
type Client interface {
 | 
			
		||||
	FillWithOval(db.DB, *models.ScanResult) (int, error)
 | 
			
		||||
 | 
			
		||||
	// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
	CheckIfOvalFetched(db.DB, string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(db.DB, string, string) (bool, error)
 | 
			
		||||
	FillWithOval(*models.ScanResult) (int, error)
 | 
			
		||||
	CheckIfOvalFetched(string, string) (bool, error)
 | 
			
		||||
	CheckIfOvalFresh(string, string) (bool, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	family string
 | 
			
		||||
	Cnf    config.VulnDictInterface
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		count, err := driver.CountDefs(osFamily, release)
 | 
			
		||||
func (b Base) CheckIfOvalFetched(osFamily, release string) (fetched bool, err error) {
 | 
			
		||||
	ovalFamily, err := GetFamilyInOval(osFamily)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	if !b.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		driver, err := newOvalDB(b.Cnf, ovalFamily)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, xerrors.Errorf("Failed to count OVAL defs: %s, %s, %w", osFamily, release, err)
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		count, err := driver.CountDefs(ovalFamily, release)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, xerrors.Errorf("Failed to count OVAL defs: %s, %s, %w", ovalFamily, release, err)
 | 
			
		||||
		}
 | 
			
		||||
		logging.Log.Infof("OVAL %s %s found. defs: %d", osFamily, release, count)
 | 
			
		||||
		return 0 < count, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "count", osFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
	url, _ := util.URLPathJoin(config.Conf.OvalDict.URL, "count", ovalFamily, release)
 | 
			
		||||
	resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
		return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
 | 
			
		||||
	}
 | 
			
		||||
	count := 0
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &count); err != nil {
 | 
			
		||||
		return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	logging.Log.Infof("OVAL %s %s is fresh. defs: %d", osFamily, release, count)
 | 
			
		||||
	return 0 < count, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfOvalFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool, err error) {
 | 
			
		||||
func (b Base) CheckIfOvalFresh(osFamily, release string) (ok bool, err error) {
 | 
			
		||||
	ovalFamily, err := GetFamilyInOval(osFamily)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	var lastModified time.Time
 | 
			
		||||
	if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		lastModified = driver.GetLastModified(osFamily, release)
 | 
			
		||||
	if !b.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		driver, err := newOvalDB(b.Cnf, ovalFamily)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
		lastModified, err = driver.GetLastModified(ovalFamily, release)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return false, xerrors.Errorf("Failed to GetLastModified: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		url, _ := util.URLPathJoin(cnf.Conf.OvalDict.URL, "lastmodified", osFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Get(url).End()
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.OvalDict.URL, "lastmodified", ovalFamily, release)
 | 
			
		||||
		resp, body, errs := gorequest.New().Timeout(10 * time.Second).Get(url).End()
 | 
			
		||||
		if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
			return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
 | 
			
		||||
			return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %+v", url, resp, errs)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
 | 
			
		||||
@@ -70,10 +102,37 @@ func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool,
 | 
			
		||||
	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",
 | 
			
		||||
		logging.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, release, lastModified)
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("OVAL is fresh: %s %s ", osFamily, release)
 | 
			
		||||
	logging.Log.Infof("OVAL %s %s is fresh. lastModified: %s", osFamily, release, lastModified.Format(time.RFC3339))
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewOvalDB returns oval db client
 | 
			
		||||
func newOvalDB(cnf config.VulnDictInterface, familyInScanResult string) (driver db.DB, err error) {
 | 
			
		||||
	if cnf.IsFetchViaHTTP() {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path := cnf.GetURL()
 | 
			
		||||
	if cnf.GetType() == "sqlite3" {
 | 
			
		||||
		path = cnf.GetSQLite3Path()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ovalFamily, err := GetFamilyInOval(familyInScanResult)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	driver, locked, err := db.NewDB(ovalFamily, cnf.GetType(), path, cnf.GetDebugSQL())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if locked {
 | 
			
		||||
			err = xerrors.Errorf("SQLite3: %s is locked. err: %w", cnf.GetSQLite3Path(), err)
 | 
			
		||||
		}
 | 
			
		||||
		err = xerrors.Errorf("Failed to new OVAL DB. err: %w", err)
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return driver, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,30 +8,41 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/kotakanbe/goval-dictionary/db"
 | 
			
		||||
	ovalmodels "github.com/kotakanbe/goval-dictionary/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RedHatBase is the base struct for RedHat and CentOS
 | 
			
		||||
// RedHatBase is the base struct for RedHat, CentOS and Rocky
 | 
			
		||||
type RedHatBase struct {
 | 
			
		||||
	Base
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillWithOval returns scan result after updating CVE info by OVAL
 | 
			
		||||
func (o RedHatBase) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
func (o RedHatBase) FillWithOval(r *models.ScanResult) (nCVEs int, err error) {
 | 
			
		||||
	var relatedDefs ovalResult
 | 
			
		||||
	if config.Conf.OvalDict.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
 | 
			
		||||
	if o.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameViaHTTP(r, o.Cnf.GetURL()); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		driver, err := newOvalDB(o.Cnf, r.Family)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if err := driver.CloseDB(); err != nil {
 | 
			
		||||
				logging.Log.Errorf("Failed to close DB. err: %+v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if relatedDefs, err = getDefsByPackNameFromOvalDB(driver, r); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	relatedDefs.Sort()
 | 
			
		||||
	for _, defPacks := range relatedDefs.entries {
 | 
			
		||||
		nCVEs += o.update(r, defPacks)
 | 
			
		||||
	}
 | 
			
		||||
@@ -81,6 +92,7 @@ var kernelRelatedPackNames = map[string]bool{
 | 
			
		||||
	"kernel-tools":            true,
 | 
			
		||||
	"kernel-tools-libs":       true,
 | 
			
		||||
	"kernel-tools-libs-devel": true,
 | 
			
		||||
	"kernel-uek":              true,
 | 
			
		||||
	"perf":                    true,
 | 
			
		||||
	"python-perf":             true,
 | 
			
		||||
}
 | 
			
		||||
@@ -91,7 +103,7 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int)
 | 
			
		||||
		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)
 | 
			
		||||
			logging.Log.Debugf("%s is newly detected by OVAL: DefID: %s", cve.CveID, defPacks.def.DefinitionID)
 | 
			
		||||
			vinfo = models.VulnInfo{
 | 
			
		||||
				CveID:       cve.CveID,
 | 
			
		||||
				Confidences: models.Confidences{models.OvalMatch},
 | 
			
		||||
@@ -102,13 +114,12 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int)
 | 
			
		||||
			cveContents := vinfo.CveContents
 | 
			
		||||
			if v, ok := vinfo.CveContents[ctype]; ok {
 | 
			
		||||
				if v.LastModified.After(ovalContent.LastModified) {
 | 
			
		||||
					util.Log.Debugf("%s, OvalID: %d ignored: ",
 | 
			
		||||
						cve.CveID, defPacks.def.ID)
 | 
			
		||||
					logging.Log.Debugf("%s ignored. DefID: %s ", cve.CveID, defPacks.def.DefinitionID)
 | 
			
		||||
				} else {
 | 
			
		||||
					util.Log.Debugf("%s OVAL will be overwritten", cve.CveID)
 | 
			
		||||
					logging.Log.Debugf("%s OVAL will be overwritten. DefID: %s", cve.CveID, defPacks.def.DefinitionID)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				util.Log.Debugf("%s also detected by OVAL", cve.CveID)
 | 
			
		||||
				logging.Log.Debugf("%s also detected by OVAL. DefID: %s", cve.CveID, defPacks.def.DefinitionID)
 | 
			
		||||
				cveContents = models.CveContents{}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@@ -143,9 +154,12 @@ func (o RedHatBase) update(r *models.ScanResult, defPacks defPacks) (nCVEs int)
 | 
			
		||||
 | 
			
		||||
func (o RedHatBase) convertToDistroAdvisory(def *ovalmodels.Definition) *models.DistroAdvisory {
 | 
			
		||||
	advisoryID := def.Title
 | 
			
		||||
	if (o.family == config.RedHat || o.family == config.CentOS) && len(advisoryID) > 0 {
 | 
			
		||||
		ss := strings.Fields(def.Title)
 | 
			
		||||
		advisoryID = strings.TrimSuffix(ss[0], ":")
 | 
			
		||||
	switch o.family {
 | 
			
		||||
	case constant.RedHat, constant.CentOS, constant.Rocky, constant.Oracle:
 | 
			
		||||
		if def.Title != "" {
 | 
			
		||||
			ss := strings.Fields(def.Title)
 | 
			
		||||
			advisoryID = strings.TrimSuffix(ss[0], ":")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &models.DistroAdvisory{
 | 
			
		||||
		AdvisoryID:  advisoryID,
 | 
			
		||||
@@ -246,11 +260,12 @@ type RedHat struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRedhat creates OVAL client for Redhat
 | 
			
		||||
func NewRedhat() RedHat {
 | 
			
		||||
func NewRedhat(cnf config.VulnDictInterface) RedHat {
 | 
			
		||||
	return RedHat{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.RedHat,
 | 
			
		||||
				family: constant.RedHat,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -262,11 +277,12 @@ type CentOS struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewCentOS creates OVAL client for CentOS
 | 
			
		||||
func NewCentOS() CentOS {
 | 
			
		||||
func NewCentOS(cnf config.VulnDictInterface) CentOS {
 | 
			
		||||
	return CentOS{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.CentOS,
 | 
			
		||||
				family: constant.CentOS,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -278,11 +294,12 @@ type Oracle struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewOracle creates OVAL client for Oracle
 | 
			
		||||
func NewOracle() Oracle {
 | 
			
		||||
func NewOracle(cnf config.VulnDictInterface) Oracle {
 | 
			
		||||
	return Oracle{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Oracle,
 | 
			
		||||
				family: constant.Oracle,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
@@ -295,11 +312,30 @@ type Amazon struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewAmazon creates OVAL client for Amazon Linux
 | 
			
		||||
func NewAmazon() Amazon {
 | 
			
		||||
func NewAmazon(cnf config.VulnDictInterface) Amazon {
 | 
			
		||||
	return Amazon{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: config.Amazon,
 | 
			
		||||
				family: constant.Amazon,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Rocky is the interface for RedhatBase OVAL
 | 
			
		||||
type Rocky struct {
 | 
			
		||||
	// Base
 | 
			
		||||
	RedHatBase
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewRocky creates OVAL client for Rocky Linux
 | 
			
		||||
func NewRocky(cnf config.VulnDictInterface) Rocky {
 | 
			
		||||
	return Rocky{
 | 
			
		||||
		RedHatBase{
 | 
			
		||||
			Base{
 | 
			
		||||
				family: constant.Rocky,
 | 
			
		||||
				Cnf:    cnf,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@ 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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -132,7 +130,7 @@ func TestPackNamesOfUpdate(t *testing.T) {
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log = util.NewCustomLogger(config.ServerInfo{})
 | 
			
		||||
	// util.Log = util.Logger{}.NewCustomLogger()
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		RedHat{}.update(&tt.in, tt.defPacks)
 | 
			
		||||
		e := tt.out.ScannedCves["CVE-2000-1000"].AffectedPackages
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user