Compare commits
	
		
			153 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					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 | ||
| 
						 | 
					a8c0926b4f | ||
| 
						 | 
					dd2959a31b | ||
| 
						 | 
					51099f42c3 | ||
| 
						 | 
					63f170cc7a | ||
| 
						 | 
					3c1489e588 | ||
| 
						 | 
					e4f1e03f62 | ||
| 
						 | 
					83d48ec990 | ||
| 
						 | 
					b20d2b2684 | ||
| 
						 | 
					2b918c70ae | ||
| 
						 | 
					1100c133ba | ||
| 
						 | 
					88899f0e89 | ||
| 
						 | 
					59dc0059bc | ||
| 
						 | 
					986fb304c0 | ||
| 
						 | 
					d6435d2885 | ||
| 
						 | 
					affb456499 | ||
| 
						 | 
					705ed0a0ac | ||
| 
						 | 
					dfffe5b508 | ||
| 
						 | 
					fca102edba | ||
| 
						 | 
					554b6345a2 | ||
| 
						 | 
					aa954dc84c | ||
| 
						 | 
					b5506a1368 | ||
| 
						 | 
					0b55f94828 | ||
| 
						 | 
					a67052f48c | ||
| 
						 | 
					6eff6a9329 | ||
| 
						 | 
					69d32d4511 | ||
| 
						 | 
					d7a613b710 | ||
| 
						 | 
					669c019287 | ||
| 
						 | 
					fcc4901a10 | ||
| 
						 | 
					4359503484 | ||
| 
						 | 
					b13f93a2d3 | ||
| 
						 | 
					8405e0fad6 | ||
| 
						 | 
					aceb3f1826 | ||
| 
						 | 
					a206675f3e | ||
| 
						 | 
					f4253d74ae | ||
| 
						 | 
					aaea15e516 | ||
| 
						 | 
					83d1f80959 | ||
| 
						 | 
					a33cff8f13 | ||
| 
						 | 
					8679759f60 | ||
| 
						 | 
					53deaee3d7 | ||
| 
						 | 
					5a14a58fe4 | ||
| 
						 | 
					fb1fbf8f95 | ||
| 
						 | 
					cfbf779f9b | ||
| 
						 | 
					d576b6c6c1 | ||
| 
						 | 
					514eb71482 | ||
| 
						 | 
					43ed904db1 | ||
| 
						 | 
					0a440ca629 | ||
| 
						 | 
					eff1dbf95b | ||
| 
						 | 
					9a32a94806 | ||
| 
						 | 
					2534098509 | ||
| 
						 | 
					9497365758 | ||
| 
						 | 
					101c44c9c0 | ||
| 
						 | 
					ffd745c004 | ||
| 
						 | 
					5fea4eaef8 | ||
| 
						 | 
					1f610043cf | ||
| 
						 | 
					3f8de02683 | ||
| 
						 | 
					d02535d053 | ||
| 
						 | 
					75fceff5f7 | ||
| 
						 | 
					ebd3834a35 | ||
| 
						 | 
					93059b74c3 | ||
| 
						 | 
					2fc3462d35 | ||
| 
						 | 
					f78dab50cb | ||
| 
						 | 
					edb324c3d9 | ||
| 
						 | 
					83bcca6e66 | ||
| 
						 | 
					a124518d78 | ||
| 
						 | 
					94bf630e29 | ||
| 
						 | 
					31bb33fd90 | ||
| 
						 | 
					4b680b9960 | ||
| 
						 | 
					8a8ab8cb18 | ||
| 
						 | 
					8146f5fd1b | ||
| 
						 | 
					425c585e47 | ||
| 
						 | 
					4f1578b2d6 | ||
| 
						 | 
					7969b343b0 | ||
| 
						 | 
					58cf1f4c8e | ||
| 
						 | 
					a5b87af862 | ||
| 
						 | 
					a0e592b934 | ||
| 
						 | 
					7eccc538bb | ||
| 
						 | 
					59daa8570a | ||
| 
						 | 
					3f52d318bc | ||
| 
						 | 
					11a7a0c934 | ||
| 
						 | 
					89f49b0e29 | ||
| 
						 | 
					72457cbf8e | ||
| 
						 | 
					c11ba27509 | ||
| 
						 | 
					8a611f9ba6 | ||
| 
						 | 
					4a73875e4d | ||
| 
						 | 
					d9d5e612ff | ||
| 
						 | 
					4d8599e4fc | ||
| 
						 | 
					59c7061d29 | ||
| 
						 | 
					996557c667 | ||
| 
						 | 
					519fb19a77 | ||
| 
						 | 
					36456cb151 | ||
| 
						 | 
					4ae87cc36c | ||
| 
						 | 
					b37df89fb1 | ||
| 
						 | 
					d18e7a751d | ||
| 
						 | 
					8d5ea98e50 | ||
| 
						 | 
					835dc08049 | ||
| 
						 | 
					62c9409fe9 | ||
| 
						 | 
					2374f578ed | ||
| 
						 | 
					34e2f033d8 | ||
| 
						 | 
					420825cacc | ||
| 
						 | 
					466ec93d8e | ||
| 
						 | 
					3f5bb6ab29 | ||
| 
						 | 
					ebe5f858c8 | ||
| 
						 | 
					9dd025437b | ||
| 
						 | 
					c0ebac305a | ||
| 
						 | 
					1f23ab7ba4 | ||
| 
						 | 
					ea3b63998d | ||
| 
						 | 
					3093426458 | ||
| 
						 | 
					37716feac7 | ||
| 
						 | 
					56b12c38d2 | ||
| 
						 | 
					749ead5d4a | 
							
								
								
									
										67
									
								
								.github/workflows/codeql-analysis.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,67 @@
 | 
			
		||||
# For most projects, this workflow file will not need changing; you simply need
 | 
			
		||||
# to commit it to your repository.
 | 
			
		||||
#
 | 
			
		||||
# You may wish to alter this file to override the set of languages analyzed,
 | 
			
		||||
# or to provide custom queries or build logic.
 | 
			
		||||
#
 | 
			
		||||
# ******** NOTE ********
 | 
			
		||||
# We have attempted to detect the languages in your repository. Please check
 | 
			
		||||
# the `language` matrix defined below to confirm you have the correct set of
 | 
			
		||||
# supported CodeQL languages.
 | 
			
		||||
#
 | 
			
		||||
name: "CodeQL"
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches: [ master ]
 | 
			
		||||
  pull_request:
 | 
			
		||||
    # The branches below must be a subset of the branches above
 | 
			
		||||
    branches: [ master ]
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '32 20 * * 0'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  analyze:
 | 
			
		||||
    name: Analyze
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        language: [ 'go' ]
 | 
			
		||||
        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
 | 
			
		||||
        # Learn more:
 | 
			
		||||
        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout repository
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
    # Initializes the CodeQL tools for scanning.
 | 
			
		||||
    - name: Initialize CodeQL
 | 
			
		||||
      uses: github/codeql-action/init@v1
 | 
			
		||||
      with:
 | 
			
		||||
        languages: ${{ matrix.language }}
 | 
			
		||||
        # If you wish to specify custom queries, you can do so here or in a config file.
 | 
			
		||||
        # By default, queries listed here will override any specified in a config file.
 | 
			
		||||
        # Prefix the list here with "+" to use these queries and those in the config file.
 | 
			
		||||
        # queries: ./path/to/local/query, your-org/your-repo/queries@main
 | 
			
		||||
 | 
			
		||||
    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
 | 
			
		||||
    # If this step fails, then you should remove it and run the build manually (see below)
 | 
			
		||||
    - name: Autobuild
 | 
			
		||||
      uses: github/codeql-action/autobuild@v1
 | 
			
		||||
 | 
			
		||||
    # ℹ️ Command-line programs to run using the OS shell.
 | 
			
		||||
    # 📚 https://git.io/JvXDl
 | 
			
		||||
 | 
			
		||||
    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
 | 
			
		||||
    #    and modify them (or add more) to build your code if your project
 | 
			
		||||
    #    uses a compiled language
 | 
			
		||||
 | 
			
		||||
    #- run: |
 | 
			
		||||
    #   make bootstrap
 | 
			
		||||
    #   make release
 | 
			
		||||
 | 
			
		||||
    - name: Perform CodeQL Analysis
 | 
			
		||||
      uses: github/codeql-action/analyze@v1
 | 
			
		||||
							
								
								
									
										29
									
								
								.github/workflows/golangci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,29 @@
 | 
			
		||||
name: golangci-lint
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - v*
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  pull_request:
 | 
			
		||||
jobs:
 | 
			
		||||
  golangci:
 | 
			
		||||
    name: lint
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: golangci-lint
 | 
			
		||||
        uses: golangci/golangci-lint-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
 | 
			
		||||
          version: v1.32
 | 
			
		||||
          args: --timeout=10m
 | 
			
		||||
          
 | 
			
		||||
          # Optional: working directory, useful for monorepos
 | 
			
		||||
          # working-directory: somedir
 | 
			
		||||
 | 
			
		||||
          # Optional: golangci-lint command line arguments.
 | 
			
		||||
          # args: --issues-exit-code=0
 | 
			
		||||
 | 
			
		||||
          # Optional: show only new issues if it's a pull request. The default value is `false`.
 | 
			
		||||
          # only-new-issues: true
 | 
			
		||||
							
								
								
									
										31
									
								
								.github/workflows/goreleaser.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,31 @@
 | 
			
		||||
name: goreleaser
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - '*'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  goreleaser:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      -
 | 
			
		||||
        name: Checkout
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
      -
 | 
			
		||||
        name: Unshallow
 | 
			
		||||
        run: git fetch --prune --unshallow
 | 
			
		||||
      -
 | 
			
		||||
        name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v2
 | 
			
		||||
        with:
 | 
			
		||||
          go-version: 1.16
 | 
			
		||||
      -
 | 
			
		||||
        name: Run GoReleaser
 | 
			
		||||
        uses: goreleaser/goreleaser-action@v2
 | 
			
		||||
        with:
 | 
			
		||||
          version: latest
 | 
			
		||||
          args: release --rm-dist
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,21 @@
 | 
			
		||||
name: Test
 | 
			
		||||
 | 
			
		||||
on: [pull_request]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
    name: Build
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
 | 
			
		||||
    - name: Set up Go 1.x
 | 
			
		||||
      uses: actions/setup-go@v2
 | 
			
		||||
      with:
 | 
			
		||||
        go-version: 1.16.x
 | 
			
		||||
      id: go
 | 
			
		||||
 | 
			
		||||
    - name: Check out code into the Go module directory
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
    - name: Test
 | 
			
		||||
      run: make test
 | 
			
		||||
							
								
								
									
										22
									
								
								.github/workflows/tidy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
			
		||||
name: go-mod-tidy-pr
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: "0 0 * * 1" # Weekly build
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  go-mod-tidy-pr:
 | 
			
		||||
    name: go-mod-tidy-pr
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
      - name: Run go-mod-tidy-pr
 | 
			
		||||
        uses: sue445/go-mod-tidy-pr@master
 | 
			
		||||
        with:
 | 
			
		||||
          github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          git_user_name: kotakanbe
 | 
			
		||||
          git_user_email: kotakanbe@gmail.com
 | 
			
		||||
          go_version: 1.16.x
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,7 +1,5 @@
 | 
			
		||||
vuls
 | 
			
		||||
.vscode
 | 
			
		||||
*.txt
 | 
			
		||||
*.json
 | 
			
		||||
*.sqlite3*
 | 
			
		||||
*.db
 | 
			
		||||
tags
 | 
			
		||||
@@ -11,8 +9,8 @@ issues/
 | 
			
		||||
vendor/
 | 
			
		||||
log/
 | 
			
		||||
results/
 | 
			
		||||
*config.toml
 | 
			
		||||
config.toml
 | 
			
		||||
