diff --git a/.gitignore b/.gitignore index d807f021a5..c6553f5aa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,11 @@ +vuls .vscode -coverage.out -issues/ *.txt *.json +*.sqlite3 +.gitmodules +coverage.out +issues/ vendor/ log/ -.gitmodules -vuls -*.sqlite3 +results/ diff --git a/README.md b/README.md index b86cfbb0c7..dba5ebae0c 100644 --- a/README.md +++ b/README.md @@ -483,6 +483,7 @@ scan: [-report-json] [-report-mail] [-report-slack] + [-report-text] [-http-proxy=http://192.168.0.1:8080] [-ask-sudo-password] [-ask-key-password] @@ -516,6 +517,8 @@ scan: Send report via Email -report-slack Send report via Slack + -report-text + Write report to text files ($PWD/results/current) -use-unattended-upgrades [Deprecated] For Ubuntu. Scan by unattended-upgrades or not (use apt-get upgrade --dry-run by default) -use-yum-plugin-security @@ -542,6 +545,11 @@ scan: 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. +## -report-text option + +At the end of the scan, scan results will be available in TEXT format in the $PWD/result/current/ directory. +all.txt includes the scan results of all servres and servername.txt includes the scan result of the server. + ## example Run go-cve-dictionary as server mode before scanning. diff --git a/commands/scan.go b/commands/scan.go index a65da9b142..08f949bec5 100644 --- a/commands/scan.go +++ b/commands/scan.go @@ -54,6 +54,7 @@ type ScanCmd struct { reportSlack bool reportMail bool reportJSON bool + reportText bool askSudoPassword bool askKeyPassword bool @@ -81,6 +82,7 @@ func (*ScanCmd) Usage() string { [-report-json] [-report-mail] [-report-slack] + [-report-text] [-http-proxy=http://192.168.0.1:8080] [-ask-sudo-password] [-ask-key-password] @@ -136,6 +138,11 @@ func (p *ScanCmd) SetFlags(f *flag.FlagSet) { false, fmt.Sprintf("Write report to JSON files (%s/results/current)", wd), ) + f.BoolVar(&p.reportText, + "report-text", + false, + fmt.Sprintf("Write report to text files (%s/results/current)", wd), + ) f.BoolVar( &p.askKeyPassword, @@ -221,7 +228,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) // report reports := []report.ResultWriter{ - report.TextWriter{}, + report.StdoutWriter{}, report.LogrusWriter{}, } if p.reportSlack { @@ -233,6 +240,9 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) if p.reportJSON { reports = append(reports, report.JSONWriter{}) } + if p.reportText { + reports = append(reports, report.TextFileWriter{}) + } c.Conf.DBPath = p.dbpath c.Conf.CveDictionaryURL = p.cveDictionaryURL diff --git a/report/json.go b/report/json.go index fe648a16c0..899cdc364a 100644 --- a/report/json.go +++ b/report/json.go @@ -49,13 +49,13 @@ func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) { } 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) + return fmt.Errorf("Failed to write JSON. path: %s, err: %s", jsonPath, err) } } return nil diff --git a/report/slack.go b/report/slack.go index 174e51a031..ca5832794a 100644 --- a/report/slack.go +++ b/report/slack.go @@ -180,10 +180,10 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string { jvn := cveInfo.CveDetail.Jvn return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s", cveInfo.CveDetail.CvssScore(config.Conf.Lang), - jvn.Severity, - fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.Vector), - jvn.Vector, - jvn.Title, + jvn.CvssSeverity(), + fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, jvn.CvssVector()), + jvn.CvssVector(), + jvn.CveTitle(), linkText, ) @@ -191,15 +191,15 @@ func attachmentText(cveInfo models.CveInfo, osFamily string) string { nvd := cveInfo.CveDetail.Nvd return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s", cveInfo.CveDetail.CvssScore(config.Conf.Lang), - nvd.Severity(), + nvd.CvssSeverity(), fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()), nvd.CvssVector(), - nvd.Summary, + nvd.CveSummary(), linkText, ) default: nvd := cveInfo.CveDetail.Nvd - return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText) + return fmt.Sprintf("?\n%s\n%s", nvd.CveSummary(), linkText) } } diff --git a/report/stdout.go b/report/stdout.go index e8d660ac3a..eb7dbaa5cc 100644 --- a/report/stdout.go +++ b/report/stdout.go @@ -23,10 +23,10 @@ import ( "github.com/future-architect/vuls/models" ) -// TextWriter write to stdout -type TextWriter struct{} +// StdoutWriter write to stdout +type StdoutWriter struct{} -func (w TextWriter) Write(scanResults []models.ScanResult) error { +func (w StdoutWriter) Write(scanResults []models.ScanResult) error { for _, s := range scanResults { text, err := toPlainText(s) if err != nil { diff --git a/report/textfile.go b/report/textfile.go new file mode 100644 index 0000000000..34685e8d97 --- /dev/null +++ b/report/textfile.go @@ -0,0 +1,63 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Architect, Inc. Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package report + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/future-architect/vuls/models" +) + +// TextFileWriter writes results to file. +type TextFileWriter struct{} + +func (w TextFileWriter) Write(scanResults []models.ScanResult) (err error) { + + path, err := ensureResultDir() + + all := []string{} + for _, r := range scanResults { + textFilePath := "" + if r.Container.ContainerID == "" { + textFilePath = filepath.Join(path, fmt.Sprintf("%s.txt", r.ServerName)) + } else { + textFilePath = filepath.Join(path, + fmt.Sprintf("%s_%s.txt", r.ServerName, r.Container.Name)) + } + text, err := toPlainText(r) + if err != nil { + return err + } + all = append(all, text) + b := []byte(text) + if err := ioutil.WriteFile(textFilePath, b, 0644); err != nil { + return fmt.Errorf("Failed to write text files. path: %s, err: %s", textFilePath, err) + } + } + + text := strings.Join(all, "\n\n") + b := []byte(text) + allPath := filepath.Join(path, "all.txt") + if err := ioutil.WriteFile(allPath, b, 0644); err != nil { + return fmt.Errorf("Failed to write text files. path: %s, err: %s", allPath, err) + } + return nil +} diff --git a/report/tui.go b/report/tui.go index abf2a62363..693ba901cb 100644 --- a/report/tui.go +++ b/report/tui.go @@ -564,19 +564,19 @@ func summaryLines(data models.ScanResult) string { // packs = append(packs, pack.Name) // } if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() { - summary := d.CveDetail.Jvn.Title + summary := d.CveDetail.Jvn.CveTitle() cols = []string{ fmt.Sprintf(indexFormat, i+1), d.CveDetail.CveID, fmt.Sprintf("| %-4.1f(%s)", d.CveDetail.CvssScore(config.Conf.Lang), - d.CveDetail.Jvn.Severity, + d.CveDetail.Jvn.CvssSeverity(), ), // strings.Join(packs, ","), summary, } } else { - summary := d.CveDetail.Nvd.Summary + summary := d.CveDetail.Nvd.CveSummary() var cvssScore string if d.CveDetail.CvssScore("en") <= 0 { @@ -584,7 +584,7 @@ func summaryLines(data models.ScanResult) string { } else { cvssScore = fmt.Sprintf("| %-4.1f(%s)", d.CveDetail.CvssScore(config.Conf.Lang), - d.CveDetail.Nvd.Severity(), + d.CveDetail.Nvd.CvssSeverity(), ) } @@ -670,16 +670,16 @@ func detailLines() (string, error) { case config.Conf.Lang == "ja" && 0 < cveInfo.CveDetail.Jvn.CvssScore(): jvn := cveInfo.CveDetail.Jvn - cvssSeverity = jvn.Severity - cvssVector = jvn.Vector - summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary) - refs = jvn.References + cvssSeverity = jvn.CvssSeverity() + cvssVector = jvn.CvssVector() + summary = fmt.Sprintf("%s\n%s", jvn.CveTitle(), jvn.CveSummary()) + refs = jvn.VulnSiteReferences() default: nvd := cveInfo.CveDetail.Nvd - cvssSeverity = nvd.Severity() + cvssSeverity = nvd.CvssSeverity() cvssVector = nvd.CvssVector() - summary = nvd.Summary - refs = nvd.References + summary = nvd.CveSummary() + refs = nvd.VulnSiteReferences() } links := []string{ diff --git a/report/util.go b/report/util.go index 48883a8ae3..11d6f4aae6 100644 --- a/report/util.go +++ b/report/util.go @@ -116,21 +116,22 @@ func ToPlainTextSummary(r models.ScanResult) string { case config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore(): - summary := d.CveDetail.Jvn.Title + summary := d.CveDetail.Jvn.CveTitle() scols = []string{ d.CveDetail.CveID, fmt.Sprintf("%-4.1f (%s)", d.CveDetail.CvssScore(config.Conf.Lang), - d.CveDetail.Jvn.Severity, + d.CveDetail.Jvn.CvssSeverity(), ), summary, } case 0 < d.CveDetail.CvssScore("en"): - summary := d.CveDetail.Nvd.Summary + summary := d.CveDetail.Nvd.CveSummary() scols = []string{ d.CveDetail.CveID, - fmt.Sprintf("%-4.1f", + fmt.Sprintf("%-4.1f (%s)", d.CveDetail.CvssScore(config.Conf.Lang), + d.CveDetail.Nvd.CvssSeverity(), ), summary, } @@ -138,7 +139,7 @@ func ToPlainTextSummary(r models.ScanResult) string { scols = []string{ d.CveDetail.CveID, "?", - d.CveDetail.Nvd.Summary, + d.CveDetail.Nvd.CveSummary(), } } @@ -219,14 +220,14 @@ func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string { dtable.AddRow("Score", fmt.Sprintf("%4.1f (%s)", cveDetail.Jvn.CvssScore(), - jvn.Severity, + jvn.CvssSeverity(), )) } else { dtable.AddRow("Score", "?") } - dtable.AddRow("Vector", jvn.Vector) - dtable.AddRow("Title", jvn.Title) - dtable.AddRow("Description", jvn.Summary) + dtable.AddRow("Vector", jvn.CvssVector()) + dtable.AddRow("Title", jvn.CveTitle()) + dtable.AddRow("Description", jvn.CveSummary()) dtable.AddRow("JVN", jvn.Link()) dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)) @@ -261,14 +262,14 @@ func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string { dtable.AddRow("Score", fmt.Sprintf("%4.1f (%s)", cveDetail.Nvd.CvssScore(), - nvd.Severity(), + nvd.CvssSeverity(), )) } else { dtable.AddRow("Score", "?") } dtable.AddRow("Vector", nvd.CvssVector()) - dtable.AddRow("Summary", nvd.Summary) + dtable.AddRow("Summary", nvd.CveSummary()) dtable.AddRow("NVD", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)) dtable.AddRow("MITRE", fmt.Sprintf("%s%s", mitreBaseURL, cveID)) dtable.AddRow("CVE Details", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))