Skip to content

Commit

Permalink
Merge pull request #77 from future-architect/json_writer_sort_order
Browse files Browse the repository at this point in the history
Add JSONWriter, Fix CVE sort order of report
  • Loading branch information
kotakanbe committed May 29, 2016
2 parents 150b1c2 + 54d6217 commit 9ae42d6
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
coverage.out
issues/
*.txt
*.json
vendor/
log/
.gitmodules
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,8 +480,9 @@ scan:
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-ignore-unscored-cves]
[-report-slack]
[-report-json]
[-report-mail]
[-report-slack]
[-http-proxy=http://192.168.0.1:8080]
[-ask-sudo-password]
[-ask-key-password]
Expand Down Expand Up @@ -509,31 +510,37 @@ scan:
Don't report the unscored CVEs
-lang string
[en|ja] (default "en")
-report-json
Write report to JSON files ($PWD/results/current)
-report-mail
Email report
Send report via Email
-report-slack
Slack report
Send report via Slack
-use-unattended-upgrades
[Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default)
-use-yum-plugin-security
[Deprecated] For CentOS 5. Scan by yum-plugin-security or not (use yum check-update by default)

```
## ask-key-password option
## -ask-key-password option
| SSH key password | -ask-key-password | |
|:-----------------|:-------------------|:----|
| empty password | - | |
| with password | required | or use ssh-agent |
## ask-sudo-password option
## -ask-sudo-password option
| sudo password on target servers | -ask-sudo-password | |
|:-----------------|:-------|:------|
| NOPASSWORD | - | defined as NOPASSWORD in /etc/sudoers on target servers |
| with password | required | . |
## -report-json option
At the end of the scan, scan results will be available in JSON format in the $PWD/result/current/ directory.
all.json includes the scan results of all servres and servername.json includes the scan result of the server.
## example
Expand Down Expand Up @@ -563,6 +570,8 @@ With this sample command, it will ..
- Scan only 2 servers (server1, server2)
- Print scan result to terminal
----
# Usage: Scan vulnerability of non-OS package
Expand Down
35 changes: 23 additions & 12 deletions commands/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package commands

import (
"flag"
"fmt"
"os"
"path/filepath"

Expand Down Expand Up @@ -52,6 +53,7 @@ type ScanCmd struct {
// reporting
reportSlack bool
reportMail bool
reportJSON bool

askSudoPassword bool
askKeyPassword bool
Expand All @@ -76,8 +78,9 @@ func (*ScanCmd) Usage() string {
[-cve-dictionary-url=http://127.0.0.1:1323]
[-cvss-over=7]
[-ignore-unscored-cves]
[-report-slack]
[-report-json]
[-report-mail]
[-report-slack]
[-http-proxy=http://192.168.0.1:8080]
[-ask-sudo-password]
[-ask-key-password]
Expand Down Expand Up @@ -126,8 +129,13 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) {
"http://proxy-url:port (default: empty)",
)

f.BoolVar(&p.reportSlack, "report-slack", false, "Slack report")
f.BoolVar(&p.reportMail, "report-mail", false, "Email report")
f.BoolVar(&p.reportSlack, "report-slack", false, "Send report via Slack")
f.BoolVar(&p.reportMail, "report-mail", false, "Send report via Email")
f.BoolVar(&p.reportJSON,
"report-json",
false,
fmt.Sprintf("Write report to JSON files (%s/results/current)", wd),
)

f.BoolVar(
&p.askKeyPassword,
Expand Down Expand Up @@ -222,6 +230,9 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
if p.reportMail {
reports = append(reports, report.MailWriter{})
}
if p.reportJSON {
reports = append(reports, report.JSONWriter{})
}

c.Conf.DBPath = p.dbpath
c.Conf.CveDictionaryURL = p.cveDictionaryURL
Expand Down Expand Up @@ -263,15 +274,6 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
return subcommands.ExitFailure
}

Log.Info("Reporting...")
filtered := scanResults.FilterByCvssOver()
for _, w := range reports {
if err := w.Write(filtered); err != nil {
Log.Fatalf("Failed to output report, err: %s", err)
return subcommands.ExitFailure
}
}

Log.Info("Insert to DB...")
if err := db.OpenDB(); err != nil {
Log.Errorf("Failed to open DB. datafile: %s, err: %s", c.Conf.DBPath, err)
Expand All @@ -287,5 +289,14 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
return subcommands.ExitFailure
}

Log.Info("Reporting...")
filtered := scanResults.FilterByCvssOver()
for _, w := range reports {
if err := w.Write(filtered); err != nil {
Log.Fatalf("Failed to report, err: %s", err)
return subcommands.ExitFailure
}
}

return subcommands.ExitSuccess
}
31 changes: 17 additions & 14 deletions models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ func (s ScanResults) FilterByCvssOver() (filtered ScanResults) {

// ScanResult has the result of scanned CVE information.
type ScanResult struct {
gorm.Model
ScanHistoryID uint
gorm.Model `json:"-"`
ScanHistoryID uint `json:"-"`

ServerName string // TOML Section key
// Hostname string
Expand Down Expand Up @@ -161,8 +161,8 @@ func (r ScanResult) CveSummary() string {

// NWLink has network link information.
type NWLink struct {
gorm.Model
ScanResultID uint
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`

IPAddress string
Netmask string
Expand All @@ -183,13 +183,16 @@ func (c CveInfos) Swap(i, j int) {

func (c CveInfos) Less(i, j int) bool {
lang := config.Conf.Lang
if c[i].CveDetail.CvssScore(lang) == c[j].CveDetail.CvssScore(lang) {
return c[i].CveDetail.CveID < c[j].CveDetail.CveID
}
return c[i].CveDetail.CvssScore(lang) > c[j].CveDetail.CvssScore(lang)
}

// CveInfo has Cve Information.
type CveInfo struct {
gorm.Model
ScanResultID uint
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`

CveDetail cve.CveDetail
Packages []PackageInfo
Expand All @@ -199,8 +202,8 @@ type CveInfo struct {

// CpeName has CPE name
type CpeName struct {
gorm.Model
CveInfoID uint
gorm.Model `json:"-"`
CveInfoID uint `json:"-"`

Name string
}
Expand Down Expand Up @@ -265,8 +268,8 @@ func (ps PackageInfoList) FindByName(name string) (result PackageInfo, found boo

// PackageInfo has installed packages.
type PackageInfo struct {
gorm.Model
CveInfoID uint
gorm.Model `json:"-"`
CveInfoID uint `json:"-"`

Name string
Version string
Expand Down Expand Up @@ -302,8 +305,8 @@ func (p PackageInfo) ToStringNewVersion() string {

// DistroAdvisory has Amazon Linux AMI Security Advisory information.
type DistroAdvisory struct {
gorm.Model
CveInfoID uint
gorm.Model `json:"-"`
CveInfoID uint `json:"-"`

AdvisoryID string
Severity string
Expand All @@ -313,8 +316,8 @@ type DistroAdvisory struct {

// Container has Container information
type Container struct {
gorm.Model
ScanResultID uint
gorm.Model `json:"-"`
ScanResultID uint `json:"-"`

ContainerID string
Name string
Expand Down
35 changes: 30 additions & 5 deletions report/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,43 @@ package report
import (
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"

"github.com/future-architect/vuls/models"
)

// JSONWriter writes report as JSON format
// JSONWriter writes results to file.
type JSONWriter struct{}

func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
var j []byte
if j, err = json.MarshalIndent(scanResults, "", " "); err != nil {
return

path, err := ensureResultDir()

var jsonBytes []byte
if jsonBytes, err = json.MarshalIndent(scanResults, "", " "); err != nil {
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
}
all := filepath.Join(path, "all.json")
if err := ioutil.WriteFile(all, jsonBytes, 0644); err != nil {
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
}

for _, r := range scanResults {
jsonPath := ""
if r.Container.ContainerID == "" {
jsonPath = filepath.Join(path, fmt.Sprintf("%s.json", r.ServerName))
} else {
jsonPath = filepath.Join(path,
fmt.Sprintf("%s_%s.json", r.ServerName, r.Container.Name))

}
if jsonBytes, err = json.MarshalIndent(r, "", " "); err != nil {
return fmt.Errorf("Failed to Marshal to JSON: %s", err)
}
if err := ioutil.WriteFile(jsonPath, jsonBytes, 0644); err != nil {
return fmt.Errorf("Failed to write JSON. path: %s, err: %s", all, err)
}
}
fmt.Println(string(j))
return nil
}
6 changes: 2 additions & 4 deletions report/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
if !config.Conf.IgnoreUnscoredCves {
cves = append(cves, scanResult.UnknownCves...)
}
scanResult.KnownCves = cves

for _, cveInfo := range scanResult.KnownCves {
for _, cveInfo := range cves {
cveID := cveInfo.CveDetail.CveID

curentPackages := []string{}
Expand Down Expand Up @@ -176,8 +175,7 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string {

switch {
case config.Conf.Lang == "ja" &&
cveInfo.CveDetail.Jvn.ID != 0 &&
0 < cveInfo.CveDetail.CvssScore("ja"):
0 < cveInfo.CveDetail.Jvn.CvssScore():

jvn := cveInfo.CveDetail.Jvn
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
Expand Down
41 changes: 35 additions & 6 deletions report/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,44 @@ package report
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"time"

"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/gosuri/uitable"
)

func ensureResultDir() (path string, err error) {
if resultDirPath != "" {
return resultDirPath, nil
}

const timeLayout = "20060102_1504"
timedir := time.Now().Format(timeLayout)
wd, _ := os.Getwd()
dir := filepath.Join(wd, "results", timedir)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", fmt.Errorf("Failed to create dir: %s", err)
}

symlinkPath := filepath.Join(wd, "results", "current")
if _, err := os.Stat(symlinkPath); err == nil {
if err := os.Remove(symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to remove symlink. path: %s, err: %s", symlinkPath, err)
}
}

if err := os.Symlink(dir, symlinkPath); err != nil {
return "", fmt.Errorf(
"Failed to create symlink: path: %s, err: %s", symlinkPath, err)
}
return dir, nil
}

func toPlainText(scanResult models.ScanResult) (string, error) {
serverInfo := scanResult.ServerInfo()

Expand Down Expand Up @@ -83,8 +114,7 @@ func ToPlainTextSummary(r models.ScanResult) string {

switch {
case config.Conf.Lang == "ja" &&
d.CveDetail.Jvn.ID != 0 &&
0 < d.CveDetail.CvssScore("ja"):
0 < d.CveDetail.Jvn.CvssScore():

summary := d.CveDetail.Jvn.Title
scols = []string{
Expand Down Expand Up @@ -121,23 +151,22 @@ func ToPlainTextSummary(r models.ScanResult) string {
return fmt.Sprintf("%s", stable)
}

//TODO Distro Advisory
func toPlainTextDetails(data models.ScanResult, osFamily string) (scoredReport, unscoredReport []string) {
for _, cve := range data.KnownCves {
switch config.Conf.Lang {
case "en":
if cve.CveDetail.Nvd.ID != 0 {
if 0 < cve.CveDetail.Nvd.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
} else {
scoredReport = append(
scoredReport, toPlainTextUnknownCve(cve, osFamily))
}
case "ja":
if cve.CveDetail.Jvn.ID != 0 {
if 0 < cve.CveDetail.Jvn.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
} else if cve.CveDetail.Nvd.ID != 0 {
} else if 0 < cve.CveDetail.Nvd.CvssScore() {
scoredReport = append(
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
} else {
Expand Down
2 changes: 2 additions & 0 deletions report/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ const (
type ResultWriter interface {
Write([]models.ScanResult) error
}

var resultDirPath string
Loading

0 comments on commit 9ae42d6

Please sign in to comment.