!setup/docker/*
 | 
			
		||||
.DS_Store
 | 
			
		||||
dist/
 | 
			
		||||
.idea
 | 
			
		||||
.idea
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
name: golang-ci
 | 
			
		||||
 | 
			
		||||
linters-settings:
 | 
			
		||||
  errcheck:
 | 
			
		||||
    #exclude: /path/to/file.txt
 | 
			
		||||
 | 
			
		||||
linters:
 | 
			
		||||
  disable-all: true
 | 
			
		||||
  enable:
 | 
			
		||||
    - goimports
 | 
			
		||||
    - golint
 | 
			
		||||
    - govet
 | 
			
		||||
    - misspell
 | 
			
		||||
    - errcheck
 | 
			
		||||
    - staticcheck
 | 
			
		||||
    - prealloc
 | 
			
		||||
    - ineffassign
 | 
			
		||||
@@ -6,22 +6,104 @@ release:
 | 
			
		||||
    owner: future-architect
 | 
			
		||||
    name: vuls
 | 
			
		||||
builds:
 | 
			
		||||
- goos:
 | 
			
		||||
- id: vuls
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - amd64
 | 
			
		||||
  main: .
 | 
			
		||||
  main: ./cmd/vuls/main.go
 | 
			
		||||
  flags:
 | 
			
		||||
      - -a
 | 
			
		||||
  ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}} 
 | 
			
		||||
  - -a
 | 
			
		||||
  ldflags: 
 | 
			
		||||
  - -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
 | 
			
		||||
  binary: vuls
 | 
			
		||||
archive:
 | 
			
		||||
 | 
			
		||||
- id: vuls-scanner
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
  - arm
 | 
			
		||||
  - arm64
 | 
			
		||||
  main: ./cmd/scanner/main.go
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  - -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
 | 
			
		||||
 | 
			
		||||
- id: trivy-to-vuls
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
  - arm
 | 
			
		||||
  - arm64
 | 
			
		||||
  main: ./contrib/trivy/cmd/main.go
 | 
			
		||||
  binary: trivy-to-vuls
 | 
			
		||||
 | 
			
		||||
- id: future-vuls
 | 
			
		||||
  env:
 | 
			
		||||
  - CGO_ENABLED=0
 | 
			
		||||
  goos:
 | 
			
		||||
  - linux
 | 
			
		||||
  goarch:
 | 
			
		||||
  - 386
 | 
			
		||||
  - amd64
 | 
			
		||||
  - arm
 | 
			
		||||
  - arm64
 | 
			
		||||
  flags:
 | 
			
		||||
  - -a
 | 
			
		||||
  - -tags=scanner
 | 
			
		||||
  main: ./contrib/future-vuls/cmd/main.go
 | 
			
		||||
  binary: future-vuls
 | 
			
		||||
 | 
			
		||||
archives:
 | 
			
		||||
 | 
			
		||||
- id: vuls
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
 | 
			
		||||
  builds:
 | 
			
		||||
  - vuls
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
- id: vuls-scanner
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
 | 
			
		||||
  builds:
 | 
			
		||||
  - vuls-scanner
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
- id: trivy-to-vuls
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
 | 
			
		||||
  builds:
 | 
			
		||||
  - trivy-to-vuls
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
 | 
			
		||||
- id: future-vuls
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
 | 
			
		||||
  builds:
 | 
			
		||||
  - future-vuls
 | 
			
		||||
  format: tar.gz
 | 
			
		||||
  name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{
 | 
			
		||||
    .Arm }}{{ end }}'
 | 
			
		||||
  files:
 | 
			
		||||
  - LICENSE
 | 
			
		||||
  - NOTICE
 | 
			
		||||
  - README*
 | 
			
		||||
  - CHANGELOG.md
 | 
			
		||||
snapshot:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +0,0 @@
 | 
			
		||||
language: go
 | 
			
		||||
 | 
			
		||||
go:
 | 
			
		||||
  - "1.13.x"
 | 
			
		||||
 | 
			
		||||
after_success:
 | 
			
		||||
  - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash
 | 
			
		||||
@@ -11,7 +11,7 @@ COPY . $GOPATH/src/$REPOSITORY
 | 
			
		||||
RUN cd $GOPATH/src/$REPOSITORY && make install
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FROM alpine:3.7
 | 
			
		||||
FROM alpine:3.11
 | 
			
		||||
 | 
			
		||||
MAINTAINER hikachan sadayuki-matsuno
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										137
									
								
								GNUmakefile
									
									
									
									
									
								
							
							
						
						@@ -20,19 +20,26 @@ BUILDTIME := $(shell date "+%Y%m%d_%H%M%S")
 | 
			
		||||
LDFLAGS := -X 'github.com/future-architect/vuls/config.Version=$(VERSION)' \
 | 
			
		||||
    -X 'github.com/future-architect/vuls/config.Revision=build-$(BUILDTIME)_$(REVISION)'
 | 
			
		||||
GO := GO111MODULE=on go
 | 
			
		||||
CGO_UNABLED := CGO_ENABLED=0 go
 | 
			
		||||
GO_OFF := GO111MODULE=off go
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
all: build
 | 
			
		||||
 | 
			
		||||
build: main.go pretest fmt
 | 
			
		||||
	$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls $<
 | 
			
		||||
build: ./cmd/vuls/main.go pretest fmt
 | 
			
		||||
	$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
b: 	main.go pretest fmt
 | 
			
		||||
	$(GO) build -ldflags "$(LDFLAGS)" -o vuls $<
 | 
			
		||||
b: ./cmd/vuls/main.go 
 | 
			
		||||
	$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
install: main.go pretest
 | 
			
		||||
	$(GO) install -ldflags "$(LDFLAGS)"
 | 
			
		||||
install: ./cmd/vuls/main.go pretest fmt
 | 
			
		||||
	$(GO) install -ldflags "$(LDFLAGS)" ./cmd/vuls
 | 
			
		||||
 | 
			
		||||
build-scanner: ./cmd/scanner/main.go pretest fmt
 | 
			
		||||
	$(CGO_UNABLED) build -tags=scanner -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/scanner
 | 
			
		||||
 | 
			
		||||
install-scanner: ./cmd/scanner/main.go pretest fmt
 | 
			
		||||
	$(CGO_UNABLED) install -tags=scanner -ldflags "$(LDFLAGS)" ./cmd/scanner
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
	$(GO_OFF) get -u golang.org/x/lint/golint
 | 
			
		||||
@@ -66,3 +73,121 @@ cov:
 | 
			
		||||
clean:
 | 
			
		||||
	echo $(PKGS) | xargs go clean || exit;
 | 
			
		||||
 | 
			
		||||
# trivy-to-vuls
 | 
			
		||||
build-trivy-to-vuls: pretest fmt
 | 
			
		||||
	$(GO) build -o trivy-to-vuls contrib/trivy/cmd/*.go
 | 
			
		||||
 | 
			
		||||
# future-vuls
 | 
			
		||||
build-future-vuls: pretest fmt
 | 
			
		||||
	$(GO) build -o future-vuls contrib/future-vuls/cmd/*.go
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# integration-test
 | 
			
		||||
BASE_DIR := '${PWD}/integration/results'
 | 
			
		||||
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)'
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	mv ${BASE_DIR}/* /tmp
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml $(ARGS)
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml  $(ARGS)
 | 
			
		||||
	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' {} \;
 | 
			
		||||
	diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	mv ${BASE_DIR}/* /tmp
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.old report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml 
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml 
 | 
			
		||||
	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' {} \;
 | 
			
		||||
	diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
 | 
			
		||||
diff-rdb-redis:
 | 
			
		||||
	mv ${BASE_DIR}/* /tmp
 | 
			
		||||
	mkdir -p ${NOW_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${NOW_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-config.toml 
 | 
			
		||||
	mkdir -p ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	cp integration/data/*.json ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	./vuls.new report --format-json --refresh-cve --results-dir=${BASE_DIR} -config=./integration/int-redis-config.toml 
 | 
			
		||||
	# remove reportedAt line
 | 
			
		||||
	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' {} \;
 | 
			
		||||
	# remove "Type": line
 | 
			
		||||
	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' {} \;
 | 
			
		||||
	# remove "SQLite3Path": line
 | 
			
		||||
	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' {} \;
 | 
			
		||||
	diff -c ${NOW_JSON_DIR} ${ONE_SEC_AFTER_JSON_DIR}
 | 
			
		||||
	echo "old: ${NOW_JSON_DIR} , new: ${ONE_SEC_AFTER_JSON_DIR}"
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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}
 | 
			
		||||
 | 
			
		||||
	git checkout ${branch}
 | 
			
		||||
	git stash apply stash@\{0\}
 | 
			
		||||
 | 
			
		||||
	# working tree
 | 
			
		||||
	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
 | 
			
		||||
	# $ make int 
 | 
			
		||||
	# $ make int-redis
 | 
			
		||||
							
								
								
									
										90
									
								
								README.md
									
									
									
									
									
								
							
							
						
						@@ -9,7 +9,7 @@
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
Vulnerability scanner for Linux/FreeBSD, agentless, written in golang.
 | 
			
		||||
Vulnerability scanner for Linux/FreeBSD, agent-less, written in Go.
 | 
			
		||||
We have a slack team. [Join slack team](http://goo.gl/forms/xm5KFo35tu)
 | 
			
		||||
Twitter: [@vuls_en](https://twitter.com/vuls_en)
 | 
			
		||||
 | 
			
		||||
@@ -23,20 +23,6 @@ Twitter: [@vuls_en](https://twitter.com/vuls_en)
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
## NEWS
 | 
			
		||||
 | 
			
		||||
| Version     | Main Feature |  Date |
 | 
			
		||||
|:------------|:---------------------------------|:--------------------|
 | 
			
		||||
| [v0.8.0](https://github.com/future-architect/vuls/releases/tag/v0.8.0) | secret | Coming soon |
 | 
			
		||||
| [v0.7.0](https://github.com/future-architect/vuls/releases/tag/v0.7.0) | WordPress Vulnerability Scan | 2019/Apr/8 |
 | 
			
		||||
| [v0.6.3](https://github.com/future-architect/vuls/releases/tag/v0.6.3) | GitHub Integration | 2019/Feb/20 |
 | 
			
		||||
| [v0.6.2](https://github.com/future-architect/vuls/releases/tag/v0.6.2) | Add US-CERT/JPCERT Alerts as VulnSrc | 2019/Jan/23 |
 | 
			
		||||
| [v0.6.1](https://github.com/future-architect/vuls/releases/tag/v0.6.1) | BugFix | 2018/Nov/16 |
 | 
			
		||||
| [v0.6.0](https://github.com/future-architect/vuls/releases/tag/v0.6.0) | Add ExploitDB as VulnSrc | 2018/Nov/3 |
 | 
			
		||||
| [v0.5.0](https://github.com/future-architect/vuls/releases/tag/v0.5.0) | Scan accuracy improvement | 2018/Aug/27 |
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
## Abstract
 | 
			
		||||
 | 
			
		||||
For a system administrator, having to perform security vulnerability analysis and software update on a daily basis can be a burden.
 | 
			
		||||
@@ -66,36 +52,47 @@ Vuls is a tool created to solve the problems listed above. It has the following
 | 
			
		||||
 | 
			
		||||
- Alpine, Amazon Linux, CentOS, Debian, Oracle Linux, Raspbian, RHEL, SUSE Enterprise Linux, and Ubuntu
 | 
			
		||||
- FreeBSD
 | 
			
		||||
- Cloud, on-premise, Docker Container and Docker Image
 | 
			
		||||
- Cloud, on-premise, Running Docker Container
 | 
			
		||||
 | 
			
		||||
### High-quality scan
 | 
			
		||||
 | 
			
		||||
Vuls uses multiple vulnerability databases
 | 
			
		||||
- Vulnerability Database
 | 
			
		||||
  - [NVD](https://nvd.nist.gov/)
 | 
			
		||||
  - [JVN(Japanese)](http://jvndb.jvn.jp/apis/myjvn/)
 | 
			
		||||
 | 
			
		||||
- [NVD](https://nvd.nist.gov/)
 | 
			
		||||
- [JVN(Japanese)](http://jvndb.jvn.jp/apis/myjvn/)
 | 
			
		||||
- OVAL
 | 
			
		||||
  - [Red Hat](https://www.redhat.com/security/data/oval/)
 | 
			
		||||
  - [Debian](https://www.debian.org/security/oval/)
 | 
			
		||||
  - [Oracle Linux](https://linux.oracle.com/security/oval/)
 | 
			
		||||
  - [RedHat](https://www.redhat.com/security/data/oval/)
 | 
			
		||||
  - [SUSE](http://ftp.suse.com/pub/projects/security/oval/)
 | 
			
		||||
  - [Ubuntu](https://people.canonical.com/~ubuntu-security/oval/)
 | 
			
		||||
  - [SUSE](http://ftp.suse.com/pub/projects/security/oval/)
 | 
			
		||||
  - [Oracle Linux](https://linux.oracle.com/security/oval/)
 | 
			
		||||
 | 
			
		||||
- [Alpine-secdb](https://git.alpinelinux.org/cgit/alpine-secdb/)
 | 
			
		||||
- [Debian Security Bug Tracker](https://security-tracker.debian.org/tracker/)
 | 
			
		||||
- [Red Hat Security Advisories](https://access.redhat.com/security/security-updates/)
 | 
			
		||||
- Commands (yum, zypper, and pkg-audit)
 | 
			
		||||
  - RHSA/ALAS/ELSA/FreeBSD-SA
 | 
			
		||||
- [Exploit Database](https://www.exploit-db.com/)
 | 
			
		||||
- [US-CERT](https://www.us-cert.gov/ncas/alerts)
 | 
			
		||||
- [JPCERT](http://www.jpcert.or.jp/at/2019.html)
 | 
			
		||||
- [WPVulnDB](https://wpvulndb.com/api)
 | 
			
		||||
- [Node.js Security Working Group](https://github.com/nodejs/security-wg)
 | 
			
		||||
- [Ruby Advisory Database](https://github.com/rubysec/ruby-advisory-db)
 | 
			
		||||
- [Safety DB(Python)](https://github.com/pyupio/safety-db)
 | 
			
		||||
- [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories)
 | 
			
		||||
- [RustSec Advisory Database](https://github.com/RustSec/advisory-db)
 | 
			
		||||
- Changelog
 | 
			
		||||
- Security Advisory
 | 
			
		||||
  - [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/)
 | 
			
		||||
 | 
			
		||||
- Commands(yum, zypper, pkg-audit)
 | 
			
		||||
  - RHSA / ALAS / ELSA / FreeBSD-SA
 | 
			
		||||
  - Changelog
 | 
			
		||||
 | 
			
		||||
- PoC, Exploit
 | 
			
		||||
  - [Exploit Database](https://www.exploit-db.com/)
 | 
			
		||||
  - [Metasploit-Framework modules](https://www.rapid7.com/db/?q=&type=metasploit)
 | 
			
		||||
 | 
			
		||||
- CERT
 | 
			
		||||
  - [US-CERT](https://www.us-cert.gov/ncas/alerts)
 | 
			
		||||
  - [JPCERT](http://www.jpcert.or.jp/at/2019.html)
 | 
			
		||||
 | 
			
		||||
- Libraries
 | 
			
		||||
  - [Node.js Security Working Group](https://github.com/nodejs/security-wg)
 | 
			
		||||
  - [Ruby Advisory Database](https://github.com/rubysec/ruby-advisory-db)
 | 
			
		||||
  - [Safety DB(Python)](https://github.com/pyupio/safety-db)
 | 
			
		||||
  - [PHP Security Advisories Database](https://github.com/FriendsOfPHP/security-advisories)
 | 
			
		||||
  - [RustSec Advisory Database](https://github.com/RustSec/advisory-db)
 | 
			
		||||
 | 
			
		||||
- WordPress
 | 
			
		||||
  - [wpscan](https://wpscan.com/api)
 | 
			
		||||
 | 
			
		||||
### Scan mode
 | 
			
		||||
 | 
			
		||||
@@ -134,17 +131,6 @@ Vuls uses multiple vulnerability databases
 | 
			
		||||
- It is possible to acquire the state of the server by connecting via SSH and executing the command.
 | 
			
		||||
- Vuls warns when the scan target server was updated the kernel etc. but not restarting it.
 | 
			
		||||
 | 
			
		||||
### **Static** Analysis
 | 
			
		||||
 | 
			
		||||
Vuls v0.8.0 can scan Docker images using [knqyf263/trivy](https://github.com/knqyf263/trivy).
 | 
			
		||||
Following Registry supported.
 | 
			
		||||
 | 
			
		||||
- ECR
 | 
			
		||||
- GCR
 | 
			
		||||
- Local Image
 | 
			
		||||
 | 
			
		||||
For details, see [Scan docker image](https://vuls.io/docs/en/tutorial-scan-docker-image.html)
 | 
			
		||||
 | 
			
		||||
### Scan vulnerabilities of non-OS-packages
 | 
			
		||||
 | 
			
		||||
- Libraries of programming language
 | 
			
		||||
@@ -182,7 +168,7 @@ Vuls has some options to detect the vulnerabilities
 | 
			
		||||
 | 
			
		||||
## Document
 | 
			
		||||
 | 
			
		||||
For more information such as Installation, Tutorial, Usage, visit [vuls.io](https://vuls.io/)
 | 
			
		||||
For more information such as Installation, Tutorial, Usage, visit [vuls.io](https://vuls.io/)  
 | 
			
		||||
[日本語翻訳ドキュメント](https://vuls.io/ja/)
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
@@ -191,11 +177,9 @@ For more information such as Installation, Tutorial, Usage, visit [vuls.io](http
 | 
			
		||||
 | 
			
		||||
kotakanbe ([@kotakanbe](https://twitter.com/kotakanbe)) created vuls and [these fine people](https://github.com/future-architect/vuls/graphs/contributors) have contributed.
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
## Contribute
 | 
			
		||||
 | 
			
		||||
## Change Log
 | 
			
		||||
 | 
			
		||||
Please see [CHANGELOG](https://github.com/future-architect/vuls/blob/master/CHANGELOG.md).
 | 
			
		||||
see [vulsdoc](https://vuls.io/docs/en/how-to-contribute.html)
 | 
			
		||||
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								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 {
 | 
			
		||||
@@ -141,7 +141,7 @@ func (b Bolt) PrettyPrint(meta Meta) error {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetChangelog get the changelgo of specified packName from the Bucket
 | 
			
		||||
// GetChangelog get the changelog of specified packName from the Bucket
 | 
			
		||||
func (b Bolt) GetChangelog(servername, packName string) (changelog string, err error) {
 | 
			
		||||
	err = b.db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								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)
 | 
			
		||||
@@ -46,7 +46,7 @@ func TestSetupBolt(t *testing.T) {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
	_ = db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(metabucket))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -87,7 +87,7 @@ func TestEnsureBuckets(t *testing.T) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Errorf("Failed to open bolt: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
	_ = db.View(func(tx *bolt.Tx) error {
 | 
			
		||||
		bkt := tx.Bucket([]byte(servername))
 | 
			
		||||
		if bkt == nil {
 | 
			
		||||
			t.Errorf("Meta bucket nof found")
 | 
			
		||||
@@ -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)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								cmd/scanner/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,36 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	commands "github.com/future-architect/vuls/subcmds"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	subcommands.Register(subcommands.HelpCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.FlagsCommand(), "")
 | 
			
		||||
	subcommands.Register(subcommands.CommandsCommand(), "")
 | 
			
		||||
	subcommands.Register(&commands.DiscoverCmd{}, "discover")
 | 
			
		||||
	subcommands.Register(&commands.ScanCmd{}, "scan")
 | 
			
		||||
	subcommands.Register(&commands.HistoryCmd{}, "history")
 | 
			
		||||
	subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
 | 
			
		||||
	subcommands.Register(&commands.SaaSCmd{}, "saas")
 | 
			
		||||
 | 
			
		||||
	var v = flag.Bool("v", false, "Show version")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
	if *v {
 | 
			
		||||
		fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
 | 
			
		||||
		os.Exit(int(subcommands.ExitSuccess))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	os.Exit(int(subcommands.Execute(ctx)))
 | 
			
		||||
}
 | 
			
		||||
@@ -7,8 +7,8 @@ import (
 | 
			
		||||
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/commands"
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	commands "github.com/future-architect/vuls/subcmds"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -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))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -1,424 +0,0 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ReportCmd is subcommand for reporting
 | 
			
		||||
type ReportCmd struct {
 | 
			
		||||
	configPath  string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
	httpConf    c.HTTPConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ReportCmd) Name() string { return "report" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ReportCmd) Synopsis() string { return "Reporting" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ReportCmd) Usage() string {
 | 
			
		||||
	return `report:
 | 
			
		||||
	report
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-ignore-github-dismissed]
 | 
			
		||||
		[-to-email]
 | 
			
		||||
		[-to-http]
 | 
			
		||||
		[-to-slack]
 | 
			
		||||
		[-to-stride]
 | 
			
		||||
		[-to-hipchat]
 | 
			
		||||
		[-to-chatwork]
 | 
			
		||||
		[-to-telegram]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-to-s3]
 | 
			
		||||
		[-to-azure-blob]
 | 
			
		||||
		[-to-saas]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-format-xml]
 | 
			
		||||
		[-format-one-email]
 | 
			
		||||
		[-format-one-line-text]
 | 
			
		||||
		[-format-list]
 | 
			
		||||
		[-format-full-text]
 | 
			
		||||
		[-gzip]
 | 
			
		||||
		[-uuid]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-quiet]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
		[-http="http://vuls-report-server"]
 | 
			
		||||
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Quiet, "quiet", false, "Quiet mode. No output on stdout")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.RefreshCve, "refresh-cve", false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreGitHubDismissed, "ignore-github-dismissed", false,
 | 
			
		||||
		"Don't report the dismissed CVEs on GitHub Security Alerts")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(
 | 
			
		||||
		&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatXML, "format-xml", false, "XML format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatOneEMail, "format-one-email", false,
 | 
			
		||||
		"Send all the host report via only one EMail (Specify with -to-email)")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatOneLineText, "format-one-line-text", false,
 | 
			
		||||
		"One line summary in plain text")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatList, "format-list", false, "Display as list format")
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatFullText, "format-full-text", false,
 | 
			
		||||
		"Detail report in plain text")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSlack, "to-slack", false, "Send report via Slack")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToStride, "to-stride", false, "Send report via Stride")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToHipChat, "to-hipchat", false, "Send report via hipchat")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToChatWork, "to-chatwork", false, "Send report via chatwork")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToTelegram, "to-telegram", false, "Send report via Telegram")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToEmail, "to-email", false, "Send report via Email")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSyslog, "to-syslog", false, "Send report via Syslog")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToS3, "to-s3", false,
 | 
			
		||||
		"Write report to S3 (bucket/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToHTTP, "to-http", false, "Send report via HTTP POST")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToAzureBlob, "to-azure-blob", false,
 | 
			
		||||
		"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
 | 
			
		||||
	f.BoolVar(&c.Conf.ToSaas, "to-saas", false,
 | 
			
		||||
		"Upload report to Future Vuls(https://vuls.biz/) before report")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.GZIP, "gzip", false, "gzip compression")
 | 
			
		||||
	f.BoolVar(&c.Conf.UUID, "uuid", false,
 | 
			
		||||
		"Auto generate of scan target servers and then write to config.toml and scan result")
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use args passed via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.httpConf.URL, "http", "", "-to-http http://vuls-report")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, %+v", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
	c.Conf.HTTP.Overwrite(p.httpConf)
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// report
 | 
			
		||||
	reports := []report.ResultWriter{
 | 
			
		||||
		report.StdoutWriter{},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToSlack {
 | 
			
		||||
		reports = append(reports, report.SlackWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToStride {
 | 
			
		||||
		reports = append(reports, report.StrideWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToHipChat {
 | 
			
		||||
		reports = append(reports, report.HipChatWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToChatWork {
 | 
			
		||||
		reports = append(reports, report.ChatWorkWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToTelegram {
 | 
			
		||||
		reports = append(reports, report.TelegramWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToEmail {
 | 
			
		||||
		reports = append(reports, report.EMailWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToSyslog {
 | 
			
		||||
		reports = append(reports, report.SyslogWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToHTTP {
 | 
			
		||||
		reports = append(reports, report.HTTPRequestWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToLocalFile {
 | 
			
		||||
		reports = append(reports, report.LocalFileWriter{
 | 
			
		||||
			CurrentDir: dir,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToS3 {
 | 
			
		||||
		if err := report.CheckIfBucketExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a bucket beforehand: %s, err: %+v",
 | 
			
		||||
				c.Conf.AWS.S3Bucket, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.S3Writer{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToAzureBlob {
 | 
			
		||||
		if len(c.Conf.Azure.AccountName) == 0 {
 | 
			
		||||
			c.Conf.Azure.AccountName = os.Getenv("AZURE_STORAGE_ACCOUNT")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(c.Conf.Azure.AccountKey) == 0 {
 | 
			
		||||
			c.Conf.Azure.AccountKey = os.Getenv("AZURE_STORAGE_ACCESS_KEY")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(c.Conf.Azure.ContainerName) == 0 {
 | 
			
		||||
			util.Log.Error("Azure storage container name is required with -azure-container option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		if err := report.CheckIfAzureContainerExists(); err != nil {
 | 
			
		||||
			util.Log.Errorf("Check if there is a container beforehand: %s, err: %+v",
 | 
			
		||||
				c.Conf.Azure.ContainerName, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.AzureBlobWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.ToSaas {
 | 
			
		||||
		if !c.Conf.UUID {
 | 
			
		||||
			util.Log.Errorf("If you use the -to-saas option, you need to enable the uuid option")
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
		reports = append(reports, report.SaasWriter{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !(c.Conf.FormatJSON || c.Conf.FormatOneLineText ||
 | 
			
		||||
		c.Conf.FormatList || c.Conf.FormatFullText || c.Conf.FormatXML) {
 | 
			
		||||
		c.Conf.FormatList = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var loaded models.ScanResults
 | 
			
		||||
	if loaded, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	hasError := false
 | 
			
		||||
	for _, r := range loaded {
 | 
			
		||||
		if len(r.Errors) == 0 {
 | 
			
		||||
			res = append(res, r)
 | 
			
		||||
		} else {
 | 
			
		||||
			util.Log.Errorf("Ignored since errors occurred during scanning: %s, err: %v",
 | 
			
		||||
				r.ServerName, r.Errors)
 | 
			
		||||
			hasError = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(res) == 0 {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range res {
 | 
			
		||||
		util.Log.Debugf("%s: %s",
 | 
			
		||||
			r.ServerInfo(),
 | 
			
		||||
			pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.UUID {
 | 
			
		||||
		// Ensure UUIDs of scan target servers in config.toml
 | 
			
		||||
		if err := report.EnsureUUIDs(p.configPath, res); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to ensure UUIDs. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !c.Conf.ToSaas {
 | 
			
		||||
		util.Log.Info("Validating db config...")
 | 
			
		||||
		if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.CveDict.URL != "" {
 | 
			
		||||
			if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
				util.Log.Errorf("CVE HTTP server is not running. err: %+v", err)
 | 
			
		||||
				util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
			err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("OVAL HTTP server is not running. err: %+v", err)
 | 
			
		||||
				util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.Gost.URL != "" {
 | 
			
		||||
			util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
			err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("gost HTTP server is not running. err: %+v", err)
 | 
			
		||||
				util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if c.Conf.Exploit.URL != "" {
 | 
			
		||||
			err := exploit.CheckHTTPHealth()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				util.Log.Errorf("exploit HTTP server is not running. err: %+v", err)
 | 
			
		||||
				util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
				return subcommands.ExitFailure
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
			CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
			OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
			GostCnf:     c.Conf.Gost,
 | 
			
		||||
			ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
			DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
		})
 | 
			
		||||
		if locked {
 | 
			
		||||
			util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to init DB Clients. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
		if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
 | 
			
		||||
			util.Log.Errorf("%+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, w := range reports {
 | 
			
		||||
		if err := w.Write(res...); err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to report. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if hasError {
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										223
									
								
								commands/scan.go
									
									
									
									
									
								
							
							
						
						@@ -1,223 +0,0 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/scan"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanCmd is Subcommand of host discovery mode
 | 
			
		||||
type ScanCmd struct {
 | 
			
		||||
	configPath     string
 | 
			
		||||
	askKeyPassword bool
 | 
			
		||||
	timeoutSec     int
 | 
			
		||||
	scanTimeoutSec int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ScanCmd) Name() string { return "scan" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ScanCmd) Synopsis() string { return "Scan vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ScanCmd) Usage() string {
 | 
			
		||||
	return `scan:
 | 
			
		||||
	scan
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cachedb-path=/path/to/cache.db]
 | 
			
		||||
		[-ssh-native-insecure]
 | 
			
		||||
		[-ssh-config]
 | 
			
		||||
		[-containers-only]
 | 
			
		||||
		[-images-only]
 | 
			
		||||
		[-libs-only]
 | 
			
		||||
		[-wordpress-only]
 | 
			
		||||
		[-skip-broken]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-ask-key-password]
 | 
			
		||||
		[-timeout=300]
 | 
			
		||||
		[-timeout-scan=7200]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-vvv]
 | 
			
		||||
		[-ips]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		[SERVER]...
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	defaultCacheDBPath := filepath.Join(wd, "cache.db")
 | 
			
		||||
	f.StringVar(&c.Conf.CacheDBPath, "cachedb-path", defaultCacheDBPath,
 | 
			
		||||
		"/path/to/cache.db (local cache of changelog for Ubuntu/Debian)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHNative, "ssh-native-insecure", false,
 | 
			
		||||
		"Use Native Go implementation of SSH. Default: Use the external command")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.SSHConfig, "ssh-config", false,
 | 
			
		||||
		"Use SSH options specified in ssh_config preferentially")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ContainersOnly, "containers-only", false,
 | 
			
		||||
		"Scan running containers only. Default: Scan both of hosts and running containers")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ImagesOnly, "images-only", false,
 | 
			
		||||
		"Scan container images only. Default: Scan both of hosts and images")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.LibsOnly, "libs-only", false,
 | 
			
		||||
		"Scan libraries (lock files) specified in config.toml only.")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.WordPressOnly, "wordpress-only", false,
 | 
			
		||||
		"Scan WordPress only.")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.SkipBroken, "skip-broken", false,
 | 
			
		||||
		"[For CentOS] yum update changelog with --skip-broken option")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&p.askKeyPassword, "ask-key-password", false,
 | 
			
		||||
		"Ask ssh privatekey password before scanning",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use stdin via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.DetectIPS, "ips", false, "retrieve IPS information")
 | 
			
		||||
	f.BoolVar(&c.Conf.Vvv, "vvv", false, "ssh -vvv")
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&p.timeoutSec, "timeout", 5*60,
 | 
			
		||||
		"Number of seconds for processing other than scan",
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	f.IntVar(&p.scanTimeoutSec, "timeout-scan", 120*60,
 | 
			
		||||
		"Number of seconds for scanning vulnerabilities for all servers",
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
 | 
			
		||||
	if err := mkdirDotVuls(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to create .vuls. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var keyPass string
 | 
			
		||||
	var err error
 | 
			
		||||
	if p.askKeyPassword {
 | 
			
		||||
		prompt := "SSH key password: "
 | 
			
		||||
		if keyPass, err = getPasswd(prompt); err != nil {
 | 
			
		||||
			util.Log.Error(err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = c.Load(p.configPath, keyPass)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		msg := []string{
 | 
			
		||||
			fmt.Sprintf("Error loading %s", p.configPath),
 | 
			
		||||
			"If you update Vuls and get this error, there may be incompatible changes in config.toml",
 | 
			
		||||
			"Please check config.toml template : https://vuls.io/docs/en/usage-settings.html",
 | 
			
		||||
		}
 | 
			
		||||
		util.Log.Errorf("%s\n%+v", strings.Join(msg, "\n"), err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Start scanning")
 | 
			
		||||
	util.Log.Infof("config: %s", p.configPath)
 | 
			
		||||
 | 
			
		||||
	var servernames []string
 | 
			
		||||
	if 0 < len(f.Args()) {
 | 
			
		||||
		servernames = f.Args()
 | 
			
		||||
	} else if c.Conf.Pipe {
 | 
			
		||||
		bytes, err := ioutil.ReadAll(os.Stdin)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("Failed to read stdin. err: %+v", err)
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
		fields := strings.Fields(string(bytes))
 | 
			
		||||
		if 0 < len(fields) {
 | 
			
		||||
			servernames = fields
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	target := make(map[string]c.ServerInfo)
 | 
			
		||||
	for _, arg := range servernames {
 | 
			
		||||
		found := false
 | 
			
		||||
		for servername, info := range c.Conf.Servers {
 | 
			
		||||
			if servername == arg {
 | 
			
		||||
				target[servername] = info
 | 
			
		||||
				found = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !found {
 | 
			
		||||
			util.Log.Errorf("%s is not in config", arg)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if 0 < len(servernames) {
 | 
			
		||||
		c.Conf.Servers = target
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Debugf("%s", pp.Sprintf("%v", target))
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnScan() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Detecting Server/Container OS... ")
 | 
			
		||||
	if err := scan.InitServers(p.timeoutSec); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init servers: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Checking Scan Modes... ")
 | 
			
		||||
	if err := scan.CheckScanModes(); err != nil {
 | 
			
		||||
		util.Log.Errorf("Fix config.toml. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Detecting Platforms... ")
 | 
			
		||||
	scan.DetectPlatforms(p.timeoutSec)
 | 
			
		||||
	util.Log.Info("Detecting IPS identifiers... ")
 | 
			
		||||
	scan.DetectIPSs(p.timeoutSec)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Scanning vulnerabilities... ")
 | 
			
		||||
	if err := scan.Scan(p.scanTimeoutSec); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to scan. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	fmt.Printf("\n\n\n")
 | 
			
		||||
	fmt.Println("To view the detail, vuls tui is useful.")
 | 
			
		||||
	fmt.Println("To send a report, run vuls report -h.")
 | 
			
		||||
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
@@ -1,223 +0,0 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	// "github.com/future-architect/vuls/Server"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/server"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ServerCmd is subcommand for server
 | 
			
		||||
type ServerCmd struct {
 | 
			
		||||
	configPath  string
 | 
			
		||||
	listen      string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*ServerCmd) Name() string { return "server" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*ServerCmd) Synopsis() string { return "Server" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*ServerCmd) Usage() string {
 | 
			
		||||
	return `Server:
 | 
			
		||||
	Server
 | 
			
		||||
		[-lang=en|ja]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-to-localfile]
 | 
			
		||||
		[-format-json]
 | 
			
		||||
		[-http-proxy=http://192.168.0.1:8080]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-listen=localhost:5515]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
 | 
			
		||||
		[RFC3339 datetime format under results dir]
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *ServerCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	f.StringVar(&p.configPath, "config", "", "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means Servering CVSS Score 6.5 and over (default: 0 (means Server all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't Server the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't Server the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&c.Conf.HTTPProxy, "http-proxy", "",
 | 
			
		||||
		"http://proxy-url:port (default: empty)")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.FormatJSON, "format-json", false, "JSON format")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.ToLocalFile, "to-localfile", false, "Write report to localfile")
 | 
			
		||||
	f.StringVar(&p.listen, "listen", "localhost:5515",
 | 
			
		||||
		"host:port (default: localhost:5515)")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *ServerCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if p.configPath != "" {
 | 
			
		||||
		if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
			util.Log.Errorf("Error loading %s. err: %+v", p.configPath, err)
 | 
			
		||||
			return subcommands.ExitUsageError
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReport() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating db config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.CveDict.URL != "" {
 | 
			
		||||
		if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
			util.Log.Errorf("CVE HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %s", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Gost.URL != "" {
 | 
			
		||||
		util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
		err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("gost HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Exploit.URL != "" {
 | 
			
		||||
		err := exploit.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("exploit HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
		CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
		OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
		GostCnf:     c.Conf.Gost,
 | 
			
		||||
		ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
		DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
	})
 | 
			
		||||
	if locked {
 | 
			
		||||
		util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init DB Clients. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
	http.Handle("/vuls", server.VulsHandler{DBclient: *dbclient})
 | 
			
		||||
	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		fmt.Fprintf(w, "ok")
 | 
			
		||||
	})
 | 
			
		||||
	util.Log.Infof("Listening on %s", p.listen)
 | 
			
		||||
	if err := http.ListenAndServe(p.listen, nil); err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to start server. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	return subcommands.ExitSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										240
									
								
								commands/tui.go
									
									
									
									
									
								
							
							
						
						@@ -1,240 +0,0 @@
 | 
			
		||||
package commands
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"flag"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	c "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/exploit"
 | 
			
		||||
	"github.com/future-architect/vuls/gost"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/oval"
 | 
			
		||||
	"github.com/future-architect/vuls/report"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/google/subcommands"
 | 
			
		||||
	cvelog "github.com/kotakanbe/go-cve-dictionary/log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TuiCmd is Subcommand of host discovery mode
 | 
			
		||||
type TuiCmd struct {
 | 
			
		||||
	configPath  string
 | 
			
		||||
	cveDict     c.GoCveDictConf
 | 
			
		||||
	ovalDict    c.GovalDictConf
 | 
			
		||||
	gostConf    c.GostConf
 | 
			
		||||
	exploitConf c.ExploitConf
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Name return subcommand name
 | 
			
		||||
func (*TuiCmd) Name() string { return "tui" }
 | 
			
		||||
 | 
			
		||||
// Synopsis return synopsis
 | 
			
		||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabilities" }
 | 
			
		||||
 | 
			
		||||
// Usage return usage
 | 
			
		||||
func (*TuiCmd) Usage() string {
 | 
			
		||||
	return `tui:
 | 
			
		||||
	tui
 | 
			
		||||
		[-refresh-cve]
 | 
			
		||||
		[-config=/path/to/config.toml]
 | 
			
		||||
		[-cvss-over=7]
 | 
			
		||||
		[-diff]
 | 
			
		||||
		[-ignore-unscored-cves]
 | 
			
		||||
		[-ignore-unfixed]
 | 
			
		||||
		[-results-dir=/path/to/results]
 | 
			
		||||
		[-log-dir=/path/to/log]
 | 
			
		||||
		[-debug]
 | 
			
		||||
		[-debug-sql]
 | 
			
		||||
		[-pipe]
 | 
			
		||||
		[-cvedb-type=sqlite3|mysql|postgres|redis|http]
 | 
			
		||||
		[-cvedb-sqlite3-path=/path/to/cve.sqlite3]
 | 
			
		||||
		[-cvedb-url=http://127.0.0.1:1323 or DB connection string]
 | 
			
		||||
		[-ovaldb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-ovaldb-sqlite3-path=/path/to/oval.sqlite3]
 | 
			
		||||
		[-ovaldb-url=http://127.0.0.1:1324 or DB connection string]
 | 
			
		||||
		[-gostdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-gostdb-sqlite3-path=/path/to/gost.sqlite3]
 | 
			
		||||
		[-gostdb-url=http://127.0.0.1:1325 or DB connection string]
 | 
			
		||||
		[-exploitdb-type=sqlite3|mysql|redis|http]
 | 
			
		||||
		[-exploitdb-sqlite3-path=/path/to/exploitdb.sqlite3]
 | 
			
		||||
		[-exploitdb-url=http://127.0.0.1:1326 or DB connection string]
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetFlags set flag
 | 
			
		||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
 | 
			
		||||
	//  f.StringVar(&p.lang, "lang", "en", "[en|ja]")
 | 
			
		||||
	f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "debug SQL")
 | 
			
		||||
	f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
 | 
			
		||||
 | 
			
		||||
	defaultLogDir := util.GetDefaultLogDir()
 | 
			
		||||
	f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
 | 
			
		||||
 | 
			
		||||
	wd, _ := os.Getwd()
 | 
			
		||||
	defaultResultsDir := filepath.Join(wd, "results")
 | 
			
		||||
	f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
 | 
			
		||||
 | 
			
		||||
	defaultConfPath := filepath.Join(wd, "config.toml")
 | 
			
		||||
	f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.RefreshCve, "refresh-cve", false,
 | 
			
		||||
		"Refresh CVE information in JSON file under results dir")
 | 
			
		||||
 | 
			
		||||
	f.Float64Var(&c.Conf.CvssScoreOver, "cvss-over", 0,
 | 
			
		||||
		"-cvss-over=6.5 means reporting CVSS Score 6.5 and over (default: 0 (means report all))")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Diff, "diff", false,
 | 
			
		||||
		"Difference between previous result and current result ")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(
 | 
			
		||||
		&c.Conf.IgnoreUnscoredCves, "ignore-unscored-cves", false,
 | 
			
		||||
		"Don't report the unscored CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.IgnoreUnfixed, "ignore-unfixed", false,
 | 
			
		||||
		"Don't report the unfixed CVEs")
 | 
			
		||||
 | 
			
		||||
	f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use stdin via PIPE")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.cveDict.Type, "cvedb-type", "",
 | 
			
		||||
		"DB type of go-cve-dictionary (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.cveDict.SQLite3Path, "cvedb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.cveDict.URL, "cvedb-url", "",
 | 
			
		||||
		"http://go-cve-dictionary.com:1323 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.ovalDict.Type, "ovaldb-type", "",
 | 
			
		||||
		"DB type of goval-dictionary (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.ovalDict.SQLite3Path, "ovaldb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.ovalDict.URL, "ovaldb-url", "",
 | 
			
		||||
		"http://goval-dictionary.com:1324 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.gostConf.Type, "gostdb-type", "",
 | 
			
		||||
		"DB type of gost (sqlite3, mysql, postgres or redis)")
 | 
			
		||||
	f.StringVar(&p.gostConf.SQLite3Path, "gostdb-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.gostConf.URL, "gostdb-url", "",
 | 
			
		||||
		"http://gost.com:1325 or DB connection string")
 | 
			
		||||
 | 
			
		||||
	f.StringVar(&p.exploitConf.Type, "exploitdb-type", "",
 | 
			
		||||
		"DB type of exploit (sqlite3, mysql, postgres, redis or http)")
 | 
			
		||||
	f.StringVar(&p.exploitConf.SQLite3Path, "exploitdb-sqlite3-path", "", "/path/to/sqlite3")
 | 
			
		||||
	f.StringVar(&p.exploitConf.URL, "exploitdb-url", "",
 | 
			
		||||
		"http://exploit.com:1326 or DB connection string")
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute execute
 | 
			
		||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
 | 
			
		||||
	c.Conf.Lang = "en"
 | 
			
		||||
 | 
			
		||||
	// Setup Logger
 | 
			
		||||
	util.Log = util.NewCustomLogger(c.ServerInfo{})
 | 
			
		||||
	cvelog.SetLogger(c.Conf.LogDir, false, c.Conf.Debug, false)
 | 
			
		||||
 | 
			
		||||
	if err := c.Load(p.configPath, ""); err != nil {
 | 
			
		||||
		util.Log.Errorf("Error loading %s, err: %+v", p.configPath, err)
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	c.Conf.CveDict.Overwrite(p.cveDict)
 | 
			
		||||
	c.Conf.OvalDict.Overwrite(p.ovalDict)
 | 
			
		||||
	c.Conf.Gost.Overwrite(p.gostConf)
 | 
			
		||||
	c.Conf.Exploit.Overwrite(p.exploitConf)
 | 
			
		||||
 | 
			
		||||
	var dir string
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.Conf.Diff {
 | 
			
		||||
		dir, err = report.JSONDir([]string{})
 | 
			
		||||
	} else {
 | 
			
		||||
		dir, err = report.JSONDir(f.Args())
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to read from JSON. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating config...")
 | 
			
		||||
	if !c.Conf.ValidateOnTui() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var res models.ScanResults
 | 
			
		||||
	if res, err = report.LoadScanResults(dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
	util.Log.Infof("Loaded: %s", dir)
 | 
			
		||||
 | 
			
		||||
	util.Log.Info("Validating db config...")
 | 
			
		||||
	if !c.Conf.ValidateOnReportDB() {
 | 
			
		||||
		return subcommands.ExitUsageError
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.CveDict.URL != "" {
 | 
			
		||||
		if err := report.CveClient.CheckHealth(); err != nil {
 | 
			
		||||
			util.Log.Errorf("CVE HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.OvalDict.URL != "" {
 | 
			
		||||
		err := oval.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("OVAL HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Gost.URL != "" {
 | 
			
		||||
		util.Log.Infof("gost: %s", c.Conf.Gost.URL)
 | 
			
		||||
		err := gost.Base{}.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("gost HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Conf.Exploit.URL != "" {
 | 
			
		||||
		err := exploit.CheckHTTPHealth()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			util.Log.Errorf("exploit HTTP server is not running. err: %+v", err)
 | 
			
		||||
			util.Log.Errorf("Run go-exploitdb as server mode before reporting")
 | 
			
		||||
			return subcommands.ExitFailure
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	dbclient, locked, err := report.NewDBClient(report.DBClientConf{
 | 
			
		||||
		CveDictCnf:  c.Conf.CveDict,
 | 
			
		||||
		OvalDictCnf: c.Conf.OvalDict,
 | 
			
		||||
		GostCnf:     c.Conf.Gost,
 | 
			
		||||
		ExploitCnf:  c.Conf.Exploit,
 | 
			
		||||
		DebugSQL:    c.Conf.DebugSQL,
 | 
			
		||||
	})
 | 
			
		||||
	if locked {
 | 
			
		||||
		util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		util.Log.Errorf("Failed to init DB Clients. err: %+v", err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer dbclient.CloseDB()
 | 
			
		||||
 | 
			
		||||
	if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
 | 
			
		||||
		util.Log.Error(err)
 | 
			
		||||
		return subcommands.ExitFailure
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, r := range res {
 | 
			
		||||
		if len(r.Warnings) != 0 {
 | 
			
		||||
			util.Log.Warnf("Warning: Some warnings occurred while scanning on %s: %s",
 | 
			
		||||
				r.FormatServerName(), r.Warnings)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return report.RunTui(res)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
									
								
							
							
						
						@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								config/chatworkconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ChatWorkConf is ChatWork config
 | 
			
		||||
type ChatWorkConf struct {
 | 
			
		||||
	APIToken string `json:"-"`
 | 
			
		||||
	Room     string `json:"-"`
 | 
			
		||||
	Enabled  bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *ChatWorkConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.Room) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("chatWorkConf.room must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.APIToken) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1215
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						@@ -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))
 | 
			
		||||
@@ -63,7 +65,7 @@ func TestSyslogConfValidate(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMajorVersion(t *testing.T) {
 | 
			
		||||
func TestDistro_MajorVersion(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in  Distro
 | 
			
		||||
		out int
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								config/httpconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,32 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTTPConf is HTTP config
 | 
			
		||||
type HTTPConf struct {
 | 
			
		||||
	URL     string `valid:"url" json:"-"`
 | 
			
		||||
	Enabled bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const httpKey = "VULS_HTTP_URL"
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *HTTPConf) Validate() (errs []error) {
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
@@ -1,9 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// IPS is
 | 
			
		||||
type IPS string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// DeepSecurity is
 | 
			
		||||
	DeepSecurity IPS = "deepsecurity"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										198
									
								
								config/os.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,198 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// EOL has End-of-Life information
 | 
			
		||||
type EOL struct {
 | 
			
		||||
	StandardSupportUntil time.Time
 | 
			
		||||
	ExtendedSupportUntil time.Time
 | 
			
		||||
	Ended                bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsStandardSupportEnded checks now is under standard support
 | 
			
		||||
func (e EOL) IsStandardSupportEnded(now time.Time) bool {
 | 
			
		||||
	return e.Ended ||
 | 
			
		||||
		!e.ExtendedSupportUntil.IsZero() && e.StandardSupportUntil.IsZero() ||
 | 
			
		||||
		!e.StandardSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsExtendedSuppportEnded checks now is under extended support
 | 
			
		||||
func (e EOL) IsExtendedSuppportEnded(now time.Time) bool {
 | 
			
		||||
	if e.Ended {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if e.StandardSupportUntil.IsZero() && e.ExtendedSupportUntil.IsZero() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return !e.ExtendedSupportUntil.IsZero() && now.After(e.ExtendedSupportUntil) ||
 | 
			
		||||
		e.ExtendedSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetEOL return EOL information for the OS-release passed by args
 | 
			
		||||
// 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 constant.Amazon:
 | 
			
		||||
		rel := "2"
 | 
			
		||||
		if isAmazonLinux1(release) {
 | 
			
		||||
			rel = "1"
 | 
			
		||||
		}
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"2": {},
 | 
			
		||||
		}[rel]
 | 
			
		||||
	case constant.RedHat:
 | 
			
		||||
		// https://access.redhat.com/support/policy/updates/errata
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"3": {Ended: true},
 | 
			
		||||
			"4": {Ended: true},
 | 
			
		||||
			"5": {Ended: true},
 | 
			
		||||
			"6": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
				ExtendedSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"7": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"8": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case constant.CentOS:
 | 
			
		||||
		// https://en.wikipedia.org/wiki/CentOS#End-of-support_schedule
 | 
			
		||||
		// TODO Stream
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"3": {Ended: true},
 | 
			
		||||
			"4": {Ended: true},
 | 
			
		||||
			"5": {Ended: true},
 | 
			
		||||
			"6": {Ended: true},
 | 
			
		||||
			"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 constant.Oracle:
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			// Source:
 | 
			
		||||
			// https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf
 | 
			
		||||
			// https://community.oracle.com/docs/DOC-917964
 | 
			
		||||
			"3": {Ended: true},
 | 
			
		||||
			"4": {Ended: true},
 | 
			
		||||
			"5": {Ended: true},
 | 
			
		||||
			"6": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2021, 3, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
				ExtendedSupportUntil: time.Date(2024, 3, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"7": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2024, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"8": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	case constant.Debian:
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			// https://wiki.debian.org/LTS
 | 
			
		||||
			"6":  {Ended: true},
 | 
			
		||||
			"7":  {Ended: true},
 | 
			
		||||
			"8":  {Ended: true},
 | 
			
		||||
			"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 constant.Raspbian:
 | 
			
		||||
		// Not found
 | 
			
		||||
		eol, found = map[string]EOL{}[major(release)]
 | 
			
		||||
	case constant.Ubuntu:
 | 
			
		||||
		// https://wiki.ubuntu.com/Releases
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"14.10": {Ended: true},
 | 
			
		||||
			"14.04": {
 | 
			
		||||
				ExtendedSupportUntil: time.Date(2022, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"15.04": {Ended: true},
 | 
			
		||||
			"16.10": {Ended: true},
 | 
			
		||||
			"17.04": {Ended: true},
 | 
			
		||||
			"17.10": {Ended: true},
 | 
			
		||||
			"16.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2021, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
				ExtendedSupportUntil: time.Date(2024, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"18.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2023, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
				ExtendedSupportUntil: time.Date(2028, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"18.10": {Ended: true},
 | 
			
		||||
			"19.04": {Ended: true},
 | 
			
		||||
			"19.10": {Ended: true},
 | 
			
		||||
			"20.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2025, 4, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"21.04": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2022, 1, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
			"21.10": {
 | 
			
		||||
				StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			},
 | 
			
		||||
		}[release]
 | 
			
		||||
	case constant.SUSEEnterpriseServer:
 | 
			
		||||
		//TODO
 | 
			
		||||
	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{
 | 
			
		||||
			"2.0":  {Ended: true},
 | 
			
		||||
			"2.1":  {Ended: true},
 | 
			
		||||
			"2.2":  {Ended: true},
 | 
			
		||||
			"2.3":  {Ended: true},
 | 
			
		||||
			"2.4":  {Ended: true},
 | 
			
		||||
			"2.5":  {Ended: true},
 | 
			
		||||
			"2.6":  {Ended: true},
 | 
			
		||||
			"2.7":  {Ended: true},
 | 
			
		||||
			"3.0":  {Ended: true},
 | 
			
		||||
			"3.1":  {Ended: true},
 | 
			
		||||
			"3.2":  {Ended: true},
 | 
			
		||||
			"3.3":  {Ended: true},
 | 
			
		||||
			"3.4":  {Ended: true},
 | 
			
		||||
			"3.5":  {Ended: true},
 | 
			
		||||
			"3.6":  {Ended: true},
 | 
			
		||||
			"3.7":  {Ended: true},
 | 
			
		||||
			"3.8":  {Ended: true},
 | 
			
		||||
			"3.9":  {Ended: true},
 | 
			
		||||
			"3.10": {StandardSupportUntil: time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"3.11": {StandardSupportUntil: time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC)},
 | 
			
		||||
			"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 constant.FreeBSD:
 | 
			
		||||
		// https://www.freebsd.org/security/
 | 
			
		||||
		eol, found = map[string]EOL{
 | 
			
		||||
			"7":  {Ended: true},
 | 
			
		||||
			"8":  {Ended: true},
 | 
			
		||||
			"9":  {Ended: true},
 | 
			
		||||
			"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)},
 | 
			
		||||
		}[major(release)]
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func major(osVer string) (majorVersion string) {
 | 
			
		||||
	return strings.Split(osVer, ".")[0]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func majorDotMinor(osVer string) (majorDotMinor string) {
 | 
			
		||||
	ss := strings.SplitN(osVer, ".", 3)
 | 
			
		||||
	if len(ss) == 1 {
 | 
			
		||||
		return osVer
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s.%s", ss[0], ss[1])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isAmazonLinux1(osRelease string) bool {
 | 
			
		||||
	return len(strings.Fields(osRelease)) == 1
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										375
									
								
								config/os_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,375 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	. "github.com/future-architect/vuls/constant"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestEOL_IsStandardSupportEnded(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		family  string
 | 
			
		||||
		release string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		fields   fields
 | 
			
		||||
		now      time.Time
 | 
			
		||||
		found    bool
 | 
			
		||||
		stdEnded bool
 | 
			
		||||
		extEnded bool
 | 
			
		||||
	}{
 | 
			
		||||
		// Amazon Linux
 | 
			
		||||
		{
 | 
			
		||||
			name:     "amazon linux 1 supported",
 | 
			
		||||
			fields:   fields{family: Amazon, release: "2018.03"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "amazon linux 1 eol on 2023-6-30",
 | 
			
		||||
			fields:   fields{family: Amazon, release: "2018.03"},
 | 
			
		||||
			now:      time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "amazon linux 2 supported",
 | 
			
		||||
			fields:   fields{family: Amazon, release: "2 (Karoo)"},
 | 
			
		||||
			now:      time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		//RHEL
 | 
			
		||||
		{
 | 
			
		||||
			name:     "RHEL7 supported",
 | 
			
		||||
			fields:   fields{family: RedHat, release: "7"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "RHEL8 supported",
 | 
			
		||||
			fields:   fields{family: RedHat, release: "8"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "RHEL6 eol",
 | 
			
		||||
			fields:   fields{family: RedHat, release: "6"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "RHEL9 not found",
 | 
			
		||||
			fields:   fields{family: RedHat, release: "9"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		//CentOS
 | 
			
		||||
		{
 | 
			
		||||
			name:     "CentOS 7 supported",
 | 
			
		||||
			fields:   fields{family: CentOS, release: "7"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "CentOS 8 supported",
 | 
			
		||||
			fields:   fields{family: CentOS, release: "8"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "CentOS 6 eol",
 | 
			
		||||
			fields:   fields{family: CentOS, release: "6"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "CentOS 9 not found",
 | 
			
		||||
			fields:   fields{family: CentOS, release: "9"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		//Oracle
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Oracle Linux 7 supported",
 | 
			
		||||
			fields:   fields{family: Oracle, release: "7"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Oracle Linux 8 supported",
 | 
			
		||||
			fields:   fields{family: Oracle, release: "8"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Oracle Linux 6 eol",
 | 
			
		||||
			fields:   fields{family: Oracle, release: "6"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Oracle Linux 9 not found",
 | 
			
		||||
			fields:   fields{family: Oracle, release: "9"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		//Ubuntu
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 18.04 supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "18.04"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 18.04 ext supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "18.04"},
 | 
			
		||||
			now:      time.Date(2025, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 16.04 supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "18.04"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 14.04 eol",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "14.04"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 14.10 eol",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "14.10"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 12.10 not found",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "12.10"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			found:    false,
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Ubuntu 21.04 supported",
 | 
			
		||||
			fields:   fields{family: Ubuntu, release: "21.04"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			found:    true,
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
		},
 | 
			
		||||
		//Debian
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Debian 9 supported",
 | 
			
		||||
			fields:   fields{family: Debian, release: "9"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Debian 10 supported",
 | 
			
		||||
			fields:   fields{family: Debian, release: "10"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Debian 8 supported",
 | 
			
		||||
			fields:   fields{family: Debian, release: "8"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Debian 11 supported",
 | 
			
		||||
			fields:   fields{family: Debian, release: "11"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		//alpine
 | 
			
		||||
		{
 | 
			
		||||
			name:     "alpine 3.10 supported",
 | 
			
		||||
			fields:   fields{family: Alpine, release: "3.10"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Alpine 3.11 supported",
 | 
			
		||||
			fields:   fields{family: Alpine, release: "3.11"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Alpine 3.12 supported",
 | 
			
		||||
			fields:   fields{family: Alpine, release: "3.12"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Alpine 3.9 eol",
 | 
			
		||||
			fields:   fields{family: Alpine, release: "3.9"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "Alpine 3.14 not found",
 | 
			
		||||
			fields:   fields{family: Alpine, release: "3.14"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    false,
 | 
			
		||||
		},
 | 
			
		||||
		// freebsd
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 11 supported",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "11"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 11 eol on 2021-9-30",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "11"},
 | 
			
		||||
			now:      time.Date(2021, 10, 1, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 12 supported",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "12"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: false,
 | 
			
		||||
			extEnded: false,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:     "freebsd 10 eol",
 | 
			
		||||
			fields:   fields{family: FreeBSD, release: "10"},
 | 
			
		||||
			now:      time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
 | 
			
		||||
			stdEnded: true,
 | 
			
		||||
			extEnded: true,
 | 
			
		||||
			found:    true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			eol, found := GetEOL(tt.fields.family, tt.fields.release)
 | 
			
		||||
			if found != tt.found {
 | 
			
		||||
				t.Errorf("GetEOL.found = %v, want %v", found, tt.found)
 | 
			
		||||
			}
 | 
			
		||||
			if found {
 | 
			
		||||
				if got := eol.IsStandardSupportEnded(tt.now); got != tt.stdEnded {
 | 
			
		||||
					t.Errorf("EOL.IsStandardSupportEnded() = %v, want %v", got, tt.stdEnded)
 | 
			
		||||
				}
 | 
			
		||||
				if got := eol.IsExtendedSuppportEnded(tt.now); got != tt.extEnded {
 | 
			
		||||
					t.Errorf("EOL.IsExtendedSupportEnded() = %v, want %v", got, tt.extEnded)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test_majorDotMinor(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		osVer string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name              string
 | 
			
		||||
		args              args
 | 
			
		||||
		wantMajorDotMinor string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "empty",
 | 
			
		||||
			args: args{
 | 
			
		||||
				osVer: "",
 | 
			
		||||
			},
 | 
			
		||||
			wantMajorDotMinor: "",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major",
 | 
			
		||||
			args: args{
 | 
			
		||||
				osVer: "3",
 | 
			
		||||
			},
 | 
			
		||||
			wantMajorDotMinor: "3",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major dot minor",
 | 
			
		||||
			args: args{
 | 
			
		||||
				osVer: "3.1",
 | 
			
		||||
			},
 | 
			
		||||
			wantMajorDotMinor: "3.1",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "major dot minor dot release",
 | 
			
		||||
			args: args{
 | 
			
		||||
				osVer: "3.1.4",
 | 
			
		||||
			},
 | 
			
		||||
			wantMajorDotMinor: "3.1",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if gotMajorDotMinor := majorDotMinor(tt.args.osVer); gotMajorDotMinor != tt.wantMajorDotMinor {
 | 
			
		||||
				t.Errorf("majorDotMinor() = %v, want %v", gotMajorDotMinor, tt.wantMajorDotMinor)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								config/saasconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,34 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SaasConf is FutureVuls config
 | 
			
		||||
type SaasConf struct {
 | 
			
		||||
	GroupID int64  `json:"-"`
 | 
			
		||||
	Token   string `json:"-"`
 | 
			
		||||
	URL     string `json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SaasConf) Validate() (errs []error) {
 | 
			
		||||
	if c.GroupID == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("GroupID must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.Token) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("Token must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.URL) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("URL must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										110
									
								
								config/scanmode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,110 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanMode has a type of scan mode. fast, fast-root, deep and offline
 | 
			
		||||
type ScanMode struct {
 | 
			
		||||
	flag byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// Fast is fast scan mode
 | 
			
		||||
	Fast = byte(1 << iota)
 | 
			
		||||
	// FastRoot is scanmode
 | 
			
		||||
	FastRoot
 | 
			
		||||
	// Deep is scanmode
 | 
			
		||||
	Deep
 | 
			
		||||
	// Offline is scanmode
 | 
			
		||||
	Offline
 | 
			
		||||
 | 
			
		||||
	fastStr     = "fast"
 | 
			
		||||
	fastRootStr = "fast-root"
 | 
			
		||||
	deepStr     = "deep"
 | 
			
		||||
	offlineStr  = "offline"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Set mode
 | 
			
		||||
func (s *ScanMode) Set(f byte) {
 | 
			
		||||
	s.flag |= f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFast return whether scan mode is fast
 | 
			
		||||
func (s ScanMode) IsFast() bool {
 | 
			
		||||
	return s.flag&Fast == Fast
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFastRoot return whether scan mode is fastroot
 | 
			
		||||
func (s ScanMode) IsFastRoot() bool {
 | 
			
		||||
	return s.flag&FastRoot == FastRoot
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsDeep return whether scan mode is deep
 | 
			
		||||
func (s ScanMode) IsDeep() bool {
 | 
			
		||||
	return s.flag&Deep == Deep
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsOffline return whether scan mode is offline
 | 
			
		||||
func (s ScanMode) IsOffline() bool {
 | 
			
		||||
	return s.flag&Offline == Offline
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *ScanMode) ensure() error {
 | 
			
		||||
	numTrue := 0
 | 
			
		||||
	for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} {
 | 
			
		||||
		if b {
 | 
			
		||||
			numTrue++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if numTrue == 0 {
 | 
			
		||||
		s.Set(Fast)
 | 
			
		||||
	} else if s.IsDeep() && s.IsOffline() {
 | 
			
		||||
		return xerrors.New("Don't specify both of deep and offline")
 | 
			
		||||
	} else if numTrue != 1 {
 | 
			
		||||
		return xerrors.New("Specify only one of offline, fast, fast-root or deep")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s ScanMode) String() string {
 | 
			
		||||
	ss := ""
 | 
			
		||||
	if s.IsFast() {
 | 
			
		||||
		ss = fastStr
 | 
			
		||||
	} else if s.IsFastRoot() {
 | 
			
		||||
		ss = fastRootStr
 | 
			
		||||
	} else if s.IsDeep() {
 | 
			
		||||
		ss = deepStr
 | 
			
		||||
	}
 | 
			
		||||
	if s.IsOffline() {
 | 
			
		||||
		ss += " " + offlineStr
 | 
			
		||||
	}
 | 
			
		||||
	return ss + " mode"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setScanMode(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
	if len(server.ScanMode) == 0 {
 | 
			
		||||
		server.ScanMode = Conf.Default.ScanMode
 | 
			
		||||
	}
 | 
			
		||||
	for _, m := range server.ScanMode {
 | 
			
		||||
		switch strings.ToLower(m) {
 | 
			
		||||
		case fastStr:
 | 
			
		||||
			server.Mode.Set(Fast)
 | 
			
		||||
		case fastRootStr:
 | 
			
		||||
			server.Mode.Set(FastRoot)
 | 
			
		||||
		case deepStr:
 | 
			
		||||
			server.Mode.Set(Deep)
 | 
			
		||||
		case offlineStr:
 | 
			
		||||
			server.Mode.Set(Offline)
 | 
			
		||||
		default:
 | 
			
		||||
			return xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline",
 | 
			
		||||
				m, server.ServerName)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := server.Mode.ensure(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("%s in %s", err, server.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								config/scanmodule.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,97 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ScanModule has a type of scan module
 | 
			
		||||
type ScanModule struct {
 | 
			
		||||
	flag byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// OSPkg is scanmodule
 | 
			
		||||
	OSPkg = byte(1 << iota)
 | 
			
		||||
	// WordPress is scanmodule
 | 
			
		||||
	WordPress
 | 
			
		||||
	// Lockfile is scanmodule
 | 
			
		||||
	Lockfile
 | 
			
		||||
	// Port is scanmodule
 | 
			
		||||
	Port
 | 
			
		||||
 | 
			
		||||
	osPkgStr     = "ospkg"
 | 
			
		||||
	wordPressStr = "wordpress"
 | 
			
		||||
	lockfileStr  = "lockfile"
 | 
			
		||||
	portStr      = "port"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var allModules = []string{osPkgStr, wordPressStr, lockfileStr, portStr}
 | 
			
		||||
 | 
			
		||||
// Set module
 | 
			
		||||
func (s *ScanModule) Set(f byte) {
 | 
			
		||||
	s.flag |= f
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsScanOSPkg return whether scanning os pkg
 | 
			
		||||
func (s ScanModule) IsScanOSPkg() bool {
 | 
			
		||||
	return s.flag&OSPkg == OSPkg
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsScanWordPress return whether scanning wordpress
 | 
			
		||||
func (s ScanModule) IsScanWordPress() bool {
 | 
			
		||||
	return s.flag&WordPress == WordPress
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsScanLockFile whether scanning lock file
 | 
			
		||||
func (s ScanModule) IsScanLockFile() bool {
 | 
			
		||||
	return s.flag&Lockfile == Lockfile
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsScanPort whether scanning listening ports
 | 
			
		||||
func (s ScanModule) IsScanPort() bool {
 | 
			
		||||
	return s.flag&Port == Port
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsZero return the struct value are all false
 | 
			
		||||
func (s ScanModule) IsZero() bool {
 | 
			
		||||
	return !(s.IsScanOSPkg() || s.IsScanWordPress() || s.IsScanLockFile() || s.IsScanPort())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *ScanModule) ensure() error {
 | 
			
		||||
	if s.IsZero() {
 | 
			
		||||
		s.Set(OSPkg)
 | 
			
		||||
		s.Set(WordPress)
 | 
			
		||||
		s.Set(Lockfile)
 | 
			
		||||
		s.Set(Port)
 | 
			
		||||
	} else if !s.IsScanOSPkg() && s.IsScanPort() {
 | 
			
		||||
		return xerrors.New("When specifying the Port, Specify OSPkg as well")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setScanModules(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
	if len(server.ScanModules) == 0 {
 | 
			
		||||
		server.ScanModules = d.ScanModules
 | 
			
		||||
	}
 | 
			
		||||
	for _, m := range server.ScanModules {
 | 
			
		||||
		switch strings.ToLower(m) {
 | 
			
		||||
		case osPkgStr:
 | 
			
		||||
			server.Module.Set(OSPkg)
 | 
			
		||||
		case wordPressStr:
 | 
			
		||||
			server.Module.Set(WordPress)
 | 
			
		||||
		case lockfileStr:
 | 
			
		||||
			server.Module.Set(Lockfile)
 | 
			
		||||
		case portStr:
 | 
			
		||||
			server.Module.Set(Port)
 | 
			
		||||
		default:
 | 
			
		||||
			return xerrors.Errorf("scanMode: %s of %s is invalid. Specify %s",
 | 
			
		||||
				m, server.ServerName, allModules)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := server.Module.ensure(); err != nil {
 | 
			
		||||
		return xerrors.Errorf("%s in %s", err, server.ServerName)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								config/scanmodule_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,65 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestScanModule_IsZero(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name  string
 | 
			
		||||
		modes []byte
 | 
			
		||||
		want  bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:  "not zero",
 | 
			
		||||
			modes: []byte{OSPkg},
 | 
			
		||||
			want:  false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:  "zero",
 | 
			
		||||
			modes: []byte{},
 | 
			
		||||
			want:  true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			s := ScanModule{}
 | 
			
		||||
			for _, b := range tt.modes {
 | 
			
		||||
				s.Set(b)
 | 
			
		||||
			}
 | 
			
		||||
			if got := s.IsZero(); got != tt.want {
 | 
			
		||||
				t.Errorf("ScanModule.IsZero() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestScanModule_validate(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		modes   []byte
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:    "valid",
 | 
			
		||||
			modes:   []byte{},
 | 
			
		||||
			wantErr: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:    "err",
 | 
			
		||||
			modes:   []byte{WordPress, Lockfile, Port},
 | 
			
		||||
			wantErr: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			s := ScanModule{}
 | 
			
		||||
			for _, b := range tt.modes {
 | 
			
		||||
				s.Set(b)
 | 
			
		||||
			}
 | 
			
		||||
			if err := s.ensure(); (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("ScanModule.validate() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								config/slackconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,52 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SlackConf is slack config
 | 
			
		||||
type SlackConf struct {
 | 
			
		||||
	HookURL     string   `valid:"url" json:"-" toml:"hookURL,omitempty"`
 | 
			
		||||
	LegacyToken string   `json:"-" toml:"legacyToken,omitempty"`
 | 
			
		||||
	Channel     string   `json:"-" toml:"channel,omitempty"`
 | 
			
		||||
	IconEmoji   string   `json:"-" toml:"iconEmoji,omitempty"`
 | 
			
		||||
	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 !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.Channel) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("slack.channel must not be empty"))
 | 
			
		||||
	} else {
 | 
			
		||||
		if !(strings.HasPrefix(c.Channel, "#") ||
 | 
			
		||||
			c.Channel == "${servername}") {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf(
 | 
			
		||||
				"channel's prefix must be '#', channel: %s", c.Channel))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.AuthUser) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("slack.authUser must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										65
									
								
								config/smtpconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,65 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SMTPConf is smtp config
 | 
			
		||||
type SMTPConf struct {
 | 
			
		||||
	SMTPAddr      string   `toml:"smtpAddr,omitempty" json:"-"`
 | 
			
		||||
	SMTPPort      string   `toml:"smtpPort,omitempty" valid:"port" json:"-"`
 | 
			
		||||
	User          string   `toml:"user,omitempty" json:"-"`
 | 
			
		||||
	Password      string   `toml:"password,omitempty" json:"-"`
 | 
			
		||||
	From          string   `toml:"from,omitempty" json:"-"`
 | 
			
		||||
	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) {
 | 
			
		||||
	for _, addr := range emails {
 | 
			
		||||
		if len(addr) == 0 {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if ok := govalidator.IsEmail(addr); !ok {
 | 
			
		||||
			errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate SMTP configuration
 | 
			
		||||
func (c *SMTPConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	emails := []string{}
 | 
			
		||||
	emails = append(emails, c.From)
 | 
			
		||||
	emails = append(emails, c.To...)
 | 
			
		||||
	emails = append(emails, c.Cc...)
 | 
			
		||||
 | 
			
		||||
	if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
 | 
			
		||||
		errs = append(errs, emailErrs...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.SMTPAddr == "" {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if c.SMTPPort == "" {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.To) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.To required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.From) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("email.From required at least one address"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										130
									
								
								config/syslogconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,130 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"log/syslog"
 | 
			
		||||
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SyslogConf is syslog config
 | 
			
		||||
type SyslogConf struct {
 | 
			
		||||
	Protocol string `json:"-"`
 | 
			
		||||
	Host     string `valid:"host" json:"-"`
 | 
			
		||||
	Port     string `valid:"port" json:"-"`
 | 
			
		||||
	Severity string `json:"-"`
 | 
			
		||||
	Facility string `json:"-"`
 | 
			
		||||
	Tag      string `json:"-"`
 | 
			
		||||
	Verbose  bool   `json:"-"`
 | 
			
		||||
	Enabled  bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *SyslogConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	//  If protocol is empty, it will connect to the local syslog server.
 | 
			
		||||
	if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" {
 | 
			
		||||
		errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Default port: 514
 | 
			
		||||
	if c.Port == "" {
 | 
			
		||||
		c.Port = "514"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := c.GetSeverity(); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := c.GetFacility(); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := govalidator.ValidateStruct(c); err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return errs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetSeverity gets severity
 | 
			
		||||
func (c *SyslogConf) GetSeverity() (syslog.Priority, error) {
 | 
			
		||||
	if c.Severity == "" {
 | 
			
		||||
		return syslog.LOG_INFO, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch c.Severity {
 | 
			
		||||
	case "emerg":
 | 
			
		||||
		return syslog.LOG_EMERG, nil
 | 
			
		||||
	case "alert":
 | 
			
		||||
		return syslog.LOG_ALERT, nil
 | 
			
		||||
	case "crit":
 | 
			
		||||
		return syslog.LOG_CRIT, nil
 | 
			
		||||
	case "err":
 | 
			
		||||
		return syslog.LOG_ERR, nil
 | 
			
		||||
	case "warning":
 | 
			
		||||
		return syslog.LOG_WARNING, nil
 | 
			
		||||
	case "notice":
 | 
			
		||||
		return syslog.LOG_NOTICE, nil
 | 
			
		||||
	case "info":
 | 
			
		||||
		return syslog.LOG_INFO, nil
 | 
			
		||||
	case "debug":
 | 
			
		||||
		return syslog.LOG_DEBUG, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return -1, xerrors.Errorf("Invalid severity: %s", c.Severity)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFacility gets facility
 | 
			
		||||
func (c *SyslogConf) GetFacility() (syslog.Priority, error) {
 | 
			
		||||
	if c.Facility == "" {
 | 
			
		||||
		return syslog.LOG_AUTH, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch c.Facility {
 | 
			
		||||
	case "kern":
 | 
			
		||||
		return syslog.LOG_KERN, nil
 | 
			
		||||
	case "user":
 | 
			
		||||
		return syslog.LOG_USER, nil
 | 
			
		||||
	case "mail":
 | 
			
		||||
		return syslog.LOG_MAIL, nil
 | 
			
		||||
	case "daemon":
 | 
			
		||||
		return syslog.LOG_DAEMON, nil
 | 
			
		||||
	case "auth":
 | 
			
		||||
		return syslog.LOG_AUTH, nil
 | 
			
		||||
	case "syslog":
 | 
			
		||||
		return syslog.LOG_SYSLOG, nil
 | 
			
		||||
	case "lpr":
 | 
			
		||||
		return syslog.LOG_LPR, nil
 | 
			
		||||
	case "news":
 | 
			
		||||
		return syslog.LOG_NEWS, nil
 | 
			
		||||
	case "uucp":
 | 
			
		||||
		return syslog.LOG_UUCP, nil
 | 
			
		||||
	case "cron":
 | 
			
		||||
		return syslog.LOG_CRON, nil
 | 
			
		||||
	case "authpriv":
 | 
			
		||||
		return syslog.LOG_AUTHPRIV, nil
 | 
			
		||||
	case "ftp":
 | 
			
		||||
		return syslog.LOG_FTP, nil
 | 
			
		||||
	case "local0":
 | 
			
		||||
		return syslog.LOG_LOCAL0, nil
 | 
			
		||||
	case "local1":
 | 
			
		||||
		return syslog.LOG_LOCAL1, nil
 | 
			
		||||
	case "local2":
 | 
			
		||||
		return syslog.LOG_LOCAL2, nil
 | 
			
		||||
	case "local3":
 | 
			
		||||
		return syslog.LOG_LOCAL3, nil
 | 
			
		||||
	case "local4":
 | 
			
		||||
		return syslog.LOG_LOCAL4, nil
 | 
			
		||||
	case "local5":
 | 
			
		||||
		return syslog.LOG_LOCAL5, nil
 | 
			
		||||
	case "local6":
 | 
			
		||||
		return syslog.LOG_LOCAL6, nil
 | 
			
		||||
	case "local7":
 | 
			
		||||
		return syslog.LOG_LOCAL7, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return -1, xerrors.Errorf("Invalid facility: %s", c.Facility)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								config/telegramconf.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,33 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/asaskevich/govalidator"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// TelegramConf is Telegram config
 | 
			
		||||
type TelegramConf struct {
 | 
			
		||||
	Token   string `json:"-"`
 | 
			
		||||
	ChatID  string `json:"-"`
 | 
			
		||||
	Enabled bool   `toml:"-" json:"-"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Validate validates configuration
 | 
			
		||||
func (c *TelegramConf) Validate() (errs []error) {
 | 
			
		||||
	if !c.Enabled {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if len(c.ChatID) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(c.Token) == 0 {
 | 
			
		||||
		errs = append(errs, xerrors.New("TelegramConf.Token must not be empty"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := govalidator.ValidateStruct(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		errs = append(errs, err)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
@@ -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,264 +16,206 @@ type TOMLLoader struct {
 | 
			
		||||
 | 
			
		||||
// Load load the configuration TOML file specified by path arg.
 | 
			
		||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
 | 
			
		||||
	var conf Config
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
 | 
			
		||||
	// util.Log.Infof("Loading config: %s", pathToToml)
 | 
			
		||||
	if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	Conf.EMail = conf.EMail
 | 
			
		||||
	Conf.Slack = conf.Slack
 | 
			
		||||
	Conf.Stride = conf.Stride
 | 
			
		||||
	Conf.HipChat = conf.HipChat
 | 
			
		||||
	Conf.ChatWork = conf.ChatWork
 | 
			
		||||
	Conf.Telegram = conf.Telegram
 | 
			
		||||
	Conf.Saas = conf.Saas
 | 
			
		||||
	Conf.Syslog = conf.Syslog
 | 
			
		||||
	Conf.HTTP = conf.HTTP
 | 
			
		||||
	Conf.AWS = conf.AWS
 | 
			
		||||
	Conf.Azure = conf.Azure
 | 
			
		||||
 | 
			
		||||
	Conf.CveDict = conf.CveDict
 | 
			
		||||
	Conf.OvalDict = conf.OvalDict
 | 
			
		||||
	Conf.Gost = conf.Gost
 | 
			
		||||
	Conf.Exploit = conf.Exploit
 | 
			
		||||
 | 
			
		||||
	d := conf.Default
 | 
			
		||||
	Conf.Default = d
 | 
			
		||||
	servers := make(map[string]ServerInfo)
 | 
			
		||||
 | 
			
		||||
	if keyPass != "" {
 | 
			
		||||
		d.KeyPassword = keyPass
 | 
			
		||||
	for _, cnf := range []VulnDictInterface{
 | 
			
		||||
		&Conf.CveDict,
 | 
			
		||||
		&Conf.OvalDict,
 | 
			
		||||
		&Conf.Gost,
 | 
			
		||||
		&Conf.Exploit,
 | 
			
		||||
		&Conf.Metasploit,
 | 
			
		||||
	} {
 | 
			
		||||
		cnf.Init()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i := 0
 | 
			
		||||
	for serverName, v := range conf.Servers {
 | 
			
		||||
		if 0 < len(v.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", serverName)
 | 
			
		||||
	index := 0
 | 
			
		||||
	for name, server := range Conf.Servers {
 | 
			
		||||
		server.ServerName = 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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s := ServerInfo{ServerName: serverName}
 | 
			
		||||
		s.Images = make(map[string]Image)
 | 
			
		||||
 | 
			
		||||
		// image are able to set any server type
 | 
			
		||||
		for name, image := range v.Images {
 | 
			
		||||
			if err := IsValidImage(image); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			s.Images[name] = image
 | 
			
		||||
		if err := setScanMode(&server, Conf.Default); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to set ScanMode: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if v.Type != ServerTypePseudo {
 | 
			
		||||
			s.Host = v.Host
 | 
			
		||||
			if len(s.Host) == 0 {
 | 
			
		||||
				return xerrors.Errorf("%s is invalid. host is empty", serverName)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.Port != "":
 | 
			
		||||
				s.Port = v.Port
 | 
			
		||||
			case d.Port != "":
 | 
			
		||||
				s.Port = d.Port
 | 
			
		||||
			default:
 | 
			
		||||
				s.Port = "22"
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			switch {
 | 
			
		||||
			case v.User != "":
 | 
			
		||||
				s.User = v.User
 | 
			
		||||
			case d.User != "":
 | 
			
		||||
				s.User = d.User
 | 
			
		||||
			default:
 | 
			
		||||
				if s.Port != "local" {
 | 
			
		||||
					return xerrors.Errorf("%s is invalid. User is empty", serverName)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			s.KeyPath = v.KeyPath
 | 
			
		||||
			if len(s.KeyPath) == 0 {
 | 
			
		||||
				s.KeyPath = d.KeyPath
 | 
			
		||||
			}
 | 
			
		||||
			s.KeyPassword = v.KeyPassword
 | 
			
		||||
			if len(s.KeyPassword) == 0 {
 | 
			
		||||
				s.KeyPassword = d.KeyPassword
 | 
			
		||||
			}
 | 
			
		||||
		if err := setScanModules(&server, Conf.Default); err != nil {
 | 
			
		||||
			return xerrors.Errorf("Failed to set ScanModule: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ScanMode = v.ScanMode
 | 
			
		||||
		if len(s.ScanMode) == 0 {
 | 
			
		||||
			s.ScanMode = d.ScanMode
 | 
			
		||||
			if len(s.ScanMode) == 0 {
 | 
			
		||||
				s.ScanMode = []string{"fast"}
 | 
			
		||||
			}
 | 
			
		||||
		if len(server.CpeNames) == 0 {
 | 
			
		||||
			server.CpeNames = Conf.Default.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
		for _, m := range s.ScanMode {
 | 
			
		||||
			switch m {
 | 
			
		||||
			case "fast":
 | 
			
		||||
				s.Mode.Set(Fast)
 | 
			
		||||
			case "fast-root":
 | 
			
		||||
				s.Mode.Set(FastRoot)
 | 
			
		||||
			case "deep":
 | 
			
		||||
				s.Mode.Set(Deep)
 | 
			
		||||
			case "offline":
 | 
			
		||||
				s.Mode.Set(Offline)
 | 
			
		||||
			default:
 | 
			
		||||
				return xerrors.Errorf("scanMode: %s of %s is invalie. Specify -fast, -fast-root, -deep or offline", m, serverName)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err := s.Mode.validate(); err != nil {
 | 
			
		||||
			return xerrors.Errorf("%s in %s", err, serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.CpeNames = v.CpeNames
 | 
			
		||||
		if len(s.CpeNames) == 0 {
 | 
			
		||||
			s.CpeNames = d.CpeNames
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Lockfiles = v.Lockfiles
 | 
			
		||||
		if len(s.Lockfiles) == 0 {
 | 
			
		||||
			s.Lockfiles = d.Lockfiles
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.FindLock = v.FindLock
 | 
			
		||||
 | 
			
		||||
		for i, n := range s.CpeNames {
 | 
			
		||||
		for i, n := range server.CpeNames {
 | 
			
		||||
			uri, err := toCpeURI(n)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, serverName, err)
 | 
			
		||||
				return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, name, err)
 | 
			
		||||
			}
 | 
			
		||||
			s.CpeNames[i] = uri
 | 
			
		||||
			server.CpeNames[i] = uri
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ContainersIncluded = v.ContainersIncluded
 | 
			
		||||
		if len(s.ContainersIncluded) == 0 {
 | 
			
		||||
			s.ContainersIncluded = d.ContainersIncluded
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ContainersExcluded = v.ContainersExcluded
 | 
			
		||||
		if len(s.ContainersExcluded) == 0 {
 | 
			
		||||
			s.ContainersExcluded = d.ContainersExcluded
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.ContainerType = v.ContainerType
 | 
			
		||||
		if len(s.ContainerType) == 0 {
 | 
			
		||||
			s.ContainerType = d.ContainerType
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Containers = v.Containers
 | 
			
		||||
		for contName, cont := range s.Containers {
 | 
			
		||||
			cont.IgnoreCves = append(cont.IgnoreCves, d.IgnoreCves...)
 | 
			
		||||
			s.Containers[contName] = cont
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(v.DependencyCheckXMLPath) != 0 || len(d.DependencyCheckXMLPath) != 0 {
 | 
			
		||||
			return xerrors.Errorf("[DEPRECATED] dependencyCheckXMLPath IS DEPRECATED. USE owaspDCXMLPath INSTEAD: %s", serverName)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.OwaspDCXMLPath = v.OwaspDCXMLPath
 | 
			
		||||
		if len(s.OwaspDCXMLPath) == 0 {
 | 
			
		||||
			s.OwaspDCXMLPath = d.OwaspDCXMLPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.Memo = v.Memo
 | 
			
		||||
		if s.Memo == "" {
 | 
			
		||||
			s.Memo = d.Memo
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.IgnoreCves = v.IgnoreCves
 | 
			
		||||
		for _, cve := range d.IgnoreCves {
 | 
			
		||||
		for _, cve := range Conf.Default.IgnoreCves {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, c := range s.IgnoreCves {
 | 
			
		||||
			for _, c := range server.IgnoreCves {
 | 
			
		||||
				if cve == c {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.IgnoreCves = append(s.IgnoreCves, cve)
 | 
			
		||||
				server.IgnoreCves = append(server.IgnoreCves, cve)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.IgnorePkgsRegexp = v.IgnorePkgsRegexp
 | 
			
		||||
		for _, pkg := range d.IgnorePkgsRegexp {
 | 
			
		||||
		for _, pkg := range Conf.Default.IgnorePkgsRegexp {
 | 
			
		||||
			found := false
 | 
			
		||||
			for _, p := range s.IgnorePkgsRegexp {
 | 
			
		||||
			for _, p := range server.IgnorePkgsRegexp {
 | 
			
		||||
				if pkg == p {
 | 
			
		||||
					found = true
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if !found {
 | 
			
		||||
				s.IgnorePkgsRegexp = append(s.IgnorePkgsRegexp, pkg)
 | 
			
		||||
				server.IgnorePkgsRegexp = append(server.IgnorePkgsRegexp, pkg)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, reg := range s.IgnorePkgsRegexp {
 | 
			
		||||
		for _, reg := range server.IgnorePkgsRegexp {
 | 
			
		||||
			_, err := regexp.Compile(reg)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return xerrors.Errorf("Faild to parse %s in %s. err: %w", reg, serverName, err)
 | 
			
		||||
				return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, name, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for contName, cont := range s.Containers {
 | 
			
		||||
		for contName, cont := range server.Containers {
 | 
			
		||||
			for _, reg := range cont.IgnorePkgsRegexp {
 | 
			
		||||
				_, err := regexp.Compile(reg)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return xerrors.Errorf("Faild to parse %s in %s@%s. err: %w",
 | 
			
		||||
						reg, contName, serverName, err)
 | 
			
		||||
					return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w",
 | 
			
		||||
						reg, contName, name, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		opt := map[string]interface{}{}
 | 
			
		||||
		for k, v := range d.Optional {
 | 
			
		||||
			opt[k] = v
 | 
			
		||||
		for ownerRepo, githubSetting := range server.GitHubRepos {
 | 
			
		||||
			if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
 | 
			
		||||
				return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
 | 
			
		||||
					ownerRepo, name)
 | 
			
		||||
			}
 | 
			
		||||
			if githubSetting.Token == "" {
 | 
			
		||||
				return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
 | 
			
		||||
					ownerRepo, name)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for k, v := range v.Optional {
 | 
			
		||||
			opt[k] = v
 | 
			
		||||
		}
 | 
			
		||||
		s.Optional = opt
 | 
			
		||||
 | 
			
		||||
		s.Enablerepo = v.Enablerepo
 | 
			
		||||
		if len(s.Enablerepo) == 0 {
 | 
			
		||||
			s.Enablerepo = d.Enablerepo
 | 
			
		||||
		if len(server.Enablerepo) == 0 {
 | 
			
		||||
			server.Enablerepo = Conf.Default.Enablerepo
 | 
			
		||||
		}
 | 
			
		||||
		if len(s.Enablerepo) != 0 {
 | 
			
		||||
			for _, repo := range s.Enablerepo {
 | 
			
		||||
		if len(server.Enablerepo) != 0 {
 | 
			
		||||
			for _, repo := range server.Enablerepo {
 | 
			
		||||
				switch repo {
 | 
			
		||||
				case "base", "updates":
 | 
			
		||||
					// nop
 | 
			
		||||
				default:
 | 
			
		||||
					return xerrors.Errorf(
 | 
			
		||||
						"For now, enablerepo have to be base or updates: %s, servername: %s",
 | 
			
		||||
						s.Enablerepo, serverName)
 | 
			
		||||
						"For now, enablerepo have to be base or updates: %s",
 | 
			
		||||
						server.Enablerepo)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.GitHubRepos = v.GitHubRepos
 | 
			
		||||
		for ownerRepo, githubSetting := range s.GitHubRepos {
 | 
			
		||||
			if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
 | 
			
		||||
				return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
 | 
			
		||||
					ownerRepo, serverName)
 | 
			
		||||
			}
 | 
			
		||||
			if githubSetting.Token == "" {
 | 
			
		||||
				return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
 | 
			
		||||
					ownerRepo, serverName)
 | 
			
		||||
		server.LogMsgAnsiColor = Colors[index%len(Colors)]
 | 
			
		||||
		index++
 | 
			
		||||
 | 
			
		||||
		Conf.Servers[name] = server
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
 | 
			
		||||
	if server.Type != constant.ServerTypePseudo {
 | 
			
		||||
		if len(server.Host) == 0 {
 | 
			
		||||
			return xerrors.Errorf("server.host is empty")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(server.JumpServer) == 0 {
 | 
			
		||||
			server.JumpServer = Conf.Default.JumpServer
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if server.Port == "" {
 | 
			
		||||
			if Conf.Default.Port != "" {
 | 
			
		||||
				server.Port = Conf.Default.Port
 | 
			
		||||
			} else {
 | 
			
		||||
				server.Port = "22"
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.UUIDs = v.UUIDs
 | 
			
		||||
		s.Type = v.Type
 | 
			
		||||
		if server.User == "" {
 | 
			
		||||
			server.User = Conf.Default.User
 | 
			
		||||
			if server.User == "" && server.Port != "local" {
 | 
			
		||||
				return xerrors.Errorf("server.user is empty")
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.WordPress.WPVulnDBToken = v.WordPress.WPVulnDBToken
 | 
			
		||||
		s.WordPress.CmdPath = v.WordPress.CmdPath
 | 
			
		||||
		s.WordPress.DocRoot = v.WordPress.DocRoot
 | 
			
		||||
		s.WordPress.OSUser = v.WordPress.OSUser
 | 
			
		||||
		s.WordPress.IgnoreInactive = v.WordPress.IgnoreInactive
 | 
			
		||||
		if server.SSHConfigPath == "" {
 | 
			
		||||
			server.SSHConfigPath = Conf.Default.SSHConfigPath
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		s.LogMsgAnsiColor = Colors[i%len(Colors)]
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		servers[serverName] = s
 | 
			
		||||
		if server.KeyPath == "" {
 | 
			
		||||
			server.KeyPath = Conf.Default.KeyPath
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	Conf.Servers = servers
 | 
			
		||||
 | 
			
		||||
	if len(server.Lockfiles) == 0 {
 | 
			
		||||
		server.Lockfiles = Conf.Default.Lockfiles
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.ContainersIncluded) == 0 {
 | 
			
		||||
		server.ContainersIncluded = Conf.Default.ContainersIncluded
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.ContainersExcluded) == 0 {
 | 
			
		||||
		server.ContainersExcluded = Conf.Default.ContainersExcluded
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.ContainerType == "" {
 | 
			
		||||
		server.ContainerType = Conf.Default.ContainerType
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for contName, cont := range server.Containers {
 | 
			
		||||
		cont.IgnoreCves = append(cont.IgnoreCves, Conf.Default.IgnoreCves...)
 | 
			
		||||
		server.Containers[contName] = cont
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.OwaspDCXMLPath == "" {
 | 
			
		||||
		server.OwaspDCXMLPath = Conf.Default.OwaspDCXMLPath
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.Memo == "" {
 | 
			
		||||
		server.Memo = Conf.Default.Memo
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if server.WordPress == nil {
 | 
			
		||||
		server.WordPress = Conf.Default.WordPress
 | 
			
		||||
		if server.WordPress == nil {
 | 
			
		||||
			server.WordPress = &WordPressConf{}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(server.IgnoredJSONKeys) == 0 {
 | 
			
		||||
		server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := map[string]interface{}{}
 | 
			
		||||
	for k, v := range Conf.Default.Optional {
 | 
			
		||||
		opt[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range server.Optional {
 | 
			
		||||
		opt[k] = v
 | 
			
		||||
	}
 | 
			
		||||
	server.Optional = opt
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -290,19 +233,5 @@ func toCpeURI(cpename string) (string, error) {
 | 
			
		||||
		}
 | 
			
		||||
		return naming.BindToURI(wfn), nil
 | 
			
		||||
	}
 | 
			
		||||
	return "", xerrors.Errorf("Unknow CPE format: %s", cpename)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsValidImage checks a container configuration
 | 
			
		||||
func IsValidImage(c Image) error {
 | 
			
		||||
	if c.Name == "" {
 | 
			
		||||
		return xerrors.New("Invalid arguments : no image name")
 | 
			
		||||
	}
 | 
			
		||||
	if c.Tag == "" && c.Digest == "" {
 | 
			
		||||
		return xerrors.New("Invalid arguments : no image tag and digest")
 | 
			
		||||
	}
 | 
			
		||||
	if c.Tag != "" && c.Digest != "" {
 | 
			
		||||
		return xerrors.New("Invalid arguments : you can either set image tag or digest")
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
	return "", xerrors.Errorf("Unknown CPE format: %s", cpename)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,62 +42,3 @@ func TestToCpeURI(t *testing.T) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsValidImage(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		name     string
 | 
			
		||||
		img      Image
 | 
			
		||||
		errOccur bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "ok with tag",
 | 
			
		||||
			img: Image{
 | 
			
		||||
				Name: "ok",
 | 
			
		||||
				Tag:  "ok",
 | 
			
		||||
			},
 | 
			
		||||
			errOccur: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "ok with digest",
 | 
			
		||||
			img: Image{
 | 
			
		||||
				Name:   "ok",
 | 
			
		||||
				Digest: "ok",
 | 
			
		||||
			},
 | 
			
		||||
			errOccur: false,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			name: "no image name with tag",
 | 
			
		||||
			img: Image{
 | 
			
		||||
				Tag: "ok",
 | 
			
		||||
			},
 | 
			
		||||
			errOccur: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			name: "no image name with digest",
 | 
			
		||||
			img: Image{
 | 
			
		||||
				Digest: "ok",
 | 
			
		||||
			},
 | 
			
		||||
			errOccur: true,
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		{
 | 
			
		||||
			name: "no tag and digest",
 | 
			
		||||
			img: Image{
 | 
			
		||||
				Name: "ok",
 | 
			
		||||
			},
 | 
			
		||||
			errOccur: true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			err := IsValidImage(tt.img)
 | 
			
		||||
			actual := err != nil
 | 
			
		||||
			if actual != tt.errOccur {
 | 
			
		||||
				t.Errorf("[%d] act: %v, exp: %v",
 | 
			
		||||
					i, actual, tt.errOccur)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										58
									
								
								constant/constant.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,58 @@
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
	// 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"
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										38
									
								
								contrib/future-vuls/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,38 @@
 | 
			
		||||
# future-vuls
 | 
			
		||||
 | 
			
		||||
## Main Features
 | 
			
		||||
 | 
			
		||||
- upload vuls results json to future-vuls
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
git clone https://github.com/future-architect/vuls.git
 | 
			
		||||
make build-future-vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Command Reference
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Upload to FutureVuls
 | 
			
		||||
 | 
			
		||||
Usage:
 | 
			
		||||
  future-vuls upload [flags]
 | 
			
		||||
 | 
			
		||||
Flags:
 | 
			
		||||
      --config string   config file (default is $HOME/.cobra.yaml)
 | 
			
		||||
  -g, --group-id int    future vuls group id, ENV: VULS_GROUP_ID
 | 
			
		||||
  -h, --help            help for upload
 | 
			
		||||
  -s, --stdin           input from stdin. ENV: VULS_STDIN
 | 
			
		||||
  -t, --token string    future vuls token
 | 
			
		||||
      --url string      future vuls upload url
 | 
			
		||||
      --uuid string     server uuid. ENV: VULS_SERVER_UUID
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
- update results json
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 cat results.json | future-vuls upload --stdin --token xxxx --url https://xxxx --group-id 1 --uuid xxxx
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										98
									
								
								contrib/future-vuls/cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,98 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/saas"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	configFile string
 | 
			
		||||
	stdIn      bool
 | 
			
		||||
	jsonDir    string
 | 
			
		||||
	serverUUID string
 | 
			
		||||
	groupID    int64
 | 
			
		||||
	token      string
 | 
			
		||||
	url        string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	var err error
 | 
			
		||||
	var cmdFvulsUploader = &cobra.Command{
 | 
			
		||||
		Use:   "upload",
 | 
			
		||||
		Short: "Upload to FutureVuls",
 | 
			
		||||
		Long:  `Upload to FutureVuls`,
 | 
			
		||||
		Run: func(cmd *cobra.Command, args []string) {
 | 
			
		||||
			if len(serverUUID) == 0 {
 | 
			
		||||
				serverUUID = os.Getenv("VULS_SERVER_UUID")
 | 
			
		||||
			}
 | 
			
		||||
			if groupID == 0 {
 | 
			
		||||
				envGroupID := os.Getenv("VULS_GROUP_ID")
 | 
			
		||||
				if groupID, err = strconv.ParseInt(envGroupID, 10, 64); err != nil {
 | 
			
		||||
					fmt.Printf("Invalid GroupID: %s\n", envGroupID)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if len(url) == 0 {
 | 
			
		||||
				url = os.Getenv("VULS_URL")
 | 
			
		||||
			}
 | 
			
		||||
			if len(token) == 0 {
 | 
			
		||||
				token = os.Getenv("VULS_TOKEN")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var scanResultJSON []byte
 | 
			
		||||
			if stdIn {
 | 
			
		||||
				reader := bufio.NewReader(os.Stdin)
 | 
			
		||||
				buf := new(bytes.Buffer)
 | 
			
		||||
				if _, err = buf.ReadFrom(reader); err != nil {
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				scanResultJSON = buf.Bytes()
 | 
			
		||||
			} else {
 | 
			
		||||
				fmt.Println("use --stdin option")
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var scanResult models.ScanResult
 | 
			
		||||
			if err = json.Unmarshal(scanResultJSON, &scanResult); err != nil {
 | 
			
		||||
				fmt.Println("Failed to parse json", err)
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			scanResult.ServerUUID = serverUUID
 | 
			
		||||
 | 
			
		||||
			config.Conf.Saas.GroupID = groupID
 | 
			
		||||
			config.Conf.Saas.Token = token
 | 
			
		||||
			config.Conf.Saas.URL = url
 | 
			
		||||
			if err = (saas.Writer{}).Write(scanResult); err != nil {
 | 
			
		||||
				fmt.Println(err)
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			return
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().StringVar(&serverUUID, "uuid", "", "server uuid. ENV: VULS_SERVER_UUID")
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().StringVar(&configFile, "config", "", "config file (default is $HOME/.cobra.yaml)")
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin. ENV: VULS_STDIN")
 | 
			
		||||
	// TODO Read JSON file from directory
 | 
			
		||||
	//	cmdFvulsUploader.Flags().StringVarP(&jsonDir, "results-dir", "d", "./", "vuls scan results json dir")
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().Int64VarP(&groupID, "group-id", "g", 0, "future vuls group id, ENV: VULS_GROUP_ID")
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().StringVarP(&token, "token", "t", "", "future vuls token")
 | 
			
		||||
	cmdFvulsUploader.PersistentFlags().StringVar(&url, "url", "", "future vuls upload url")
 | 
			
		||||
 | 
			
		||||
	var rootCmd = &cobra.Command{Use: "future-vuls"}
 | 
			
		||||
	rootCmd.AddCommand(cmdFvulsUploader)
 | 
			
		||||
	if err = rootCmd.Execute(); err != nil {
 | 
			
		||||
		fmt.Println("Failed to execute command", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -16,11 +16,11 @@ type analysis struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type dependency struct {
 | 
			
		||||
	Identifiers []vulnerabilityId `xml:"identifiers>vulnerabilityIds"`
 | 
			
		||||
	Identifiers []vulnerabilityID `xml:"identifiers>vulnerabilityIds"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type vulnerabilityId struct {
 | 
			
		||||
	Id string `xml:"id"`
 | 
			
		||||
type vulnerabilityID struct {
 | 
			
		||||
	ID string `xml:"id"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendIfMissing(slice []string, str string) []string {
 | 
			
		||||
@@ -55,7 +55,7 @@ func Parse(path string) ([]string, error) {
 | 
			
		||||
	cpes := []string{}
 | 
			
		||||
	for _, d := range anal.Dependencies {
 | 
			
		||||
		for _, ident := range d.Identifiers {
 | 
			
		||||
			id := ident.Id // Start with cpe:2.3:
 | 
			
		||||
			id := ident.ID // Start with cpe:2.3:
 | 
			
		||||
			// Convert from CPE 2.3 to CPE 2.2
 | 
			
		||||
			if strings.HasPrefix(id, "cpe:2.3:") {
 | 
			
		||||
				wfn, err := naming.UnbindFS(id)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										35
									
								
								contrib/trivy/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,35 @@
 | 
			
		||||
# trivy-to-vuls
 | 
			
		||||
 | 
			
		||||
## Main Features
 | 
			
		||||
 | 
			
		||||
- convert trivy's results json to vuls's report json
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
git clone https://github.com/future-architect/vuls.git
 | 
			
		||||
make build-trivy-to-vuls
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Command Reference
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Parse trivy json to vuls results
 | 
			
		||||
 | 
			
		||||
Usage:
 | 
			
		||||
  trivy-to-vuls parse [flags]
 | 
			
		||||
 | 
			
		||||
Flags:
 | 
			
		||||
  -h, --help                          help for parse
 | 
			
		||||
  -s, --stdin                         input from stdin
 | 
			
		||||
  -d, --trivy-json-dir string         trivy json dir (default "./")
 | 
			
		||||
  -f, --trivy-json-file-name string   trivy json file name (default "results.json")
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
- use trivy output
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 trivy -q image -f=json python:3.4-alpine | trivy-to-vuls parse --stdin
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										78
									
								
								contrib/trivy/cmd/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,78 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/contrib/trivy/parser"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/spf13/cobra"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	serverUUID   string
 | 
			
		||||
	stdIn        bool
 | 
			
		||||
	jsonDir      string
 | 
			
		||||
	jsonFileName string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	var err error
 | 
			
		||||
	var cmdTrivyToVuls = &cobra.Command{
 | 
			
		||||
		Use:   "parse",
 | 
			
		||||
		Short: "Parse trivy json to vuls results",
 | 
			
		||||
		Long:  `Parse trivy json to vuls results`,
 | 
			
		||||
		Run: func(cmd *cobra.Command, args []string) {
 | 
			
		||||
			jsonFilePath := filepath.Join(jsonDir, jsonFileName)
 | 
			
		||||
			var trivyJSON []byte
 | 
			
		||||
			if stdIn {
 | 
			
		||||
				reader := bufio.NewReader(os.Stdin)
 | 
			
		||||
				buf := new(bytes.Buffer)
 | 
			
		||||
				if _, err = buf.ReadFrom(reader); err != nil {
 | 
			
		||||
					os.Exit(1)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
				trivyJSON = buf.Bytes()
 | 
			
		||||
			} else {
 | 
			
		||||
				if trivyJSON, err = ioutil.ReadFile(jsonFilePath); err != nil {
 | 
			
		||||
					fmt.Println("Failed to read file", err)
 | 
			
		||||
					os.Exit(1)
 | 
			
		||||
					return
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			scanResult := &models.ScanResult{
 | 
			
		||||
				JSONVersion: models.JSONVersion,
 | 
			
		||||
				ScannedCves: models.VulnInfos{},
 | 
			
		||||
			}
 | 
			
		||||
			if scanResult, err = parser.Parse(trivyJSON, scanResult); err != nil {
 | 
			
		||||
				fmt.Println("Failed to execute command", err)
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			var resultJSON []byte
 | 
			
		||||
			if resultJSON, err = json.MarshalIndent(scanResult, "", "   "); err != nil {
 | 
			
		||||
				fmt.Println("Failed to create json", err)
 | 
			
		||||
				os.Exit(1)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			fmt.Println(string(resultJSON))
 | 
			
		||||
			return
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	cmdTrivyToVuls.Flags().BoolVarP(&stdIn, "stdin", "s", false, "input from stdin")
 | 
			
		||||
	cmdTrivyToVuls.Flags().StringVarP(&jsonDir, "trivy-json-dir", "d", "./", "trivy json dir")
 | 
			
		||||
	cmdTrivyToVuls.Flags().StringVarP(&jsonFileName, "trivy-json-file-name", "f", "results.json", "trivy json file name")
 | 
			
		||||
 | 
			
		||||
	var rootCmd = &cobra.Command{Use: "trivy-to-vuls"}
 | 
			
		||||
	rootCmd.AddCommand(cmdTrivyToVuls)
 | 
			
		||||
	if err = rootCmd.Execute(); err != nil {
 | 
			
		||||
		fmt.Println("Failed to execute command", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								contrib/trivy/parser/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,180 @@
 | 
			
		||||
package parser
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/aquasecurity/fanal/analyzer/os"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/report"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/types"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Parse :
 | 
			
		||||
func Parse(vulnJSON []byte, scanResult *models.ScanResult) (result *models.ScanResult, err error) {
 | 
			
		||||
	var trivyResults report.Results
 | 
			
		||||
	if err = json.Unmarshal(vulnJSON, &trivyResults); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pkgs := models.Packages{}
 | 
			
		||||
	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{
 | 
			
		||||
					CveID: vuln.VulnerabilityID,
 | 
			
		||||
					Confidences: models.Confidences{
 | 
			
		||||
						{
 | 
			
		||||
							Score:           100,
 | 
			
		||||
							DetectionMethod: models.TrivyMatchStr,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					AffectedPackages: models.PackageFixStatuses{},
 | 
			
		||||
					CveContents:      models.CveContents{},
 | 
			
		||||
					LibraryFixedIns:  models.LibraryFixedIns{},
 | 
			
		||||
					// VulnType : "",
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			vulnInfo := vulnInfos[vuln.VulnerabilityID]
 | 
			
		||||
			var notFixedYet bool
 | 
			
		||||
			fixState := ""
 | 
			
		||||
			if len(vuln.FixedVersion) == 0 {
 | 
			
		||||
				notFixedYet = true
 | 
			
		||||
				fixState = "Affected"
 | 
			
		||||
			}
 | 
			
		||||
			var references models.References
 | 
			
		||||
			for _, reference := range vuln.References {
 | 
			
		||||
				references = append(references, models.Reference{
 | 
			
		||||
					Source: "trivy",
 | 
			
		||||
					Link:   reference,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			sort.Slice(references, func(i, j int) bool {
 | 
			
		||||
				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
 | 
			
		||||
			if IsTrivySupportedOS(trivyResult.Type) {
 | 
			
		||||
				pkgs[vuln.PkgName] = models.Package{
 | 
			
		||||
					Name:    vuln.PkgName,
 | 
			
		||||
					Version: vuln.InstalledVersion,
 | 
			
		||||
				}
 | 
			
		||||
				vulnInfo.AffectedPackages = append(vulnInfo.AffectedPackages, models.PackageFixStatus{
 | 
			
		||||
					Name:        vuln.PkgName,
 | 
			
		||||
					NotFixedYet: notFixedYet,
 | 
			
		||||
					FixState:    fixState,
 | 
			
		||||
					FixedIn:     vuln.FixedVersion,
 | 
			
		||||
				})
 | 
			
		||||
			} else {
 | 
			
		||||
				// LibraryScanの結果
 | 
			
		||||
				vulnInfo.LibraryFixedIns = append(vulnInfo.LibraryFixedIns, models.LibraryFixedIn{
 | 
			
		||||
					Key:     trivyResult.Type,
 | 
			
		||||
					Name:    vuln.PkgName,
 | 
			
		||||
					Path:    trivyResult.Target,
 | 
			
		||||
					FixedIn: vuln.FixedVersion,
 | 
			
		||||
				})
 | 
			
		||||
				libScanner := uniqueLibraryScannerPaths[trivyResult.Target]
 | 
			
		||||
				libScanner.Libs = append(libScanner.Libs, types.Library{
 | 
			
		||||
					Name:    vuln.PkgName,
 | 
			
		||||
					Version: vuln.InstalledVersion,
 | 
			
		||||
				})
 | 
			
		||||
				uniqueLibraryScannerPaths[trivyResult.Target] = libScanner
 | 
			
		||||
			}
 | 
			
		||||
			vulnInfos[vuln.VulnerabilityID] = vulnInfo
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// flatten and unique libraries
 | 
			
		||||
	libraryScanners := make([]models.LibraryScanner, 0, len(uniqueLibraryScannerPaths))
 | 
			
		||||
	for path, v := range uniqueLibraryScannerPaths {
 | 
			
		||||
		uniqueLibrary := map[string]types.Library{}
 | 
			
		||||
		for _, lib := range v.Libs {
 | 
			
		||||
			uniqueLibrary[lib.Name+lib.Version] = lib
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var libraries []types.Library
 | 
			
		||||
		for _, library := range uniqueLibrary {
 | 
			
		||||
			libraries = append(libraries, library)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		sort.Slice(libraries, func(i, j int) bool {
 | 
			
		||||
			return libraries[i].Name < libraries[j].Name
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		libscanner := models.LibraryScanner{
 | 
			
		||||
			Path: path,
 | 
			
		||||
			Libs: libraries,
 | 
			
		||||
		}
 | 
			
		||||
		libraryScanners = append(libraryScanners, libscanner)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Slice(libraryScanners, func(i, j int) bool {
 | 
			
		||||
		return libraryScanners[i].Path < libraryScanners[j].Path
 | 
			
		||||
	})
 | 
			
		||||
	scanResult.ScannedCves = vulnInfos
 | 
			
		||||
	scanResult.Packages = pkgs
 | 
			
		||||
	scanResult.LibraryScanners = libraryScanners
 | 
			
		||||
	return scanResult, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsTrivySupportedOS :
 | 
			
		||||
func IsTrivySupportedOS(family string) bool {
 | 
			
		||||
	supportedFamilies := []string{
 | 
			
		||||
		os.RedHat,
 | 
			
		||||
		os.Debian,
 | 
			
		||||
		os.Ubuntu,
 | 
			
		||||
		os.CentOS,
 | 
			
		||||
		os.Fedora,
 | 
			
		||||
		os.Amazon,
 | 
			
		||||
		os.Oracle,
 | 
			
		||||
		os.Windows,
 | 
			
		||||
		os.OpenSUSE,
 | 
			
		||||
		os.OpenSUSELeap,
 | 
			
		||||
		os.OpenSUSETumbleweed,
 | 
			
		||||
		os.SLES,
 | 
			
		||||
		os.Photon,
 | 
			
		||||
		os.Alpine,
 | 
			
		||||
	}
 | 
			
		||||
	for _, supportedFamily := range supportedFamilies {
 | 
			
		||||
		if family == supportedFamily {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	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"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5510
									
								
								contrib/trivy/parser/parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -29,5 +29,5 @@ var SansTopTwentyfive = map[string]string{
 | 
			
		||||
	"759": "25",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SansTopTwentyfiveURL
 | 
			
		||||
// SansTopTwentyfiveURL is a URL of sans 25
 | 
			
		||||
var SansTopTwentyfiveURL = "https://www.sans.org/top25-software-errors/"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,6 @@
 | 
			
		||||
package report
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
@@ -11,69 +13,61 @@ 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 disctionary service.
 | 
			
		||||
var CveClient cvedictClient
 | 
			
		||||
 | 
			
		||||
type cvedictClient struct {
 | 
			
		||||
	//  httpProxy string
 | 
			
		||||
	baseURL string
 | 
			
		||||
type goCveDictClient struct {
 | 
			
		||||
	cnf    config.VulnDictInterface
 | 
			
		||||
	driver cvedb.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (api *cvedictClient) initialize() {
 | 
			
		||||
	api.baseURL = config.Conf.CveDict.URL
 | 
			
		||||
func newGoCveDictClient(cnf config.VulnDictInterface, o logging.LogOpts) (*goCveDictClient, error) {
 | 
			
		||||
	cvelog.SetLogger(o.LogDir, o.Quiet, o.Debug, false)
 | 
			
		||||
 | 
			
		||||
	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 cvedictClient) CheckHealth() error {
 | 
			
		||||
	if !config.Conf.CveDict.IsFetchViaHTTP() {
 | 
			
		||||
		util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDict.Type)
 | 
			
		||||
func (api goCveDictClient) closeDB() error {
 | 
			
		||||
	if api.driver == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	api.initialize()
 | 
			
		||||
	url := fmt.Sprintf("%s/health", api.baseURL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	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 CVE server. url: %s, errs: %w",
 | 
			
		||||
			url, errs)
 | 
			
		||||
	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 {
 | 
			
		||||
	Key       string
 | 
			
		||||
	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))
 | 
			
		||||
@@ -93,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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -129,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: %w",
 | 
			
		||||
			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 {
 | 
			
		||||
@@ -153,7 +145,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
 | 
			
		||||
	}
 | 
			
		||||
	cveDetail := cvemodels.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
 | 
			
		||||
		errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
 | 
			
		||||
		errChan <- xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	resChan <- response{
 | 
			
		||||
@@ -162,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: %w", 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 {
 | 
			
		||||
@@ -204,7 +194,23 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
 | 
			
		||||
	cveDetails := []cvemodels.CveDetail{}
 | 
			
		||||
	if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
 | 
			
		||||
		return nil,
 | 
			
		||||
			xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
 | 
			
		||||
			xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										472
									
								
								detector/detector.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,472 @@
 | 
			
		||||
// +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, config.Conf.IgnoreGitHubDismissed); 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)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logging.Log.Infof("Fill CVE detailed with gost")
 | 
			
		||||
		if err := gost.FillCVEsWithRedHat(&r, config.Conf.Gost); err != nil {
 | 
			
		||||
			return nil, xerrors.Errorf("Failed to fill with gost: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		logging.Log.Infof("Fill CVE detailed with go-cve-dictionary")
 | 
			
		||||
		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 exploits 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 modules 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
 | 
			
		||||
		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, ignoreDismissed bool) 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, ignoreDismissed)
 | 
			
		||||
		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("Detect WordPress CVE. pkgs: %d ", 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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nCVEs, err := client.DetectUnfixed(r, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("Failed to detect unfixed CVEs with gost: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			if val, ok := r.ScannedCves[detail.CveID]; ok {
 | 
			
		||||
				names := val.CpeURIs
 | 
			
		||||
				names = util.AppendIfMissing(names, name)
 | 
			
		||||
				val.CpeURIs = names
 | 
			
		||||
				val.Confidences.AppendIfMissing(models.CpeNameMatch)
 | 
			
		||||
				r.ScannedCves[detail.CveID] = val
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:       detail.CveID,
 | 
			
		||||
					CpeURIs:     []string{name},
 | 
			
		||||
					Confidences: models.Confidences{models.CpeNameMatch},
 | 
			
		||||
				}
 | 
			
		||||
				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
									
								
							
							
						
						@@ -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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										198
									
								
								detector/github.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,198 @@
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/errof"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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/
 | 
			
		||||
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
 | 
			
		||||
	// Memo : https://developer.github.com/v4/explorer/
 | 
			
		||||
	const jsonfmt = `{"query":
 | 
			
		||||
	"query { repository(owner:\"%s\", name:\"%s\") { url vulnerabilityAlerts(first: %d, %s) { pageInfo { endCursor hasNextPage startCursor } edges { node { id dismissReason dismissedAt securityVulnerability{ package { name ecosystem } severity vulnerableVersionRange firstPatchedVersion { identifier } } securityAdvisory { description ghsaId permalink publishedAt summary updatedAt withdrawnAt origin severity references { url } identifiers { type value } } } } } } } "}`
 | 
			
		||||
	after := ""
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
 | 
			
		||||
		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
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// https://developer.github.com/v4/previews/#repository-vulnerability-alerts
 | 
			
		||||
		// To toggle this preview and access data, need to provide a custom media type in the Accept header:
 | 
			
		||||
		// MEMO: I tried to get the affected version via GitHub API. Bit it seems difficult to determin the affected version if there are multiple dependency files such as package.json.
 | 
			
		||||
		// TODO remove this header if it is no longer preview status in the future.
 | 
			
		||||
		req.Header.Set("Accept", "application/vnd.github.package-deletes-preview+json")
 | 
			
		||||
		req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
 | 
			
		||||
		resp, err := httpClient.Do(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
		body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		alerts := SecurityAlerts{}
 | 
			
		||||
		if err := json.Unmarshal(body, &alerts); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// util.Log.Debugf("%s", pp.Sprint(alerts))
 | 
			
		||||
		// util.Log.Debugf("%s", string(body))
 | 
			
		||||
		if alerts.Data.Repository.URL == "" {
 | 
			
		||||
			return 0, errof.New(errof.ErrFailedToAccessGithubAPI,
 | 
			
		||||
				fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
 | 
			
		||||
			if ignoreDismissed && v.Node.DismissReason != "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pkgName := fmt.Sprintf("%s %s",
 | 
			
		||||
				alerts.Data.Repository.URL, v.Node.SecurityVulnerability.Package.Name)
 | 
			
		||||
 | 
			
		||||
			m := models.GitHubSecurityAlert{
 | 
			
		||||
				PackageName:   pkgName,
 | 
			
		||||
				FixedIn:       v.Node.SecurityVulnerability.FirstPatchedVersion.Identifier,
 | 
			
		||||
				AffectedRange: v.Node.SecurityVulnerability.VulnerableVersionRange,
 | 
			
		||||
				Dismissed:     len(v.Node.DismissReason) != 0,
 | 
			
		||||
				DismissedAt:   v.Node.DismissedAt,
 | 
			
		||||
				DismissReason: v.Node.DismissReason,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cveIDs, other := []string{}, []string{}
 | 
			
		||||
			for _, identifier := range v.Node.SecurityAdvisory.Identifiers {
 | 
			
		||||
				if identifier.Type == "CVE" {
 | 
			
		||||
					cveIDs = append(cveIDs, identifier.Value)
 | 
			
		||||
				} else {
 | 
			
		||||
					other = append(other, identifier.Value)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// If CVE-ID has not been assigned, use the GHSA ID etc as a ID.
 | 
			
		||||
			if len(cveIDs) == 0 {
 | 
			
		||||
				cveIDs = other
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			refs := []models.Reference{}
 | 
			
		||||
			for _, r := range v.Node.SecurityAdvisory.References {
 | 
			
		||||
				refs = append(refs, models.Reference{Link: r.URL})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, cveID := range cveIDs {
 | 
			
		||||
				cveContent := models.CveContent{
 | 
			
		||||
					Type:          models.GitHub,
 | 
			
		||||
					CveID:         cveID,
 | 
			
		||||
					Title:         v.Node.SecurityAdvisory.Summary,
 | 
			
		||||
					Summary:       v.Node.SecurityAdvisory.Description,
 | 
			
		||||
					Cvss2Severity: v.Node.SecurityVulnerability.Severity,
 | 
			
		||||
					Cvss3Severity: v.Node.SecurityVulnerability.Severity,
 | 
			
		||||
					SourceLink:    v.Node.SecurityAdvisory.Permalink,
 | 
			
		||||
					References:    refs,
 | 
			
		||||
					Published:     v.Node.SecurityAdvisory.PublishedAt,
 | 
			
		||||
					LastModified:  v.Node.SecurityAdvisory.UpdatedAt,
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if val, ok := r.ScannedCves[cveID]; ok {
 | 
			
		||||
					val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m)
 | 
			
		||||
					val.CveContents[models.GitHub] = cveContent
 | 
			
		||||
					r.ScannedCves[cveID] = val
 | 
			
		||||
				} else {
 | 
			
		||||
					v := models.VulnInfo{
 | 
			
		||||
						CveID:                cveID,
 | 
			
		||||
						Confidences:          models.Confidences{models.GitHubMatch},
 | 
			
		||||
						GitHubSecurityAlerts: models.GitHubSecurityAlerts{m},
 | 
			
		||||
						CveContents:          models.NewCveContents(cveContent),
 | 
			
		||||
					}
 | 
			
		||||
					r.ScannedCves[cveID] = v
 | 
			
		||||
				}
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		after = fmt.Sprintf(`after: \"%s\"`, alerts.Data.Repository.VulnerabilityAlerts.PageInfo.EndCursor)
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
 | 
			
		||||
type SecurityAlerts struct {
 | 
			
		||||
	Data struct {
 | 
			
		||||
		Repository struct {
 | 
			
		||||
			URL                 string `json:"url"`
 | 
			
		||||
			VulnerabilityAlerts struct {
 | 
			
		||||
				PageInfo struct {
 | 
			
		||||
					EndCursor   string `json:"endCursor"`
 | 
			
		||||
					HasNextPage bool   `json:"hasNextPage"`
 | 
			
		||||
					StartCursor string `json:"startCursor"`
 | 
			
		||||
				} `json:"pageInfo"`
 | 
			
		||||
				Edges []struct {
 | 
			
		||||
					Node struct {
 | 
			
		||||
						ID                    string    `json:"id"`
 | 
			
		||||
						DismissReason         string    `json:"dismissReason"`
 | 
			
		||||
						DismissedAt           time.Time `json:"dismissedAt"`
 | 
			
		||||
						SecurityVulnerability struct {
 | 
			
		||||
							Package struct {
 | 
			
		||||
								Name      string `json:"name"`
 | 
			
		||||
								Ecosystem string `json:"ecosystem"`
 | 
			
		||||
							} `json:"package"`
 | 
			
		||||
							Severity               string `json:"severity"`
 | 
			
		||||
							VulnerableVersionRange string `json:"vulnerableVersionRange"`
 | 
			
		||||
							FirstPatchedVersion    struct {
 | 
			
		||||
								Identifier string `json:"identifier"`
 | 
			
		||||
							} `json:"firstPatchedVersion"`
 | 
			
		||||
						} `json:"securityVulnerability"`
 | 
			
		||||
						SecurityAdvisory struct {
 | 
			
		||||
							Description string    `json:"description"`
 | 
			
		||||
							GhsaID      string    `json:"ghsaId"`
 | 
			
		||||
							Permalink   string    `json:"permalink"`
 | 
			
		||||
							PublishedAt time.Time `json:"publishedAt"`
 | 
			
		||||
							Summary     string    `json:"summary"`
 | 
			
		||||
							UpdatedAt   time.Time `json:"updatedAt"`
 | 
			
		||||
							WithdrawnAt time.Time `json:"withdrawnAt"`
 | 
			
		||||
							Origin      string    `json:"origin"`
 | 
			
		||||
							Severity    string    `json:"severity"`
 | 
			
		||||
							References  []struct {
 | 
			
		||||
								URL string `json:"url"`
 | 
			
		||||
							} `json:"references"`
 | 
			
		||||
							Identifiers []struct {
 | 
			
		||||
								Type  string `json:"type"`
 | 
			
		||||
								Value string `json:"value"`
 | 
			
		||||
							} `json:"identifiers"`
 | 
			
		||||
						} `json:"securityAdvisory"`
 | 
			
		||||
					} `json:"node"`
 | 
			
		||||
				} `json:"edges"`
 | 
			
		||||
			} `json:"vulnerabilityAlerts"`
 | 
			
		||||
		} `json:"repository"`
 | 
			
		||||
	} `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										111
									
								
								detector/library.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,111 @@
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	db2 "github.com/aquasecurity/trivy-db/pkg/db"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/db"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/github"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/indicator"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/log"
 | 
			
		||||
	"github.com/spf13/afero"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
	"k8s.io/utils/clock"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/logging"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DetectLibsCves fills LibraryScanner information
 | 
			
		||||
func DetectLibsCves(r *models.ScanResult, cacheDir string, noProgress bool) (err error) {
 | 
			
		||||
	totalCnt := 0
 | 
			
		||||
	if len(r.LibraryScanners) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// initialize trivy's logger and db
 | 
			
		||||
	err = log.InitLogger(false, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Info("Updating library db...")
 | 
			
		||||
	if err := downloadDB("", cacheDir, noProgress, false, false); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := db2.Init(cacheDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer db2.Close()
 | 
			
		||||
 | 
			
		||||
	for _, lib := range r.LibraryScanners {
 | 
			
		||||
		vinfos, err := lib.Scan()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for _, vinfo := range vinfos {
 | 
			
		||||
			vinfo.Confidences.AppendIfMissing(models.TrivyMatch)
 | 
			
		||||
			if v, ok := r.ScannedCves[vinfo.CveID]; !ok {
 | 
			
		||||
				r.ScannedCves[vinfo.CveID] = vinfo
 | 
			
		||||
			} else {
 | 
			
		||||
				v.LibraryFixedIns = append(v.LibraryFixedIns, vinfo.LibraryFixedIns...)
 | 
			
		||||
				r.ScannedCves[vinfo.CveID] = v
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		totalCnt += len(vinfos)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logging.Log.Infof("%s: %d CVEs are detected with Library",
 | 
			
		||||
		r.FormatServerName(), totalCnt)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func downloadDB(appVersion, cacheDir string, quiet, light, skipUpdate bool) error {
 | 
			
		||||
	client := initializeDBClient(cacheDir, quiet)
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	needsUpdate, err := client.NeedsUpdate(appVersion, light, skipUpdate)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("database error: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if needsUpdate {
 | 
			
		||||
		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)
 | 
			
		||||
		}
 | 
			
		||||
		if err = client.UpdateMetadata(cacheDir); err != nil {
 | 
			
		||||
			return xerrors.Errorf("unable to update database metadata: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// for debug
 | 
			
		||||
	if err := showDBInfo(cacheDir); err != nil {
 | 
			
		||||
		return xerrors.Errorf("failed to show database info: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initializeDBClient(cacheDir string, quiet bool) db.Client {
 | 
			
		||||
	config := db2.Config{}
 | 
			
		||||
	client := github.NewClient()
 | 
			
		||||
	progressBar := indicator.NewProgressBar(quiet)
 | 
			
		||||
	realClock := clock.RealClock{}
 | 
			
		||||
	fs := afero.NewOsFs()
 | 
			
		||||
	metadata := db.NewMetadata(fs, cacheDir)
 | 
			
		||||
	dbClient := db.NewClient(config, client, progressBar, realClock, metadata)
 | 
			
		||||
	return dbClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func showDBInfo(cacheDir string) error {
 | 
			
		||||
	m := db.NewMetadata(afero.NewOsFs(), cacheDir)
 | 
			
		||||
	metadata, err := m.Get()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return xerrors.Errorf("something wrong with DB: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	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
									
								
							
							
						
						@@ -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")
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										270
									
								
								detector/util.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,270 @@
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										270
									
								
								detector/wordpress.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,270 @@
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//WpCveInfos is for wpscan json
 | 
			
		||||
type WpCveInfos struct {
 | 
			
		||||
	ReleaseDate  string `json:"release_date"`
 | 
			
		||||
	ChangelogURL string `json:"changelog_url"`
 | 
			
		||||
	// Status        string `json:"status"`
 | 
			
		||||
	LatestVersion string `json:"latest_version"`
 | 
			
		||||
	LastUpdated   string `json:"last_updated"`
 | 
			
		||||
	// Popular         bool        `json:"popular"`
 | 
			
		||||
	Vulnerabilities []WpCveInfo `json:"vulnerabilities"`
 | 
			
		||||
	Error           string      `json:"error"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//WpCveInfo is for wpscan json
 | 
			
		||||
type WpCveInfo struct {
 | 
			
		||||
	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"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//References is for wpscan json
 | 
			
		||||
type References struct {
 | 
			
		||||
	URL     []string `json:"url"`
 | 
			
		||||
	Cve     []string `json:"cve"`
 | 
			
		||||
	Secunia []string `json:"secunia"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectWordPressCves access to wpscan and fetch scurity alerts and then set to the given ScanResult.
 | 
			
		||||
// https://wpscan.com/
 | 
			
		||||
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, 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, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Themes
 | 
			
		||||
	themes := r.WordPressPackages.Themes()
 | 
			
		||||
	if !cnf.DetectInactive {
 | 
			
		||||
		themes = removeInactives(themes)
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range themes {
 | 
			
		||||
		url := fmt.Sprintf("https://wpscan.com/api/v3/themes/%s", p.Name)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		vulns := detect(p, candidates)
 | 
			
		||||
		wpVinfos = append(wpVinfos, vulns...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Plugins
 | 
			
		||||
	plugins := r.WordPressPackages.Plugins()
 | 
			
		||||
	if !cnf.DetectInactive {
 | 
			
		||||
		plugins = removeInactives(plugins)
 | 
			
		||||
	}
 | 
			
		||||
	for _, p := range plugins {
 | 
			
		||||
		url := fmt.Sprintf("https://wpscan.com/api/v3/plugins/%s", p.Name)
 | 
			
		||||
		candidates, err := wpscan(url, p.Name, cnf.Token, false)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		vulns := detect(p, candidates)
 | 
			
		||||
		wpVinfos = append(wpVinfos, vulns...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, wpVinfo := range wpVinfos {
 | 
			
		||||
		if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok {
 | 
			
		||||
			vinfo.CveContents[models.WpScan] = wpVinfo.CveContents[models.WpScan]
 | 
			
		||||
			vinfo.VulnType = wpVinfo.VulnType
 | 
			
		||||
			vinfo.Confidences = append(vinfo.Confidences, wpVinfo.Confidences...)
 | 
			
		||||
			vinfo.WpPackageFixStats = append(vinfo.WpPackageFixStats, wpVinfo.WpPackageFixStats...)
 | 
			
		||||
			r.ScannedCves[wpVinfo.CveID] = vinfo
 | 
			
		||||
		} else {
 | 
			
		||||
			r.ScannedCves[wpVinfo.CveID] = wpVinfo
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return len(wpVinfos), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wpscan(url, name, token string, isCore bool) (vinfos []models.VulnInfo, err error) {
 | 
			
		||||
	body, err := httpRequest(url, token)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if body == "" {
 | 
			
		||||
		logging.Log.Debugf("wpscan.com response body is empty. URL: %s", url)
 | 
			
		||||
	}
 | 
			
		||||
	if isCore {
 | 
			
		||||
		name = "core"
 | 
			
		||||
	}
 | 
			
		||||
	return convertToVinfos(name, body)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func detect(installed models.WpPackage, candidates []models.VulnInfo) (vulns []models.VulnInfo) {
 | 
			
		||||
	for _, v := range candidates {
 | 
			
		||||
		for _, fixstat := range v.WpPackageFixStats {
 | 
			
		||||
			ok, err := match(installed.Version, fixstat.FixedIn)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				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)
 | 
			
		||||
				logging.Log.Debugf("Affected: %s installed: %s, fixedIn: %s",
 | 
			
		||||
					installed.Name, installed.Version, fixstat.FixedIn)
 | 
			
		||||
			} else {
 | 
			
		||||
				logging.Log.Debugf("Not affected: %s : %s, fixedIn: %s",
 | 
			
		||||
					installed.Name, installed.Version, fixstat.FixedIn)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func match(installedVer, fixedIn string) (bool, error) {
 | 
			
		||||
	v1, err := version.NewVersion(installedVer)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	v2, err := version.NewVersion(fixedIn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, err
 | 
			
		||||
	}
 | 
			
		||||
	return v1.LessThan(v2), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func convertToVinfos(pkgName, body string) (vinfos []models.VulnInfo, err error) {
 | 
			
		||||
	if body == "" {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// "pkgName" : CVE Detailed data
 | 
			
		||||
	pkgnameCves := map[string]WpCveInfos{}
 | 
			
		||||
	if err = json.Unmarshal([]byte(body), &pkgnameCves); err != nil {
 | 
			
		||||
		return nil, xerrors.Errorf("Failed to unmarshal %s. err: %w", body, err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v := range pkgnameCves {
 | 
			
		||||
		vs := extractToVulnInfos(pkgName, v.Vulnerabilities)
 | 
			
		||||
		vinfos = append(vinfos, vs...)
 | 
			
		||||
	}
 | 
			
		||||
	return vinfos, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnInfo) {
 | 
			
		||||
	for _, vulnerability := range cves {
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
 | 
			
		||||
		if len(vulnerability.References.Cve) == 0 {
 | 
			
		||||
			cveIDs = append(cveIDs, fmt.Sprintf("WPVDBID-%s", vulnerability.ID))
 | 
			
		||||
		}
 | 
			
		||||
		for _, cveNumber := range vulnerability.References.Cve {
 | 
			
		||||
			cveIDs = append(cveIDs, "CVE-"+cveNumber)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var refs []models.Reference
 | 
			
		||||
		for _, url := range vulnerability.References.URL {
 | 
			
		||||
			refs = append(refs, models.Reference{
 | 
			
		||||
				Link: url,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, cveID := range cveIDs {
 | 
			
		||||
			vinfos = append(vinfos, models.VulnInfo{
 | 
			
		||||
				CveID: cveID,
 | 
			
		||||
				CveContents: models.NewCveContents(
 | 
			
		||||
					models.CveContent{
 | 
			
		||||
						Type:         models.WpScan,
 | 
			
		||||
						CveID:        cveID,
 | 
			
		||||
						Title:        vulnerability.Title,
 | 
			
		||||
						References:   refs,
 | 
			
		||||
						Published:    vulnerability.CreatedAt,
 | 
			
		||||
						LastModified: vulnerability.UpdatedAt,
 | 
			
		||||
					},
 | 
			
		||||
				),
 | 
			
		||||
				VulnType: vulnerability.VulnType,
 | 
			
		||||
				Confidences: []models.Confidence{
 | 
			
		||||
					models.WpScanMatch,
 | 
			
		||||
				},
 | 
			
		||||
				WpPackageFixStats: []models.WpPackageFixStatus{{
 | 
			
		||||
					Name:    pkgName,
 | 
			
		||||
					FixedIn: vulnerability.FixedIn,
 | 
			
		||||
				}},
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func httpRequest(url, token string) (string, error) {
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		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 "", errof.New(errof.ErrFailedToAccessWpScan,
 | 
			
		||||
			fmt.Sprintf("Failed to access to wpscan.com. err: %s", err))
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	if resp.StatusCode == 200 {
 | 
			
		||||
		return string(body), nil
 | 
			
		||||
	} else if resp.StatusCode == 404 {
 | 
			
		||||
		// This package is not in wpscan
 | 
			
		||||
		return "", nil
 | 
			
		||||
	} 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
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func removeInactives(pkgs models.WordPressPackages) (removed models.WordPressPackages) {
 | 
			
		||||
	for _, p := range pkgs {
 | 
			
		||||
		if p.Status == "inactive" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		removed = append(removed, p)
 | 
			
		||||
	}
 | 
			
		||||
	return removed
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								detector/wordpress_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,81 @@
 | 
			
		||||
package detector
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestRemoveInactive(t *testing.T) {
 | 
			
		||||
	var tests = []struct {
 | 
			
		||||
		in       models.WordPressPackages
 | 
			
		||||
		expected models.WordPressPackages
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			in: models.WordPressPackages{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "akismet",
 | 
			
		||||
					Status:  "inactive",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: nil,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: models.WordPressPackages{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "akismet",
 | 
			
		||||
					Status:  "inactive",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "BackWPup",
 | 
			
		||||
					Status:  "inactive",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: nil,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			in: models.WordPressPackages{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "akismet",
 | 
			
		||||
					Status:  "active",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "BackWPup",
 | 
			
		||||
					Status:  "inactive",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expected: models.WordPressPackages{
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "akismet",
 | 
			
		||||
					Status:  "active",
 | 
			
		||||
					Update:  "",
 | 
			
		||||
					Version: "",
 | 
			
		||||
					Type:    "",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i, tt := range tests {
 | 
			
		||||
		actual := removeInactives(tt.in)
 | 
			
		||||
		if !reflect.DeepEqual(actual, tt.expected) {
 | 
			
		||||
			t.Errorf("[%d] WordPressPackages error ", i)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -16,6 +16,12 @@ func (e Error) Error() string {
 | 
			
		||||
var (
 | 
			
		||||
	// ErrFailedToAccessGithubAPI is error of github alert's api access
 | 
			
		||||
	ErrFailedToAccessGithubAPI ErrorCode = "ErrFailedToAccessGithubAPI"
 | 
			
		||||
 | 
			
		||||
	// 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,117 +0,0 @@
 | 
			
		||||
package exploit
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/mozqnet/go-exploitdb/db"
 | 
			
		||||
	exploitmodels "github.com/mozqnet/go-exploitdb/models"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillWithExploit fills exploit information that has in Exploit
 | 
			
		||||
func FillWithExploit(driver db.DB, r *models.ScanResult) (nExploitCve int, err error) {
 | 
			
		||||
	if cnf.Conf.Exploit.IsFetchViaHTTP() {
 | 
			
		||||
		var cveIDs []string
 | 
			
		||||
		for cveID := range r.ScannedCves {
 | 
			
		||||
			cveIDs = append(cveIDs, cveID)
 | 
			
		||||
		}
 | 
			
		||||
		prefix, _ := util.URLPathJoin(cnf.Conf.Exploit.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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.Conf.Exploit.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.Conf.Exploit.URL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return xerrors.Errorf("Failed to connect to exploit server. url: %s, errs: %w", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfExploitFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func CheckIfExploitFetched(driver db.DB, osFamily string) (fetched bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfExploitFresh checks if oval entries are fresh enough
 | 
			
		||||
func CheckIfExploitFresh(driver db.DB, osFamily string) (ok bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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: %w", 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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										134
									
								
								github/github.go
									
									
									
									
									
								
							
							
						
						@@ -1,134 +0,0 @@
 | 
			
		||||
package github
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/errof"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/future-architect/vuls/util"
 | 
			
		||||
	"github.com/k0kubun/pp"
 | 
			
		||||
	"golang.org/x/oauth2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillGitHubSecurityAlerts access to owner/repo on GitHub and fetch scurity 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/
 | 
			
		||||
func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
 | 
			
		||||
	src := oauth2.StaticTokenSource(
 | 
			
		||||
		&oauth2.Token{AccessToken: token},
 | 
			
		||||
	)
 | 
			
		||||
	httpClient := oauth2.NewClient(context.Background(), src)
 | 
			
		||||
 | 
			
		||||
	// TODO Use `https://github.com/shurcooL/githubv4` if the tool supports vulnerabilityAlerts Endpoint
 | 
			
		||||
	const jsonfmt = `{"query":
 | 
			
		||||
	"query { repository(owner:\"%s\", name:\"%s\") { url, vulnerabilityAlerts(first: %d, %s) { pageInfo{ endCursor, hasNextPage, startCursor}, edges { node { id, externalIdentifier, externalReference, fixedIn, packageName,  dismissReason, dismissedAt } } } } }"}`
 | 
			
		||||
	after := ""
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		jsonStr := fmt.Sprintf(jsonfmt, owner, repo, 100, after)
 | 
			
		||||
		req, err := http.NewRequest("POST",
 | 
			
		||||
			"https://api.github.com/graphql",
 | 
			
		||||
			bytes.NewBuffer([]byte(jsonStr)),
 | 
			
		||||
		)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// https://developer.github.com/v4/previews/#repository-vulnerability-alerts
 | 
			
		||||
		// To toggle this preview and access data, need to provide a custom media type in the Accept header:
 | 
			
		||||
		// MEMO: I tried to get the affected version via GitHub API. Bit it seems difficult to determin the affected version if there are multiple dependency files such as package.json.
 | 
			
		||||
		// TODO remove this header if it is no longer preview status in the future.
 | 
			
		||||
		req.Header.Set("Accept", "application/vnd.github.vixen-preview+json")
 | 
			
		||||
		req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
 | 
			
		||||
		resp, err := httpClient.Do(req)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
		alerts := SecurityAlerts{}
 | 
			
		||||
		if json.NewDecoder(resp.Body).Decode(&alerts); err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		util.Log.Debugf("%s", pp.Sprint(alerts))
 | 
			
		||||
		if alerts.Data.Repository.URL == "" {
 | 
			
		||||
			return 0, errof.New(
 | 
			
		||||
				errof.ErrFailedToAccessGithubAPI,
 | 
			
		||||
				fmt.Sprintf("Failed to access to GitHub API. Response: %#v", alerts),
 | 
			
		||||
			)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
 | 
			
		||||
			if config.Conf.IgnoreGitHubDismissed && v.Node.DismissReason != "" {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			pkgName := fmt.Sprintf("%s %s",
 | 
			
		||||
				alerts.Data.Repository.URL, v.Node.PackageName)
 | 
			
		||||
 | 
			
		||||
			m := models.GitHubSecurityAlert{
 | 
			
		||||
				PackageName:   pkgName,
 | 
			
		||||
				FixedIn:       v.Node.FixedIn,
 | 
			
		||||
				AffectedRange: v.Node.AffectedRange,
 | 
			
		||||
				Dismissed:     len(v.Node.DismissReason) != 0,
 | 
			
		||||
				DismissedAt:   v.Node.DismissedAt,
 | 
			
		||||
				DismissReason: v.Node.DismissReason,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			cveID := v.Node.ExternalIdentifier
 | 
			
		||||
 | 
			
		||||
			if val, ok := r.ScannedCves[cveID]; ok {
 | 
			
		||||
				val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m)
 | 
			
		||||
				r.ScannedCves[cveID] = val
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			} else {
 | 
			
		||||
				v := models.VulnInfo{
 | 
			
		||||
					CveID:                cveID,
 | 
			
		||||
					Confidences:          models.Confidences{models.GitHubMatch},
 | 
			
		||||
					GitHubSecurityAlerts: models.GitHubSecurityAlerts{m},
 | 
			
		||||
				}
 | 
			
		||||
				r.ScannedCves[cveID] = v
 | 
			
		||||
				nCVEs++
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		after = fmt.Sprintf(`after: \"%s\"`, alerts.Data.Repository.VulnerabilityAlerts.PageInfo.EndCursor)
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//SecurityAlerts has detected CVE-IDs, PackageNames, Refs
 | 
			
		||||
type SecurityAlerts struct {
 | 
			
		||||
	Data struct {
 | 
			
		||||
		Repository struct {
 | 
			
		||||
			URL                 string `json:"url,omitempty"`
 | 
			
		||||
			VulnerabilityAlerts struct {
 | 
			
		||||
				PageInfo struct {
 | 
			
		||||
					EndCursor   string `json:"endCursor,omitempty"`
 | 
			
		||||
					HasNextPage bool   `json:"hasNextPage,omitempty"`
 | 
			
		||||
					StartCursor string `json:"startCursor,omitempty"`
 | 
			
		||||
				} `json:"pageInfo,omitempty"`
 | 
			
		||||
				Edges []struct {
 | 
			
		||||
					Node struct {
 | 
			
		||||
						ID                 string    `json:"id,omitempty"`
 | 
			
		||||
						ExternalIdentifier string    `json:"externalIdentifier,omitempty"`
 | 
			
		||||
						ExternalReference  string    `json:"externalReference,omitempty"`
 | 
			
		||||
						FixedIn            string    `json:"fixedIn,omitempty"`
 | 
			
		||||
						AffectedRange      string    `json:"affectedRange,omitempty"`
 | 
			
		||||
						PackageName        string    `json:"packageName,omitempty"`
 | 
			
		||||
						DismissReason      string    `json:"dismissReason,omitempty"`
 | 
			
		||||
						DismissedAt        time.Time `json:"dismissedAt,omitempty"`
 | 
			
		||||
					} `json:"node,omitempty"`
 | 
			
		||||
				} `json:"edges,omitempty"`
 | 
			
		||||
			} `json:"vulnerabilityAlerts,omitempty"`
 | 
			
		||||
		} `json:"repository,omitempty"`
 | 
			
		||||
	} `json:"data,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						@@ -1,56 +1,60 @@
 | 
			
		||||
module github.com/future-architect/vuls
 | 
			
		||||
 | 
			
		||||
go 1.13
 | 
			
		||||
 | 
			
		||||
replace (
 | 
			
		||||
	github.com/genuinetools/reg => github.com/tomoyamachi/reg v0.16.1-0.20190706172545-2a2250fd7c00
 | 
			
		||||
	gopkg.in/mattn/go-colorable.v0 => github.com/mattn/go-colorable v0.1.0
 | 
			
		||||
	gopkg.in/mattn/go-isatty.v0 => github.com/mattn/go-isatty v0.0.6
 | 
			
		||||
)
 | 
			
		||||
go 1.16
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go v33.2.0+incompatible
 | 
			
		||||
	github.com/Azure/go-autorest/autorest v0.9.1 // indirect
 | 
			
		||||
	github.com/Azure/go-autorest/autorest/to v0.3.0 // indirect
 | 
			
		||||
	github.com/Azure/azure-sdk-for-go v50.2.0+incompatible
 | 
			
		||||
	github.com/BurntSushi/toml v0.3.1
 | 
			
		||||
	github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91
 | 
			
		||||
	github.com/aquasecurity/fanal v0.0.0-20200124194549-91468b8e0460
 | 
			
		||||
	github.com/aquasecurity/go-dep-parser v0.0.0-20190819075924-ea223f0ef24b
 | 
			
		||||
	github.com/aquasecurity/trivy v0.1.6
 | 
			
		||||
	github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
 | 
			
		||||
	github.com/aws/aws-sdk-go v1.25.31
 | 
			
		||||
	github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd
 | 
			
		||||
	github.com/aquasecurity/trivy v0.16.0
 | 
			
		||||
	github.com/aquasecurity/trivy-db v0.0.0-20210121143430-2a5c54036a86
 | 
			
		||||
	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/cenkalti/backoff v2.2.1+incompatible
 | 
			
		||||
	github.com/dnaeon/go-vcr v1.0.1 // indirect
 | 
			
		||||
	github.com/elazarl/goproxy v0.0.0-20190911111923-ecfe977594f1 // indirect
 | 
			
		||||
	github.com/google/subcommands v1.0.1
 | 
			
		||||
	github.com/gosuri/uitable v0.0.3
 | 
			
		||||
	github.com/hashicorp/go-version v1.2.0
 | 
			
		||||
	github.com/hashicorp/uuid v0.0.0-20160311170451-ebb0a03e909c
 | 
			
		||||
	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/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/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
 | 
			
		||||
	github.com/jroimartin/gocui v0.4.0
 | 
			
		||||
	github.com/jesseduffield/gocui v0.3.0
 | 
			
		||||
	github.com/k0kubun/pp v3.0.1+incompatible
 | 
			
		||||
	github.com/knqyf263/go-cpe v0.0.0-20180327054844-659663f6eca2
 | 
			
		||||
	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/go-version v1.1.1
 | 
			
		||||
	github.com/knqyf263/gost v0.1.2
 | 
			
		||||
	github.com/kotakanbe/go-cve-dictionary v0.4.1
 | 
			
		||||
	github.com/knqyf263/gost v0.1.10
 | 
			
		||||
	github.com/kotakanbe/go-cve-dictionary v0.5.10
 | 
			
		||||
	github.com/kotakanbe/go-pingscanner v0.1.0
 | 
			
		||||
	github.com/kotakanbe/goval-dictionary v0.2.3
 | 
			
		||||
	github.com/kotakanbe/goval-dictionary v0.3.3
 | 
			
		||||
	github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
 | 
			
		||||
	github.com/lib/pq v1.10.0 // indirect
 | 
			
		||||
	github.com/magiconair/properties v1.8.4 // indirect
 | 
			
		||||
	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
 | 
			
		||||
	github.com/mitchellh/go-homedir v1.1.0
 | 
			
		||||
	github.com/mozqnet/go-exploitdb v0.0.0-20190911093644-f647f17ea8ca
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.4.1 // indirect
 | 
			
		||||
	github.com/nlopes/slack v0.6.0
 | 
			
		||||
	github.com/nsf/termbox-go v0.0.0-20190817171036-93860e161317 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.2-0.20190607075207-195002e6e56a
 | 
			
		||||
	github.com/parnurzeal/gorequest v0.2.15
 | 
			
		||||
	github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
 | 
			
		||||
	github.com/olekukonko/tablewriter v0.0.5
 | 
			
		||||
	github.com/parnurzeal/gorequest v0.2.16
 | 
			
		||||
	github.com/pelletier/go-toml v1.8.1 // indirect
 | 
			
		||||
	github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
 | 
			
		||||
	github.com/satori/go.uuid v1.2.0 // indirect
 | 
			
		||||
	github.com/sirupsen/logrus v1.4.2
 | 
			
		||||
	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
 | 
			
		||||
	github.com/sirupsen/logrus v1.7.0
 | 
			
		||||
	github.com/spf13/afero v1.6.0
 | 
			
		||||
	github.com/spf13/cast v1.3.1 // indirect
 | 
			
		||||
	github.com/spf13/cobra v1.1.3
 | 
			
		||||
	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 | 
			
		||||
	github.com/takuzoo3868/go-msfdb v0.1.5
 | 
			
		||||
	github.com/vulsio/go-exploitdb v0.1.7
 | 
			
		||||
	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
 | 
			
		||||
	golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
 | 
			
		||||
	golang.org/x/net v0.0.0-20210323141857-08027d57d8cf // indirect
 | 
			
		||||
	golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196
 | 
			
		||||
	golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
 | 
			
		||||
	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
 | 
			
		||||
	gopkg.in/ini.v1 v1.62.0 // indirect
 | 
			
		||||
	k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								gost/base.go
									
									
									
									
									
								
							
							
						
						@@ -1,51 +0,0 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	cnf "github.com/future-architect/vuls/config"
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	"github.com/parnurzeal/gorequest"
 | 
			
		||||
	"golang.org/x/xerrors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 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{}.fillFixed(driver, r)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckHTTPHealth do health check
 | 
			
		||||
func (b Base) CheckHTTPHealth() error {
 | 
			
		||||
	if !cnf.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s/health", cnf.Conf.Gost.URL)
 | 
			
		||||
	var errs []error
 | 
			
		||||
	var resp *http.Response
 | 
			
		||||
	resp, _, errs = gorequest.New().Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End()
 | 
			
		||||
	//  resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End()
 | 
			
		||||
	if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
 | 
			
		||||
		return xerrors.Errorf("Failed to connect to gost server. url: %s, errs: %w", url, errs)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfGostFetched checks if oval entries are in DB by family, release.
 | 
			
		||||
func (b Base) CheckIfGostFetched(driver db.DB, osFamily string) (fetched bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CheckIfGostFresh checks if oval entries are fresh enough
 | 
			
		||||
func (b Base) CheckIfGostFresh(driver db.DB, osFamily string) (ok bool, err error) {
 | 
			
		||||
	//TODO
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"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/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -21,8 +23,23 @@ type packCves struct {
 | 
			
		||||
	cves      []models.CveContent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (deb Debian) supported(major string) bool {
 | 
			
		||||
	_, ok := map[string]string{
 | 
			
		||||
		"8":  "jessie",
 | 
			
		||||
		"9":  "stretch",
 | 
			
		||||
		"10": "buster",
 | 
			
		||||
	}[major]
 | 
			
		||||
	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) {
 | 
			
		||||
func (deb Debian) DetectUnfixed(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if !deb.supported(major(r.Release)) {
 | 
			
		||||
		// only logging
 | 
			
		||||
		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.
 | 
			
		||||
	if r.Container.ContainerID == "" {
 | 
			
		||||
@@ -37,9 +54,17 @@ 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 != constant.Raspbian {
 | 
			
		||||
		scanResult = *r
 | 
			
		||||
	} else {
 | 
			
		||||
		scanResult = r.RemoveRaspbianPackFromResult()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	packCvesList := []packCves{}
 | 
			
		||||
	if config.Conf.Gost.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(config.Conf.Gost.URL, "debian", major(r.Release), "pkgs")
 | 
			
		||||
	if deb.DBDriver.Cnf.IsFetchViaHTTP() {
 | 
			
		||||
		url, _ := util.URLPathJoin(deb.DBDriver.Cnf.GetURL(), "debian", major(scanResult.Release), "pkgs")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, url)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
@@ -61,11 +86,11 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
		if deb.DBDriver.DB == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(r.Release), pack.Name)
 | 
			
		||||
		for _, pack := range scanResult.Packages {
 | 
			
		||||
			cveDebs := deb.DBDriver.DB.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
@@ -78,8 +103,8 @@ func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCV
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// SrcPack
 | 
			
		||||
		for _, pack := range r.SrcPackages {
 | 
			
		||||
			cveDebs := driver.GetUnfixedCvesDebian(major(r.Release), pack.Name)
 | 
			
		||||
		for _, pack := range scanResult.SrcPackages {
 | 
			
		||||
			cveDebs := deb.DBDriver.DB.GetUnfixedCvesDebian(major(scanResult.Release), pack.Name)
 | 
			
		||||
			cves := []models.CveContent{}
 | 
			
		||||
			for _, cveDeb := range cveDebs {
 | 
			
		||||
				cves = append(cves, *deb.ConvertToModel(&cveDeb))
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										61
									
								
								gost/debian_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,61 @@
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestDebian_Supported(t *testing.T) {
 | 
			
		||||
	type fields struct {
 | 
			
		||||
		Base Base
 | 
			
		||||
	}
 | 
			
		||||
	type args struct {
 | 
			
		||||
		major string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name: "8 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				major: "8",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "9 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				major: "9",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "10 is supported",
 | 
			
		||||
			args: args{
 | 
			
		||||
				major: "10",
 | 
			
		||||
			},
 | 
			
		||||
			want: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "11 is not supported yet",
 | 
			
		||||
			args: args{
 | 
			
		||||
				major: "11",
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "empty string is not supported yet",
 | 
			
		||||
			args: args{
 | 
			
		||||
				major: "",
 | 
			
		||||
			},
 | 
			
		||||
			want: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			deb := Debian{}
 | 
			
		||||
			if got := deb.supported(tt.args.major); got != tt.want {
 | 
			
		||||
				t.Errorf("Debian.Supported() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										85
									
								
								gost/gost.go
									
									
									
									
									
								
							
							
						
						@@ -1,33 +1,86 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	DetectUnfixed(*models.ScanResult, bool) (int, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	//TODO implement
 | 
			
		||||
	// CheckHTTPHealth() error
 | 
			
		||||
	// CheckIfGostFetched checks if Gost entries are fetched
 | 
			
		||||
	// CheckIfGostFetched(db.DB, string, string) (bool, error)
 | 
			
		||||
	// CheckIfGostFresh(db.DB, string, string) (bool, error)
 | 
			
		||||
// Base is a base struct
 | 
			
		||||
type Base struct {
 | 
			
		||||
	DBDriver DBDriver
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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:
 | 
			
		||||
		return Debian{}
 | 
			
		||||
	case cnf.Windows:
 | 
			
		||||
		return Microsoft{}
 | 
			
		||||
	case constant.RedHat, constant.CentOS:
 | 
			
		||||
		return RedHat{Base{DBDriver: driver}}, nil
 | 
			
		||||
	case constant.Debian, constant.Raspbian:
 | 
			
		||||
		return Debian{Base{DBDriver: driver}}, nil
 | 
			
		||||
	case constant.Windows:
 | 
			
		||||
		return Microsoft{Base{DBDriver: driver}}, nil
 | 
			
		||||
	default:
 | 
			
		||||
		return Pseudo{}
 | 
			
		||||
		return Pseudo{}, 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,10 +1,11 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	gostmodels "github.com/knqyf263/gost/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -14,31 +15,32 @@ type Microsoft struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
func (ms Microsoft) DetectUnfixed(r *models.ScanResult, _ bool) (nCVEs int, err error) {
 | 
			
		||||
	if ms.DBDriver.DB == nil {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
	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
 | 
			
		||||
		}
 | 
			
		||||
		cveCont := ms.ConvertToModel(&msCve)
 | 
			
		||||
		cveCont, mitigations := ms.ConvertToModel(&msCve)
 | 
			
		||||
		v, _ := r.ScannedCves[cveID]
 | 
			
		||||
		if v.CveContents == nil {
 | 
			
		||||
			v.CveContents = models.CveContents{}
 | 
			
		||||
		}
 | 
			
		||||
		v.CveContents[models.Microsoft] = *cveCont
 | 
			
		||||
		v.Mitigations = append(v.Mitigations, mitigations...)
 | 
			
		||||
		r.ScannedCves[cveID] = v
 | 
			
		||||
	}
 | 
			
		||||
	return len(cveIDs), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveContent {
 | 
			
		||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) {
 | 
			
		||||
	v3score := 0.0
 | 
			
		||||
	var v3Vector string
 | 
			
		||||
	for _, scoreSet := range cve.ScoreSets {
 | 
			
		||||
@@ -67,12 +69,11 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveCont
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	}
 | 
			
		||||
	var kbids []string
 | 
			
		||||
	kbids := []string{}
 | 
			
		||||
	for _, kbid := range cve.KBIDs {
 | 
			
		||||
		kbids = append(kbids, kbid.KBID)
 | 
			
		||||
	}
 | 
			
		||||
@@ -80,6 +81,23 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveCont
 | 
			
		||||
		option["kbids"] = strings.Join(kbids, ",")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
 | 
			
		||||
	mitigations := []models.Mitigation{}
 | 
			
		||||
	if cve.Mitigation != "" {
 | 
			
		||||
		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{
 | 
			
		||||
		Type:          models.Microsoft,
 | 
			
		||||
		CveID:         cve.CveID,
 | 
			
		||||
@@ -90,10 +108,9 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveCont
 | 
			
		||||
		Cvss3Severity: v3Severity,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		CweIDs:        cwe,
 | 
			
		||||
		Mitigation:    cve.Mitigation,
 | 
			
		||||
		Published:     cve.PublishDate,
 | 
			
		||||
		LastModified:  cve.LastUpdateDate,
 | 
			
		||||
		SourceLink:    "https://portal.msrc.microsoft.com/ja-jp/security-guidance/advisory/" + cve.CveID,
 | 
			
		||||
		SourceLink:    vendorURL,
 | 
			
		||||
		Optional:      option,
 | 
			
		||||
	}
 | 
			
		||||
	}, mitigations
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
	"github.com/knqyf263/gost/db"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Pseudo is Gost client except for RedHat family and Debian
 | 
			
		||||
@@ -12,10 +12,6 @@ type Pseudo struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DetectUnfixed fills cve information that has in Gost
 | 
			
		||||
func (pse Pseudo) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (int, error) {
 | 
			
		||||
func (pse Pseudo) DetectUnfixed(r *models.ScanResult, _ bool) (int, error) {
 | 
			
		||||
	return 0, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func major(osVer string) (majorVersion string) {
 | 
			
		||||
	return strings.Split(osVer, ".")[0]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										220
									
								
								gost/redhat.go
									
									
									
									
									
								
							
							
						
						@@ -1,3 +1,5 @@
 | 
			
		||||
// +build !scanner
 | 
			
		||||
 | 
			
		||||
package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@@ -8,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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -18,12 +19,44 @@ type RedHat struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.fillUnfixed(driver, r, ignoreWillNotFix)
 | 
			
		||||
func (red RedHat) DetectUnfixed(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
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := map[string]gostmodels.RedhatCVE{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &cves); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if red.DBDriver.DB == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := red.DBDriver.DB.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
	var cveIDs []string
 | 
			
		||||
func (red RedHat) fillCvesWithRedHatAPI(r *models.ScanResult) error {
 | 
			
		||||
	cveIDs := []string{}
 | 
			
		||||
	for cveID, vuln := range r.ScannedCves {
 | 
			
		||||
		if _, ok := vuln.CveContents[models.RedHatAPI]; ok {
 | 
			
		||||
			continue
 | 
			
		||||
@@ -31,9 +64,8 @@ func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
		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
 | 
			
		||||
@@ -46,129 +78,68 @@ func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
 | 
			
		||||
			if redCve.ID == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			cveCont := red.ConvertToModel(&redCve)
 | 
			
		||||
			v, ok := r.ScannedCves[res.request.cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cveCont.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
					Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[res.request.cveID] = v
 | 
			
		||||
			red.setFixedCveToScanResult(&redCve, r)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
		if red.DBDriver.DB == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for cveID, redCve := range driver.GetRedhatMulti(cveIDs) {
 | 
			
		||||
		for _, redCve := range red.DBDriver.DB.GetRedhatMulti(cveIDs) {
 | 
			
		||||
			if len(redCve.Name) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			cveCont := red.ConvertToModel(&redCve)
 | 
			
		||||
			v, ok := r.ScannedCves[cveID]
 | 
			
		||||
			if ok {
 | 
			
		||||
				if v.CveContents == nil {
 | 
			
		||||
					v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
				} else {
 | 
			
		||||
					v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				v = models.VulnInfo{
 | 
			
		||||
					CveID:       cveCont.CveID,
 | 
			
		||||
					CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
					Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			r.ScannedCves[cveID] = v
 | 
			
		||||
			red.setFixedCveToScanResult(&redCve, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) fillUnfixed(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")
 | 
			
		||||
		responses, err := getAllUnfixedCvesViaHTTP(r, prefix)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, res := range responses {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := map[string]gostmodels.RedhatCVE{}
 | 
			
		||||
			if err := json.Unmarshal([]byte(res.json), &cves); err != nil {
 | 
			
		||||
				return 0, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				cveCont := red.ConvertToModel(&cve)
 | 
			
		||||
				v, ok := r.ScannedCves[cve.Name]
 | 
			
		||||
				if ok {
 | 
			
		||||
					if v.CveContents == nil {
 | 
			
		||||
						v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
					} else {
 | 
			
		||||
						v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					v = models.VulnInfo{
 | 
			
		||||
						CveID:       cveCont.CveID,
 | 
			
		||||
						CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
						Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
					}
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
				pkgStats := red.mergePackageStates(v,
 | 
			
		||||
					cve.PackageState, r.Packages, r.Release)
 | 
			
		||||
				if 0 < len(pkgStats) {
 | 
			
		||||
					v.AffectedPackages = pkgStats
 | 
			
		||||
					r.ScannedCves[cve.Name] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
func (red RedHat) setFixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) {
 | 
			
		||||
	cveCont, mitigations := red.ConvertToModel(cve)
 | 
			
		||||
	v, ok := r.ScannedCves[cveCont.CveID]
 | 
			
		||||
	if ok {
 | 
			
		||||
		if v.CveContents == nil {
 | 
			
		||||
			v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
		} else {
 | 
			
		||||
			v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if driver == nil {
 | 
			
		||||
			return 0, nil
 | 
			
		||||
		}
 | 
			
		||||
		for _, pack := range r.Packages {
 | 
			
		||||
			// CVE-ID: RedhatCVE
 | 
			
		||||
			cves := map[string]gostmodels.RedhatCVE{}
 | 
			
		||||
			cves = driver.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
 | 
			
		||||
			for _, cve := range cves {
 | 
			
		||||
				cveCont := red.ConvertToModel(&cve)
 | 
			
		||||
				v, ok := r.ScannedCves[cve.Name]
 | 
			
		||||
				if ok {
 | 
			
		||||
					if v.CveContents == nil {
 | 
			
		||||
						v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
					} else {
 | 
			
		||||
						v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					v = models.VulnInfo{
 | 
			
		||||
						CveID:       cveCont.CveID,
 | 
			
		||||
						CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
						Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
					}
 | 
			
		||||
					nCVEs++
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				pkgStats := red.mergePackageStates(v,
 | 
			
		||||
					cve.PackageState, r.Packages, r.Release)
 | 
			
		||||
				if 0 < len(pkgStats) {
 | 
			
		||||
					v.AffectedPackages = pkgStats
 | 
			
		||||
					r.ScannedCves[cve.Name] = v
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		v = models.VulnInfo{
 | 
			
		||||
			CveID:       cveCont.CveID,
 | 
			
		||||
			CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
			Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nCVEs, nil
 | 
			
		||||
	v.Mitigations = append(v.Mitigations, mitigations...)
 | 
			
		||||
	r.ScannedCves[cveCont.CveID] = v
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) setUnfixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) (newly bool) {
 | 
			
		||||
	cveCont, mitigations := red.ConvertToModel(cve)
 | 
			
		||||
	v, ok := r.ScannedCves[cve.Name]
 | 
			
		||||
	if ok {
 | 
			
		||||
		if v.CveContents == nil {
 | 
			
		||||
			v.CveContents = models.NewCveContents(*cveCont)
 | 
			
		||||
		} else {
 | 
			
		||||
			v.CveContents[models.RedHatAPI] = *cveCont
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		v = models.VulnInfo{
 | 
			
		||||
			CveID:       cveCont.CveID,
 | 
			
		||||
			CveContents: models.NewCveContents(*cveCont),
 | 
			
		||||
			Confidences: models.Confidences{models.RedHatAPIMatch},
 | 
			
		||||
		}
 | 
			
		||||
		newly = true
 | 
			
		||||
	}
 | 
			
		||||
	v.Mitigations = append(v.Mitigations, mitigations...)
 | 
			
		||||
	pkgStats := red.mergePackageStates(v,
 | 
			
		||||
		cve.PackageState, r.Packages, r.Release)
 | 
			
		||||
	if 0 < len(pkgStats) {
 | 
			
		||||
		v.AffectedPackages = pkgStats
 | 
			
		||||
		r.ScannedCves[cve.Name] = v
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageFixStatuses) {
 | 
			
		||||
@@ -219,7 +190,7 @@ func (red RedHat) parseCwe(str string) (cwes []string) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ConvertToModel converts gost model to vuls model
 | 
			
		||||
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
 | 
			
		||||
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) (*models.CveContent, []models.Mitigation) {
 | 
			
		||||
	cwes := red.parseCwe(cve.Cwe)
 | 
			
		||||
 | 
			
		||||
	details := []string{}
 | 
			
		||||
@@ -245,11 +216,23 @@ func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
 | 
			
		||||
		v3severity = cve.ThreatSeverity
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var refs []models.Reference
 | 
			
		||||
	refs := []models.Reference{}
 | 
			
		||||
	for _, r := range cve.References {
 | 
			
		||||
		refs = append(refs, models.Reference{Link: r.Reference})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vendorURL := "https://access.redhat.com/security/cve/" + cve.Name
 | 
			
		||||
	mitigations := []models.Mitigation{}
 | 
			
		||||
	if cve.Mitigation != "" {
 | 
			
		||||
		mitigations = []models.Mitigation{
 | 
			
		||||
			{
 | 
			
		||||
				CveContentType: models.RedHatAPI,
 | 
			
		||||
				Mitigation:     cve.Mitigation,
 | 
			
		||||
				URL:            vendorURL,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &models.CveContent{
 | 
			
		||||
		Type:          models.RedHatAPI,
 | 
			
		||||
		CveID:         cve.Name,
 | 
			
		||||
@@ -263,8 +246,7 @@ func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
 | 
			
		||||
		Cvss3Severity: v3severity,
 | 
			
		||||
		References:    refs,
 | 
			
		||||
		CweIDs:        cwes,
 | 
			
		||||
		Mitigation:    cve.Mitigation,
 | 
			
		||||
		Published:     cve.PublicDate,
 | 
			
		||||
		SourceLink:    "https://access.redhat.com/security/cve/" + cve.Name,
 | 
			
		||||
	}
 | 
			
		||||
		SourceLink:    vendorURL,
 | 
			
		||||
	}, mitigations
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								gost/util.go
									
									
									
									
									
								
							
							
						
						@@ -2,9 +2,11 @@ package gost
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"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"
 | 
			
		||||
@@ -47,7 +49,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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -121,7 +123,7 @@ func getAllUnfixedCvesViaHTTP(r *models.ScanResult, 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)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
@@ -153,18 +155,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: %w", 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 {
 | 
			
		||||
@@ -181,3 +183,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
 | 
			
		||||
		json:    body,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func major(osVer string) (majorVersion string) {
 | 
			
		||||
	return strings.Split(osVer, ".")[0]
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 247 KiB  | 
| 
		 Before Width: | Height: | Size: 97 KiB  | 
| 
		 Before Width: | Height: | Size: 91 KiB  | 
@@ -1,414 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.decision">
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
SUSE: zypper
 | 
			
		||||
FreeBSD: pkg<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="630.0546766682629"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="750.4705298628534"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="234.29467728596296" y="709.1901021013174"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n6:">
 | 
			
		||||
        <node id="n6::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="416.1341210280616" y="745.8561177263174"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n6::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="249.29467728596296" y="745.8561177263174"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="92.54867256637169" y="376.28592169721867"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="27.144753476611868" y="459.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="92.64475347661187" y="545.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n1" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="183.35883739927397" y="2.000003510871693">Amazon
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="right" ratio="0.7796030035582084" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-123.36984126984123" ty="0.0">
 | 
			
		||||
            <y:Point x="443.6849206349206" y="658.0546766682629"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="102.9296875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="77.078125" x="-97.68364242524859" y="5.005267793098369">Alpine Linux
 | 
			
		||||
CentOS
 | 
			
		||||
RHEL
 | 
			
		||||
Ubuntu
 | 
			
		||||
Debian
 | 
			
		||||
Oracle Linux
 | 
			
		||||
Suse<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="59.14459455430983" distanceToCenter="true" position="right" ratio="0.0" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n4" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n7" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n8" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n9" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n3" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n1" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-196.80057112212188" y="20.933597260871807">Raspbian<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="left" ratio="0.6447921222409765" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n10" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="-125.78842258255952" ty="0.0">
 | 
			
		||||
            <y:Point x="161.14475347661187" y="658.0546766682629"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 78 KiB  | 
@@ -1,515 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.17-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="0.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="83.482421875" x="92.2587890625" y="18.93359375">Detect the OS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.decision">
 | 
			
		||||
          <y:Geometry height="40.0" width="80.0" x="403.6849206349206" y="206.44247787610618"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" hasText="false" height="4.0" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="4.0" x="38.0" y="18.0">
 | 
			
		||||
            <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="90.44247787610618" width="268.0" x="309.6849206349206" y="86.0"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="right" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="88.796875" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="170.763671875" x="48.61816406250006" y="0.8228014380530908">Get installed packages
 | 
			
		||||
Alpine Linux: apk
 | 
			
		||||
Debian/Ubuntu: dpkg-query
 | 
			
		||||
Amazon/RHEL/CentOS: rpm
 | 
			
		||||
FreeBSD: pkg
 | 
			
		||||
SUSE: zypper<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="260.83984375" x="3.580078125" y="11.8671875">Check upgradable packages
 | 
			
		||||
Debian/Ubuntu: apt-get upgrade --dry-run<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimit">
 | 
			
		||||
          <y:Geometry height="51.10998735777497" width="137.19216182048035" x="75.40391908975982" y="376.28592169721867"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="131.751953125" x="2.7201043477401754" y="9.422181178887513">foreach 
 | 
			
		||||
upgradable  packages<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="5.551115123125783E-16" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n5">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="10.0" y="459.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="213.619140625" x="27.1904296875" y="11.8671875">Parse changelog and get  CVE IDs
 | 
			
		||||
Debian/Ubuntu: aptitude changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n6">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.loopLimitEnd">
 | 
			
		||||
          <y:Geometry height="50.0" width="137.0" x="75.5" y="545.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.24609375" x="40.876953125" y="15.93359375">end loop<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n7">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="625.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="152.634765625" x="57.6826171875" y="18.93359375">Write results to JSON files<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="0.0" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n8">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="287.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="232.744140625" x="17.6279296875" y="4.80078125">Get CVE IDs by using package manager
 | 
			
		||||
Amazon/RHEL: yum plugin security
 | 
			
		||||
FreeBSD: pkg audit<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n9">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="716.4553275126422"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="42.595703125" x="112.7021484375" y="18.93359375">Report<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n10">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.6849206349206" y="371.39590905499364"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999943" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n11">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="309.68492063492056" y="459.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="205.52734375" x="31.236328125000057" y="18.93359375">Parse changelogs and get CVE IDs <y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.5" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.1619001116071429" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n12">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:GenericNode configuration="com.yworks.flowchart.process">
 | 
			
		||||
          <y:Geometry height="56.0" width="268.0" x="609.3698412698412" y="373.8409153761062"/>
 | 
			
		||||
          <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="293.06640625" x="-12.533203124999886" y="11.8671875">Get all changelogs of updatable packages at once
 | 
			
		||||
Amazon / RHEL: yum changelog<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="2.220446049250313E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
        </y:GenericNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n13" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="116.89483989807195" width="333.6788874841973" x="229.74083438685204" y="675.1748997511062"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="333.6788874841973" x="0.0" y="0.0">Vulnerability Database</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000000" verticalTextPosition="bottom" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 1</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n13:">
 | 
			
		||||
        <node id="n13::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="411.5802781289507" y="711.8409153761062"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="117.970703125" x="9.434370308549205" y="23.548005886535975">CVE DB (NVD / JVN)<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n13::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:GenericNode configuration="com.yworks.flowchart.dataBase">
 | 
			
		||||
              <y:Geometry height="65.22882427307195" width="136.83944374209864" x="244.74083438685204" y="711.8409153761062"/>
 | 
			
		||||
              <y:Fill color="#E8EEF7" color2="#B7C9E3" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.533203125" x="40.653120308549205" y="23.548005886535975">OVAL DB<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="-8.326672684688674E-16" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
            </y:GenericNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n2" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="45.22123893805309" tx="0.0" ty="-20.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n1" target="n3">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="-40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="144.0" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="56.98046875" x="-257.65322875976574" y="2.0000035108718635">Debian
 | 
			
		||||
Ubuntu
 | 
			
		||||
Raspbian<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="1.9999999999998863" distanceToCenter="false" position="left" ratio="0.8652035780364729" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n3" target="n4">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.554993678887485"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n4" target="n5">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="25.554993678887485" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n5" target="n6">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-25.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n6" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="68.5" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n1" target="n8">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="40.0" sy="0.0" tx="0.0" ty="-28.0">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="226.44247787610618"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="46.3984375" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="51.806640625" x="200.87829463898197" y="4.000003510871693">Amazon
 | 
			
		||||
RHEL
 | 
			
		||||
FreeBSD<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="6.283185307179586" distance="6.999999999999886" distanceToCenter="false" position="right" ratio="0.8192728556300707" segment="-1"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n0" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-45.22123893805309"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n7" target="n9">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e9" source="n1" target="n10">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="20.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="46.708984375" x="-53.35447755843876" y="5.000003510871807">CentOS<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.0" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e10" source="n10" target="n11">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="28.0" tx="0.0" ty="-28.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e11" source="n11" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="-24.34091537610618">
 | 
			
		||||
            <y:Point x="743.3698412698412" y="487.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e12" source="n8" target="n12">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e13" source="n12" target="n7">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e14" source="n9" target="n13">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="10.8330078125"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e15" source="n1" target="n7">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
 | 
			
		||||
            <y:Point x="999.0" y="226.44247787610618"/>
 | 
			
		||||
            <y:Point x="999.0" y="570.8409153761062"/>
 | 
			
		||||
            <y:Point x="743.3698412698412" y="570.8409153761062"/>
 | 
			
		||||
          </y:Path>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:EdgeLabel alignment="right" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="32.265625" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="76.8203125" x="422.923942251054" y="13.867191010871807">Alpine Linux
 | 
			
		||||
SUSE<y:LabelModel>
 | 
			
		||||
              <y:SmartEdgeLabelModel autoRotationEnabled="false" defaultAngle="0.0" defaultDistance="10.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartEdgeLabelModelParameter angle="0.0" distance="30.0" distanceToCenter="true" position="right" ratio="0.8856709076027529" segment="0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
            <y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/>
 | 
			
		||||
          </y:EdgeLabel>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 90 KiB  | 
| 
		 Before Width: | Height: | Size: 179 KiB  | 
@@ -1,265 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="478.6165008544913" y="1358.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1043.7241210937468"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="38.623046875" x="17.3134765625" y="15.93359375">Nginx<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="711.9623756408686" y="1287.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="42.7890625" x="15.23046875" y="15.93359375">MySQL<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="101.666015625" width="291.7208747863772" x="602.72693824768" y="1146.2994791666624"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="23" leftF="23.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n3:">
 | 
			
		||||
        <node id="n3::n0">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="640.72693824768" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n1">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="723.4623756408686" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n2">
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="806.1978130340572" y="1182.9654947916624"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n4">
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="821.1978130340572" y="1287.206868489578"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="35.412109375" x="18.9189453125" y="15.93359375">Redis<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n3" target="n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n3" target="n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="n3::n0">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e3" source="n0" target="n3::n1">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e4" source="n0" target="n3::n2">
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e5" source="n3" target="n4">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e6" source="n0" target="n4">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e7" source="n0" target="n1">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e8" source="n0" target="n2">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 14 KiB  | 
@@ -1,194 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
 | 
			
		||||
  <!--Created by yEd 3.14.2-->
 | 
			
		||||
  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
 | 
			
		||||
  <key for="port" id="d1" yfiles.type="portgraphics"/>
 | 
			
		||||
  <key for="port" id="d2" yfiles.type="portgeometry"/>
 | 
			
		||||
  <key for="port" id="d3" yfiles.type="portuserdata"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="node" id="d4"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="node" id="d5"/>
 | 
			
		||||
  <key for="node" id="d6" yfiles.type="nodegraphics"/>
 | 
			
		||||
  <key for="graphml" id="d7" yfiles.type="resources"/>
 | 
			
		||||
  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
 | 
			
		||||
  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
 | 
			
		||||
  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
 | 
			
		||||
  <graph edgedefault="directed" id="G">
 | 
			
		||||
    <data key="d0"/>
 | 
			
		||||
    <node id="n0">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="508.30825042724564" y="1132.4827473958312"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="28.87890625" x="22.185546875" y="15.93359375">Vuls<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n1">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="993.2413736979156"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="23.8046875" x="24.72265625" y="15.93359375">ELB<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n2">
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ShapeNode>
 | 
			
		||||
          <y:Geometry height="50.0" width="73.25" x="749.6541252136229" y="1236.7241210937468"/>
 | 
			
		||||
          <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
          <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="27.0390625" x="23.10546875" y="15.93359375">RDS<y:LabelModel>
 | 
			
		||||
              <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
            </y:LabelModel>
 | 
			
		||||
            <y:ModelParameter>
 | 
			
		||||
              <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
            </y:ModelParameter>
 | 
			
		||||
          </y:NodeLabel>
 | 
			
		||||
          <y:Shape type="roundrectangle"/>
 | 
			
		||||
        </y:ShapeNode>
 | 
			
		||||
      </data>
 | 
			
		||||
    </node>
 | 
			
		||||
    <node id="n3" yfiles.foldertype="group">
 | 
			
		||||
      <data key="d4"/>
 | 
			
		||||
      <data key="d5"/>
 | 
			
		||||
      <data key="d6">
 | 
			
		||||
        <y:ProxyAutoBoundsNode>
 | 
			
		||||
          <y:Realizers active="0">
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="101.666015625" width="291.7208747863772" x="640.4186878204343" y="1095.8167317708312"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="291.7208747863772" x="0.0" y="0.0">Web/App</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="false" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="15" bottomF="15.0" left="15" leftF="15.0" right="15" rightF="15.0" top="15" topF="15.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="23" leftF="23.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
            <y:GroupNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="50.0" x="0.0" y="60.0"/>
 | 
			
		||||
              <y:Fill color="#F5F5F5" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="dashed" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="right" autoSizePolicy="node_width" backgroundColor="#EBEBEB" borderDistance="0.0" fontFamily="Dialog" fontSize="15" fontStyle="plain" hasLineColor="false" height="21.666015625" modelName="internal" modelPosition="t" textColor="#000000" visible="true" width="63.75830078125" x="-6.879150390625" y="0.0">Folder 5</y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
              <y:State closed="true" closedHeight="50.0" closedWidth="50.0" innerGraphDisplayEnabled="false"/>
 | 
			
		||||
              <y:Insets bottom="5" bottomF="5.0" left="5" leftF="5.0" right="5" rightF="5.0" top="5" topF="5.0"/>
 | 
			
		||||
              <y:BorderInsets bottom="0" bottomF="0.0" left="0" leftF="0.0" right="0" rightF="0.0" top="0" topF="0.0"/>
 | 
			
		||||
            </y:GroupNode>
 | 
			
		||||
          </y:Realizers>
 | 
			
		||||
        </y:ProxyAutoBoundsNode>
 | 
			
		||||
      </data>
 | 
			
		||||
      <graph edgedefault="directed" id="n3:">
 | 
			
		||||
        <node id="n3::n0">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="678.4186878204343" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n1">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="761.1541252136229" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
        <node id="n3::n2">
 | 
			
		||||
          <data key="d5"/>
 | 
			
		||||
          <data key="d6">
 | 
			
		||||
            <y:ShapeNode>
 | 
			
		||||
              <y:Geometry height="50.0" width="73.25" x="843.8895626068115" y="1132.4827473958312"/>
 | 
			
		||||
              <y:Fill color="#C0C0C0" transparent="false"/>
 | 
			
		||||
              <y:BorderStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
              <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="custom" textColor="#000000" visible="true" width="31.26953125" x="20.990234375" y="15.93359375">Rails<y:LabelModel>
 | 
			
		||||
                  <y:SmartNodeLabelModel distance="4.0"/>
 | 
			
		||||
                </y:LabelModel>
 | 
			
		||||
                <y:ModelParameter>
 | 
			
		||||
                  <y:SmartNodeLabelModelParameter labelRatioX="0.0" labelRatioY="0.0" nodeRatioX="0.0" nodeRatioY="0.0" offsetX="0.0" offsetY="0.0" upX="0.0" upY="-1.0"/>
 | 
			
		||||
                </y:ModelParameter>
 | 
			
		||||
              </y:NodeLabel>
 | 
			
		||||
              <y:Shape type="roundrectangle"/>
 | 
			
		||||
            </y:ShapeNode>
 | 
			
		||||
          </data>
 | 
			
		||||
        </node>
 | 
			
		||||
      </graph>
 | 
			
		||||
    </node>
 | 
			
		||||
    <edge id="e0" source="n3" target="n1">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e1" source="n3" target="n2">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="none"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
    <edge id="e2" source="n0" target="n3::n0">
 | 
			
		||||
      <data key="d9"/>
 | 
			
		||||
      <data key="d10">
 | 
			
		||||
        <y:PolyLineEdge>
 | 
			
		||||
          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
 | 
			
		||||
          <y:LineStyle color="#000000" type="line" width="1.0"/>
 | 
			
		||||
          <y:Arrows source="none" target="standard"/>
 | 
			
		||||
          <y:BendStyle smoothed="false"/>
 | 
			
		||||
        </y:PolyLineEdge>
 | 
			
		||||
      </data>
 | 
			
		||||
    </edge>
 | 
			
		||||
  </graph>
 | 
			
		||||
  <data key="d7">
 | 
			
		||||
    <y:Resources/>
 | 
			
		||||
  </data>
 | 
			
		||||
</graphml>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.6 KiB  | 
							
								
								
									
										6534
									
								
								integration/data/amazon_2.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										5634
									
								
								integration/data/centos_7.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										5631
									
								
								integration/data/debian_10.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										131
									
								
								integration/data/rails.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,131 @@
 | 
			
		||||
{
 | 
			
		||||
    "jsonVersion": 4,
 | 
			
		||||
    "lang": "",
 | 
			
		||||
    "serverUUID": "",
 | 
			
		||||
    "serverName": "rails",
 | 
			
		||||
    "family": "pseudo",
 | 
			
		||||
    "release": "",
 | 
			
		||||
    "container": {
 | 
			
		||||
        "containerID": "",
 | 
			
		||||
        "name": "",
 | 
			
		||||
        "image": "",
 | 
			
		||||
        "type": "",
 | 
			
		||||
        "uuid": ""
 | 
			
		||||
    },
 | 
			
		||||
    "platform": {
 | 
			
		||||
        "name": "other",
 | 
			
		||||
        "instanceID": ""
 | 
			
		||||
    },
 | 
			
		||||
    "scannedAt": "2021-03-31T12:22:26.428630183+09:00",
 | 
			
		||||
    "scanMode": "fast mode",
 | 
			
		||||
    "scannedVersion": "v0.15.9",
 | 
			
		||||
    "scannedRevision": "build-20210331_121257_1a58c94",
 | 
			
		||||
    "scannedBy": "dev",
 | 
			
		||||
    "scannedVia": "pseudo",
 | 
			
		||||
    "scannedIpv4Addrs": [
 | 
			
		||||
        "172.19.0.1",
 | 
			
		||||
        "172.17.0.1",
 | 
			
		||||
        "172.27.0.1"
 | 
			
		||||
    ],
 | 
			
		||||
    "reportedAt": "0001-01-01T00:00:00Z",
 | 
			
		||||
    "reportedVersion": "",
 | 
			
		||||
    "reportedRevision": "",
 | 
			
		||||
    "reportedBy": "",
 | 
			
		||||
    "errors": [],
 | 
			
		||||
    "warnings": [],
 | 
			
		||||
    "scannedCves": {},
 | 
			
		||||
    "runningKernel": {
 | 
			
		||||
        "release": "",
 | 
			
		||||
        "version": "",
 | 
			
		||||
        "rebootRequired": false
 | 
			
		||||
    },
 | 
			
		||||
    "packages": {},
 | 
			
		||||
    "config": {
 | 
			
		||||
        "scan": {
 | 
			
		||||
            "logDir": "/var/log/vuls",
 | 
			
		||||
            "resultsDir": "/home/ubuntu/go/src/github.com/future-architect/vuls/results",
 | 
			
		||||
            "default": {
 | 
			
		||||
                "port": "22",
 | 
			
		||||
                "scanMode": [
 | 
			
		||||
                    "fast"
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            "servers": {
 | 
			
		||||
                "rails": {
 | 
			
		||||
                    "serverName": "rails",
 | 
			
		||||
                    "cpeNames": [
 | 
			
		||||
                        "cpe:/a:rubyonrails:ruby_on_rails:3.0.1"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "scanMode": [
 | 
			
		||||
                        "fast"
 | 
			
		||||
                    ],
 | 
			
		||||
                    "type": "pseudo",
 | 
			
		||||
                    "wordpress": {}
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "cveDict": {
 | 
			
		||||
                "Name": "cveDict",
 | 
			
		||||
                "Type": "sqlite3",
 | 
			
		||||
                "SQLite3Path": "/home/ubuntu/go/src/github.com/kotakanbe/go-cve-dictionary/cve.sqlite3",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "ovalDict": {
 | 
			
		||||
                "Name": "ovalDict",
 | 
			
		||||
                "Type": "sqlite3",
 | 
			
		||||
                "SQLite3Path": "/home/ubuntu/go/src/github.com/kotakanbe/goval-dictionary/oval.sqlite3",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "gost": {
 | 
			
		||||
                "Name": "gost",
 | 
			
		||||
                "Type": "sqlite3",
 | 
			
		||||
                "SQLite3Path": "/home/ubuntu/go/src/github.com/future-architect/vuls/gost.sqlite3",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "exploit": {
 | 
			
		||||
                "Name": "exploit",
 | 
			
		||||
                "Type": "sqlite3",
 | 
			
		||||
                "SQLite3Path": "/home/ubuntu/go/src/github.com/vulsio/go-exploitdb/go-exploitdb.sqlite3",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "metasploit": {
 | 
			
		||||
                "Name": "metasploit",
 | 
			
		||||
                "Type": "sqlite3",
 | 
			
		||||
                "SQLite3Path": "/home/ubuntu/go/src/github.com/takuzoo3868/go-msfdb/go-msfdb.sqlite3",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        "report": {
 | 
			
		||||
            "default": {},
 | 
			
		||||
            "cveDict": {
 | 
			
		||||
                "Name": "",
 | 
			
		||||
                "Type": "",
 | 
			
		||||
                "SQLite3Path": "",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "ovalDict": {
 | 
			
		||||
                "Name": "",
 | 
			
		||||
                "Type": "",
 | 
			
		||||
                "SQLite3Path": "",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "gost": {
 | 
			
		||||
                "Name": "",
 | 
			
		||||
                "Type": "",
 | 
			
		||||
                "SQLite3Path": "",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "exploit": {
 | 
			
		||||
                "Name": "",
 | 
			
		||||
                "Type": "",
 | 
			
		||||
                "SQLite3Path": "",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            },
 | 
			
		||||
            "metasploit": {
 | 
			
		||||
                "Name": "",
 | 
			
		||||
                "Type": "",
 | 
			
		||||
                "SQLite3Path": "",
 | 
			
		||||
                "DebugSQL": false
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5158
									
								
								integration/data/rhel_71.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										6926
									
								
								integration/data/rhel_8.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										8609
									
								
								integration/data/ubuntu_1804.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										8559
									
								
								integration/data/ubuntu_2004.json
									
									
									
									
									
										Executable file
									
								
							
							
						
						
							
								
								
									
										27
									
								
								integration/int-config.toml
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,27 @@
 | 
			
		||||
[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:ruby_on_rails:3.0.1" ]
 | 
			
		||||
							
								
								
									
										27
									
								
								integration/int-redis-config.toml
									
									
									
									
									
										Executable file
									
								
							
							
						
						@@ -0,0 +1,27 @@
 | 
			
		||||
[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:ruby_on_rails:3.0.1" ]
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
package libmanager
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/db"
 | 
			
		||||
	"github.com/aquasecurity/trivy/pkg/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/future-architect/vuls/models"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FillLibrary fills LibraryScanner informations
 | 
			
		||||
func FillLibrary(r *models.ScanResult) (totalCnt int, err error) {
 | 
			
		||||
	// initialize trivy's logger and db
 | 
			
		||||
	err = log.InitLogger(false, false)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	if err := db.Init(); err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, lib := range r.LibraryScanners {
 | 
			
		||||
		vinfos, err := lib.Scan()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return 0, err
 | 
			
		||||
		}
 | 
			
		||||
		for _, vinfo := range vinfos {
 | 
			
		||||
			r.ScannedCves[vinfo.CveID] = vinfo
 | 
			
		||||
		}
 | 
			
		||||
		totalCnt += len(vinfos)
 | 
			
		||||
	}
 | 
			
		||||
	db.Close()
 | 
			
		||||
 | 
			
		||||
	return totalCnt, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										117
									
								
								logging/logutil.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,117 @@
 | 
			
		||||
package logging
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"flag"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"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"`
 | 
			
		||||
	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 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)}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// File output
 | 
			
		||||
	dir := GetDefaultLogDir()
 | 
			
		||||
	if logDir != "" {
 | 
			
		||||
		dir = logDir
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Only log to a file if quiet mode enabled
 | 
			
		||||
	if quiet && flag.Lookup("test.v") == nil {
 | 
			
		||||
		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 = file
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Out = os.Stderr
 | 
			
		||||
			log.Errorf("Failed to create log file. path: %s, err: %+v", logFile, err)
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		log.Out = os.Stderr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	whereami := "localhost"
 | 
			
		||||
	if 0 < len(serverName) {
 | 
			
		||||
		whereami = serverName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||