Add: source code
This commit is contained in:
37
report/json.go
Normal file
37
report/json.go
Normal file
@@ -0,0 +1,37 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// JSONWriter writes report as JSON format
|
||||
type JSONWriter struct{}
|
||||
|
||||
func (w JSONWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
var j []byte
|
||||
if j, err = json.MarshalIndent(scanResults, "", " "); err != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println(string(j))
|
||||
return nil
|
||||
}
|
||||
51
report/logrus.go
Normal file
51
report/logrus.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/models"
|
||||
formatter "github.com/kotakanbe/logrus-prefixed-formatter"
|
||||
)
|
||||
|
||||
// LogrusWriter write to logfile
|
||||
type LogrusWriter struct {
|
||||
}
|
||||
|
||||
func (w LogrusWriter) Write(scanResults []models.ScanResult) error {
|
||||
path := "/var/log/vuls/report.log"
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log := logrus.New()
|
||||
log.Formatter = &formatter.TextFormatter{}
|
||||
log.Out = f
|
||||
log.Level = logrus.InfoLevel
|
||||
|
||||
for _, s := range scanResults {
|
||||
text, err := toPlainText(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof(text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
70
report/mail.go
Normal file
70
report/mail.go
Normal file
@@ -0,0 +1,70 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
// MailWriter send mail
|
||||
type MailWriter struct{}
|
||||
|
||||
func (w MailWriter) Write(scanResults []models.ScanResult) (err error) {
|
||||
conf := config.Conf
|
||||
for _, s := range scanResults {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", conf.Mail.From)
|
||||
m.SetHeader("To", conf.Mail.To...)
|
||||
m.SetHeader("Cc", conf.Mail.Cc...)
|
||||
|
||||
subject := fmt.Sprintf("%s%s %s",
|
||||
conf.Mail.SubjectPrefix,
|
||||
s.ServerName,
|
||||
s.CveSummary(),
|
||||
)
|
||||
m.SetHeader("Subject", subject)
|
||||
|
||||
var body string
|
||||
if body, err = toPlainText(s); err != nil {
|
||||
return err
|
||||
}
|
||||
m.SetBody("text/plain", body)
|
||||
port, _ := strconv.Atoi(conf.Mail.SMTPPort)
|
||||
d := gomail.NewPlainDialer(
|
||||
conf.Mail.SMTPAddr,
|
||||
port,
|
||||
conf.Mail.User,
|
||||
conf.Mail.Password,
|
||||
)
|
||||
|
||||
d.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
236
report/slack.go
Normal file
236
report/slack.go
Normal file
@@ -0,0 +1,236 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/cenkalti/backoff"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
)
|
||||
|
||||
type field struct {
|
||||
Title string `json:"title"`
|
||||
Value string `json:"value"`
|
||||
Short bool `json:"short"`
|
||||
}
|
||||
type attachment struct {
|
||||
Title string `json:"title"`
|
||||
TitleLink string `json:"title_link"`
|
||||
Fallback string `json:"fallback"`
|
||||
Text string `json:"text"`
|
||||
Pretext string `json:"pretext"`
|
||||
Color string `json:"color"`
|
||||
Fields []*field `json:"fields"`
|
||||
MrkdwnIn []string `json:"mrkdwn_in"`
|
||||
}
|
||||
type message struct {
|
||||
Text string `json:"text"`
|
||||
Username string `json:"username"`
|
||||
IconEmoji string `json:"icon_emoji"`
|
||||
Channel string `json:"channel"`
|
||||
Attachments []*attachment `json:"attachments"`
|
||||
}
|
||||
|
||||
// SlackWriter send report to slack
|
||||
type SlackWriter struct{}
|
||||
|
||||
func (w SlackWriter) Write(scanResults []models.ScanResult) error {
|
||||
conf := config.Conf.Slack
|
||||
for _, s := range scanResults {
|
||||
|
||||
channel := conf.Channel
|
||||
if channel == "${servername}" {
|
||||
channel = fmt.Sprintf("#%s", s.ServerName)
|
||||
}
|
||||
|
||||
msg := message{
|
||||
Text: msgText(s),
|
||||
Username: conf.AuthUser,
|
||||
IconEmoji: conf.IconEmoji,
|
||||
Channel: channel,
|
||||
Attachments: toSlackAttachments(s),
|
||||
}
|
||||
|
||||
bytes, _ := json.Marshal(msg)
|
||||
jsonBody := string(bytes)
|
||||
f := func() (err error) {
|
||||
resp, body, errs := gorequest.New().Proxy(config.Conf.HTTPProxy).Post(conf.HookURL).
|
||||
Send(string(jsonBody)).End()
|
||||
if resp.StatusCode != 200 {
|
||||
log.Errorf("Resonse body: %s", body)
|
||||
if len(errs) > 0 {
|
||||
return errs[0]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
notify := func(err error, t time.Duration) {
|
||||
log.Warn("Retrying in ", t)
|
||||
}
|
||||
if err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify); err != nil {
|
||||
return fmt.Errorf("HTTP Error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func msgText(r models.ScanResult) string {
|
||||
|
||||
notifyUsers := ""
|
||||
if 0 < len(r.KnownCves) || 0 < len(r.UnknownCves) {
|
||||
notifyUsers = getNotifyUsers(config.Conf.Slack.NotifyUsers)
|
||||
}
|
||||
|
||||
hostinfo := fmt.Sprintf(
|
||||
"*%s* (%s %s)",
|
||||
r.ServerName,
|
||||
r.Family,
|
||||
r.Release,
|
||||
)
|
||||
return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, hostinfo, r.CveSummary())
|
||||
}
|
||||
|
||||
func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) {
|
||||
|
||||
scanResult.KnownCves = append(scanResult.KnownCves, scanResult.UnknownCves...)
|
||||
for _, cveInfo := range scanResult.KnownCves {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
|
||||
curentPackages := []string{}
|
||||
for _, p := range cveInfo.Packages {
|
||||
curentPackages = append(curentPackages, p.ToStringCurrentVersion())
|
||||
}
|
||||
for _, cpename := range cveInfo.CpeNames {
|
||||
curentPackages = append(curentPackages, cpename.Name)
|
||||
}
|
||||
|
||||
newPackages := []string{}
|
||||
for _, p := range cveInfo.Packages {
|
||||
newPackages = append(newPackages, p.ToStringNewVersion())
|
||||
}
|
||||
|
||||
a := attachment{
|
||||
Title: cveID,
|
||||
TitleLink: fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID),
|
||||
Text: attachmentText(cveInfo, scanResult.Family),
|
||||
MrkdwnIn: []string{"text", "pretext"},
|
||||
Fields: []*field{
|
||||
{
|
||||
// Title: "Current Package/CPE",
|
||||
Title: "Installed",
|
||||
Value: strings.Join(curentPackages, "\n"),
|
||||
Short: true,
|
||||
},
|
||||
{
|
||||
Title: "Candidate",
|
||||
Value: strings.Join(newPackages, "\n"),
|
||||
Short: true,
|
||||
},
|
||||
},
|
||||
Color: color(cveInfo.CveDetail.CvssScore(config.Conf.Lang)),
|
||||
}
|
||||
attaches = append(attaches, &a)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// https://api.slack.com/docs/attachments
|
||||
func color(cvssScore float64) string {
|
||||
switch {
|
||||
case 7 <= cvssScore:
|
||||
return "danger"
|
||||
case 4 <= cvssScore && cvssScore < 7:
|
||||
return "warning"
|
||||
case cvssScore < 0:
|
||||
return "#C0C0C0"
|
||||
default:
|
||||
return "good"
|
||||
}
|
||||
}
|
||||
|
||||
func attachmentText(cveInfo models.CveInfo, osFamily string) string {
|
||||
|
||||
linkText := links(cveInfo, osFamily)
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
cveInfo.CveDetail.Jvn.ID != 0 &&
|
||||
0 < cveInfo.CveDetail.CvssScore("ja"):
|
||||
|
||||
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,
|
||||
linkText,
|
||||
)
|
||||
|
||||
case 0 < cveInfo.CveDetail.CvssScore("en"):
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("*%4.1f (%s)* <%s|%s>\n%s\n%s",
|
||||
cveInfo.CveDetail.CvssScore(config.Conf.Lang),
|
||||
nvd.Severity(),
|
||||
fmt.Sprintf(cvssV2CalcURLTemplate, cveInfo.CveDetail.CveID, nvd.CvssVector()),
|
||||
nvd.CvssVector(),
|
||||
nvd.Summary,
|
||||
linkText,
|
||||
)
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
return fmt.Sprintf("?\n%s\n%s", nvd.Summary, linkText)
|
||||
}
|
||||
}
|
||||
|
||||
func links(cveInfo models.CveInfo, osFamily string) string {
|
||||
links := []string{}
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
if config.Conf.Lang == "ja" && 0 < len(cveInfo.CveDetail.Jvn.Link()) {
|
||||
jvn := fmt.Sprintf("<%s|JVN>", cveInfo.CveDetail.Jvn.Link())
|
||||
links = append(links, jvn)
|
||||
}
|
||||
links = append(links, fmt.Sprintf("<%s|CVEDetails>",
|
||||
fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)))
|
||||
links = append(links, fmt.Sprintf("<%s|MITRE>",
|
||||
fmt.Sprintf("%s%s", mitreBaseURL, cveID)))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
links = append(links,
|
||||
fmt.Sprintf("<%s|%s>", link.url, link.title))
|
||||
}
|
||||
|
||||
return strings.Join(links, " / ")
|
||||
}
|
||||
|
||||
// See testcase
|
||||
func getNotifyUsers(notifyUsers []string) string {
|
||||
slackStyleTexts := []string{}
|
||||
for _, username := range notifyUsers {
|
||||
slackStyleTexts = append(slackStyleTexts, fmt.Sprintf("<%s>", username))
|
||||
}
|
||||
return strings.Join(slackStyleTexts, " ")
|
||||
}
|
||||
23
report/slack_test.go
Normal file
23
report/slack_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package report
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetNotifyUsers(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
[]string{"@user1", "@user2"},
|
||||
"<@user1> <@user2>",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
actual := getNotifyUsers(tt.in)
|
||||
if tt.expected != actual {
|
||||
t.Errorf("expected %s, actual %s", tt.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
38
report/stdout.go
Normal file
38
report/stdout.go
Normal file
@@ -0,0 +1,38 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
// TextWriter write to stdout
|
||||
type TextWriter struct{}
|
||||
|
||||
func (w TextWriter) Write(scanResults []models.ScanResult) error {
|
||||
for _, s := range scanResults {
|
||||
text, err := toPlainText(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(text)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
770
report/tui.go
Normal file
770
report/tui.go
Normal file
@@ -0,0 +1,770 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/db"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/google/subcommands"
|
||||
"github.com/gosuri/uitable"
|
||||
"github.com/jroimartin/gocui"
|
||||
cve "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
var scanHistory models.ScanHistory
|
||||
var currentScanResult models.ScanResult
|
||||
var currentCveInfo int
|
||||
var currentDetailLimitY int
|
||||
|
||||
// RunTui execute main logic
|
||||
func RunTui() subcommands.ExitStatus {
|
||||
var err error
|
||||
scanHistory, err = latestScanHistory()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
g := gocui.NewGui()
|
||||
if err := g.Init(); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
g.SetLayout(layout)
|
||||
if err := keybindings(g); err != nil {
|
||||
log.Panicln(err)
|
||||
}
|
||||
g.SelBgColor = gocui.ColorGreen
|
||||
g.SelFgColor = gocui.ColorBlack
|
||||
g.Cursor = true
|
||||
|
||||
if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
|
||||
log.Panicln(err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func latestScanHistory() (latest models.ScanHistory, err error) {
|
||||
if err := db.OpenDB(); err != nil {
|
||||
return latest, fmt.Errorf(
|
||||
"Failed to open DB. datafile: %s, err: %s", config.Conf.DBPath, err)
|
||||
}
|
||||
latest, err = db.SelectLatestScanHistory()
|
||||
return
|
||||
}
|
||||
|
||||
func keybindings(g *gocui.Gui) (err error) {
|
||||
errs := []error{}
|
||||
|
||||
// Move beetween views
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowRight, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlN, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyCtrlP, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, showMsg))
|
||||
|
||||
// summary
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlH, gocui.ModNone, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowLeft, gocui.ModAlt, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
// errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
||||
errs = append(errs, g.SetKeybinding("summary", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
||||
|
||||
// detail
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyTab, gocui.ModNone, nextView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlQ, gocui.ModNone, previousView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlH, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlL, gocui.ModNone, nextView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModAlt, previousView))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowLeft, gocui.ModAlt, nextView))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowDown, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyArrowUp, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlJ, gocui.ModNone, cursorDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlK, gocui.ModNone, cursorUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlD, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlU, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeySpace, gocui.ModNone, cursorPageDown))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace, gocui.ModNone, cursorPageUp))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyBackspace2, gocui.ModNone, cursorPageUp))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlM, gocui.ModNone, cursorMoveMiddle))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlN, gocui.ModNone, nextSummary))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyCtrlP, gocui.ModNone, previousSummary))
|
||||
errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, nextView))
|
||||
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
|
||||
|
||||
//TODO Help Ctrl-h
|
||||
|
||||
errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
|
||||
for _, e := range errs {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil {
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
return g.SetCurrentView("summary")
|
||||
case "summary":
|
||||
return g.SetCurrentView("detail")
|
||||
case "detail":
|
||||
return g.SetCurrentView("side")
|
||||
default:
|
||||
return g.SetCurrentView("summary")
|
||||
}
|
||||
}
|
||||
|
||||
func previousView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil {
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
return g.SetCurrentView("side")
|
||||
case "summary":
|
||||
return g.SetCurrentView("side")
|
||||
case "detail":
|
||||
return g.SetCurrentView("summary")
|
||||
default:
|
||||
return g.SetCurrentView("side")
|
||||
}
|
||||
}
|
||||
|
||||
func movable(v *gocui.View, nextY int) (ok bool, yLimit int) {
|
||||
switch v.Name() {
|
||||
case "side":
|
||||
yLimit = len(scanHistory.ScanResults) - 1
|
||||
if yLimit < nextY {
|
||||
return false, yLimit
|
||||
}
|
||||
return true, yLimit
|
||||
case "summary":
|
||||
yLimit = len(currentScanResult.KnownCves) - 1
|
||||
if yLimit < nextY {
|
||||
return false, yLimit
|
||||
}
|
||||
return true, yLimit
|
||||
case "detail":
|
||||
if currentDetailLimitY < nextY {
|
||||
return false, currentDetailLimitY
|
||||
}
|
||||
return true, currentDetailLimitY
|
||||
default:
|
||||
return true, 0
|
||||
}
|
||||
}
|
||||
|
||||
func pageUpDownJumpCount(v *gocui.View) int {
|
||||
var jump int
|
||||
switch v.Name() {
|
||||
case "side", "summary":
|
||||
jump = 8
|
||||
case "detail":
|
||||
jump = 30
|
||||
default:
|
||||
jump = 8
|
||||
}
|
||||
return jump
|
||||
}
|
||||
|
||||
// redraw views
|
||||
func onMovingCursorRedrawView(g *gocui.Gui, v *gocui.View) error {
|
||||
switch v.Name() {
|
||||
case "summary":
|
||||
if err := redrawDetail(g); err != nil {
|
||||
return err
|
||||
}
|
||||
case "side":
|
||||
if err := changeHost(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
// ok, := movable(v, oy+cy+1)
|
||||
// _, maxY := v.Size()
|
||||
ok, _ := movable(v, oy+cy+1)
|
||||
// log.Info(cy, oy, maxY, yLimit)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveTop(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, 0)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveBottom(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
_, maxY := v.Size()
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, maxY-1)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorMoveMiddle(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
_, maxY := v.Size()
|
||||
cx, _ := v.Cursor()
|
||||
v.SetCursor(cx, maxY/2)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorPageDown(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := pageUpDownJumpCount(v)
|
||||
|
||||
if v != nil {
|
||||
cx, cy := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
ok, yLimit := movable(v, oy+cy+jump)
|
||||
_, maxY := v.Size()
|
||||
|
||||
if !ok {
|
||||
if yLimit < maxY {
|
||||
v.SetCursor(cx, yLimit)
|
||||
} else {
|
||||
v.SetCursor(cx, maxY-1)
|
||||
v.SetOrigin(ox, yLimit-maxY+1)
|
||||
}
|
||||
} else if yLimit < oy+jump+maxY {
|
||||
if yLimit < maxY {
|
||||
v.SetCursor(cx, yLimit)
|
||||
} else {
|
||||
v.SetOrigin(ox, yLimit-maxY+1)
|
||||
v.SetCursor(cx, maxY-1)
|
||||
}
|
||||
} else {
|
||||
v.SetCursor(cx, cy)
|
||||
v.SetOrigin(ox, oy+jump)
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func cursorPageUp(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := pageUpDownJumpCount(v)
|
||||
if v != nil {
|
||||
cx, _ := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
if err := v.SetOrigin(ox, oy-jump); err != nil {
|
||||
v.SetOrigin(ox, 0)
|
||||
v.SetCursor(cx, 0)
|
||||
|
||||
}
|
||||
onMovingCursorRedrawView(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func previousSummary(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
// cursor to summary
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
// move next line
|
||||
if err := cursorUp(g, g.CurrentView()); err != nil {
|
||||
return err
|
||||
}
|
||||
// cursor to detail
|
||||
if err := g.SetCurrentView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func nextSummary(g *gocui.Gui, v *gocui.View) error {
|
||||
if v != nil {
|
||||
// cursor to summary
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
// move next line
|
||||
if err := cursorDown(g, g.CurrentView()); err != nil {
|
||||
return err
|
||||
}
|
||||
// cursor to detail
|
||||
if err := g.SetCurrentView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func changeHost(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
if err := g.DeleteView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.DeleteView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, cy := v.Cursor()
|
||||
l, err := v.Line(cy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serverName := strings.TrimSpace(l)
|
||||
|
||||
for _, r := range scanHistory.ScanResults {
|
||||
if serverName == r.ServerName {
|
||||
currentScanResult = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := setSummaryLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func redrawDetail(g *gocui.Gui) error {
|
||||
if err := g.DeleteView("detail"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLine(g *gocui.Gui, v *gocui.View) error {
|
||||
var l string
|
||||
var err error
|
||||
|
||||
_, cy := v.Cursor()
|
||||
if l, err = v.Line(cy); err != nil {
|
||||
l = ""
|
||||
}
|
||||
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
if err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
jump := 8
|
||||
_, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
ok, yLimit := movable(v, oy+cy+jump)
|
||||
// maxX, maxY := v.Size()
|
||||
_, maxY := v.Size()
|
||||
|
||||
l := fmt.Sprintf("cy: %d, oy: %d, maxY: %d, yLimit: %d, curCve %d, ok: %v", cy, oy, maxY, yLimit, currentCveInfo, ok)
|
||||
// if v, err := g.SetView("msg", maxX/2-30, maxY/2, maxX/2+30, maxY/2+2); err != nil {
|
||||
if v, err := g.SetView("msg", 10, maxY/2, 10+50, maxY/2+2); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(v, l)
|
||||
if err := g.SetCurrentView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delMsg(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := g.DeleteView("msg"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetCurrentView("summary"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func quit(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func layout(g *gocui.Gui) error {
|
||||
if err := setSideLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setSummaryLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := setDetailLayout(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSideLayout(g *gocui.Gui) error {
|
||||
_, maxY := g.Size()
|
||||
if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Highlight = true
|
||||
|
||||
for _, result := range scanHistory.ScanResults {
|
||||
fmt.Fprintln(v, result.ServerName)
|
||||
}
|
||||
currentScanResult = scanHistory.ScanResults[0]
|
||||
if err := g.SetCurrentView("side"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setSummaryLayout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
if v, err := g.SetView("summary", 30, -1, maxX, int(float64(maxY)*0.2)); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := summaryLines(currentScanResult)
|
||||
fmt.Fprintf(v, lines)
|
||||
|
||||
v.Highlight = true
|
||||
v.Editable = false
|
||||
v.Wrap = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func summaryLines(data models.ScanResult) string {
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = 1000
|
||||
stable.Wrap = false
|
||||
|
||||
indexFormat := ""
|
||||
if len(data.KnownCves) < 10 {
|
||||
indexFormat = "[%1d]"
|
||||
} else if len(data.KnownCves) < 100 {
|
||||
indexFormat = "[%2d]"
|
||||
} else {
|
||||
indexFormat = "[%3d]"
|
||||
}
|
||||
|
||||
for i, d := range data.KnownCves {
|
||||
var cols []string
|
||||
// packs := []string{}
|
||||
// for _, pack := range d.Packages {
|
||||
// packs = append(packs, pack.Name)
|
||||
// }
|
||||
if config.Conf.Lang == "ja" && 0 < d.CveDetail.Jvn.CvssScore() {
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
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,
|
||||
),
|
||||
// strings.Join(packs, ","),
|
||||
summary,
|
||||
}
|
||||
} else {
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
|
||||
var cvssScore string
|
||||
if d.CveDetail.CvssScore("en") <= 0 {
|
||||
cvssScore = "| ?"
|
||||
} else {
|
||||
cvssScore = fmt.Sprintf("| %-4.1f(%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Nvd.Severity(),
|
||||
)
|
||||
}
|
||||
|
||||
cols = []string{
|
||||
fmt.Sprintf(indexFormat, i+1),
|
||||
d.CveDetail.CveID,
|
||||
cvssScore,
|
||||
summary,
|
||||
}
|
||||
}
|
||||
|
||||
icols := make([]interface{}, len(cols))
|
||||
for j := range cols {
|
||||
icols[j] = cols[j]
|
||||
}
|
||||
stable.AddRow(icols...)
|
||||
}
|
||||
// ignore UnknownCves
|
||||
return fmt.Sprintf("%s", stable)
|
||||
}
|
||||
|
||||
func setDetailLayout(g *gocui.Gui) error {
|
||||
maxX, maxY := g.Size()
|
||||
|
||||
summaryView, err := g.View("summary")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, cy := summaryView.Cursor()
|
||||
_, oy := summaryView.Origin()
|
||||
currentCveInfo = cy + oy
|
||||
|
||||
if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
// text := report.ToPlainTextDetailsLangEn(
|
||||
// currentScanResult.KnownCves[currentCveInfo],
|
||||
// currentScanResult.Family)
|
||||
|
||||
//TODO error handling
|
||||
text, err := detailLines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(v, text)
|
||||
v.Editable = false
|
||||
v.Wrap = true
|
||||
|
||||
currentDetailLimitY = len(strings.Split(text, "\n")) - 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dataForTmpl struct {
|
||||
CveID string
|
||||
CvssScore string
|
||||
CvssVector string
|
||||
CvssSeverity string
|
||||
Summary string
|
||||
VulnSiteLinks []string
|
||||
References []cve.Reference
|
||||
Packages []string
|
||||
CpeNames []models.CpeName
|
||||
PublishedDate time.Time
|
||||
LastModifiedDate time.Time
|
||||
}
|
||||
|
||||
func detailLines() (string, error) {
|
||||
cveInfo := currentScanResult.KnownCves[currentCveInfo]
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
|
||||
tmpl, err := template.New("detail").Parse(detailTemplate())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var cvssSeverity, cvssVector, summary string
|
||||
var refs []cve.Reference
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
0 < cveInfo.CveDetail.Jvn.CvssScore():
|
||||
jvn := cveInfo.CveDetail.Jvn
|
||||
cvssSeverity = jvn.Severity
|
||||
cvssVector = jvn.Vector
|
||||
summary = fmt.Sprintf("%s\n%s", jvn.Title, jvn.Summary)
|
||||
refs = jvn.References
|
||||
default:
|
||||
nvd := cveInfo.CveDetail.Nvd
|
||||
cvssSeverity = nvd.Severity()
|
||||
cvssVector = nvd.CvssVector()
|
||||
summary = nvd.Summary
|
||||
refs = nvd.References
|
||||
}
|
||||
|
||||
links := []string{
|
||||
fmt.Sprintf("[NVD]( %s )", fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID)),
|
||||
fmt.Sprintf("[MITRE]( %s )", fmt.Sprintf("%s%s", mitreBaseURL, cveID)),
|
||||
fmt.Sprintf("[CveDetais]( %s )", fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID)),
|
||||
fmt.Sprintf("[CVSSv2 Caluclator]( %s )", fmt.Sprintf(cvssV2CalcURLTemplate, cveID, cvssVector)),
|
||||
}
|
||||
dlinks := distroLinks(cveInfo, currentScanResult.Family)
|
||||
for _, link := range dlinks {
|
||||
links = append(links, fmt.Sprintf("[%s]( %s )", link.title, link.url))
|
||||
}
|
||||
|
||||
var cvssScore string
|
||||
if cveInfo.CveDetail.CvssScore(config.Conf.Lang) == -1 {
|
||||
cvssScore = "?"
|
||||
} else {
|
||||
cvssScore = fmt.Sprintf("%4.1f", cveInfo.CveDetail.CvssScore(config.Conf.Lang))
|
||||
}
|
||||
|
||||
packages := []string{}
|
||||
for _, pack := range cveInfo.Packages {
|
||||
packages = append(packages,
|
||||
fmt.Sprintf(
|
||||
"%s -> %s",
|
||||
pack.ToStringCurrentVersion(),
|
||||
pack.ToStringNewVersion()))
|
||||
}
|
||||
|
||||
data := dataForTmpl{
|
||||
CveID: cveID,
|
||||
CvssScore: cvssScore,
|
||||
CvssSeverity: cvssSeverity,
|
||||
CvssVector: cvssVector,
|
||||
Summary: summary,
|
||||
VulnSiteLinks: links,
|
||||
References: refs,
|
||||
Packages: packages,
|
||||
CpeNames: cveInfo.CpeNames,
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil) // create empty buffer
|
||||
if err := tmpl.Execute(buf, data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// * {{.Name}}-{{.Version}}-{{.Release}}
|
||||
|
||||
func detailTemplate() string {
|
||||
return `
|
||||
{{.CveID}}
|
||||
==============
|
||||
|
||||
CVSS Score
|
||||
--------------
|
||||
|
||||
{{.CvssScore}} ({{.CvssSeverity}}) {{.CvssVector}}
|
||||
|
||||
Summary
|
||||
--------------
|
||||
|
||||
{{.Summary }}
|
||||
|
||||
Package/CPE
|
||||
--------------
|
||||
|
||||
{{range $pack := .Packages -}}
|
||||
* {{$pack}}
|
||||
{{end -}}
|
||||
{{range .CpeNames -}}
|
||||
* {{.Name}}
|
||||
{{end}}
|
||||
Links
|
||||
--------------
|
||||
|
||||
{{range $link := .VulnSiteLinks -}}
|
||||
* {{$link}}
|
||||
{{end}}
|
||||
References
|
||||
--------------
|
||||
|
||||
{{range .References -}}
|
||||
* [{{.Source}}]( {{.Link}} )
|
||||
{{end}}
|
||||
|
||||
`
|
||||
}
|
||||
334
report/util.go
Normal file
334
report/util.go
Normal file
@@ -0,0 +1,334 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/gosuri/uitable"
|
||||
)
|
||||
|
||||
func toPlainText(scanResult models.ScanResult) (string, error) {
|
||||
hostinfo := fmt.Sprintf(
|
||||
"%s (%s %s)",
|
||||
scanResult.ServerName,
|
||||
scanResult.Family,
|
||||
scanResult.Release,
|
||||
)
|
||||
|
||||
var buffer bytes.Buffer
|
||||
for i := 0; i < len(hostinfo); i++ {
|
||||
buffer.WriteString("=")
|
||||
}
|
||||
header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String())
|
||||
|
||||
if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 {
|
||||
return fmt.Sprintf(`
|
||||
%s
|
||||
No unsecure packages.
|
||||
`, header), nil
|
||||
}
|
||||
|
||||
summary := ToPlainTextSummary(scanResult)
|
||||
scoredReport, unscoredReport := []string{}, []string{}
|
||||
scoredReport, unscoredReport = toPlainTextDetails(scanResult, scanResult.Family)
|
||||
|
||||
scored := strings.Join(scoredReport, "\n\n")
|
||||
unscored := strings.Join(unscoredReport, "\n\n")
|
||||
detail := fmt.Sprintf(`
|
||||
%s
|
||||
|
||||
%s
|
||||
`,
|
||||
scored,
|
||||
unscored,
|
||||
)
|
||||
text := fmt.Sprintf("%s\n%s\n%s\n", header, summary, detail)
|
||||
|
||||
return text, nil
|
||||
}
|
||||
|
||||
// ToPlainTextSummary format summary for plain text.
|
||||
func ToPlainTextSummary(r models.ScanResult) string {
|
||||
stable := uitable.New()
|
||||
stable.MaxColWidth = 84
|
||||
stable.Wrap = true
|
||||
cves := append(r.KnownCves, r.UnknownCves...)
|
||||
for _, d := range cves {
|
||||
var scols []string
|
||||
|
||||
switch {
|
||||
case config.Conf.Lang == "ja" &&
|
||||
d.CveDetail.Jvn.ID != 0 &&
|
||||
0 < d.CveDetail.CvssScore("ja"):
|
||||
|
||||
summary := d.CveDetail.Jvn.Title
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f (%s)",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
d.CveDetail.Jvn.Severity,
|
||||
),
|
||||
summary,
|
||||
}
|
||||
case 0 < d.CveDetail.CvssScore("en"):
|
||||
summary := d.CveDetail.Nvd.Summary
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
fmt.Sprintf("%-4.1f",
|
||||
d.CveDetail.CvssScore(config.Conf.Lang),
|
||||
),
|
||||
summary,
|
||||
}
|
||||
default:
|
||||
scols = []string{
|
||||
d.CveDetail.CveID,
|
||||
"?",
|
||||
d.CveDetail.Nvd.Summary,
|
||||
}
|
||||
}
|
||||
|
||||
cols := make([]interface{}, len(scols))
|
||||
for i := range cols {
|
||||
cols[i] = scols[i]
|
||||
}
|
||||
stable.AddRow(cols...)
|
||||
}
|
||||
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 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
case "ja":
|
||||
if cve.CveDetail.Jvn.ID != 0 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangJa(cve, osFamily))
|
||||
} else if cve.CveDetail.Nvd.ID != 0 {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextDetailsLangEn(cve, osFamily))
|
||||
} else {
|
||||
scoredReport = append(
|
||||
scoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, cve := range data.UnknownCves {
|
||||
unscoredReport = append(
|
||||
unscoredReport, toPlainTextUnknownCve(cve, osFamily))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func toPlainTextUnknownCve(cveInfo models.CveInfo, osFamily string) string {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
dtable := uitable.New()
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
dtable.AddRow("Score", "?")
|
||||
dtable.AddRow("NVD",
|
||||
fmt.Sprintf("%s?vulnId=%s", nvdBaseURL, cveID))
|
||||
dtable.AddRow("CVE Details",
|
||||
fmt.Sprintf("%s/%s", cveDetailsBaseURL, cveID))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s", dtable)
|
||||
}
|
||||
|
||||
func toPlainTextDetailsLangJa(cveInfo models.CveInfo, osFamily string) string {
|
||||
|
||||
cveDetail := cveInfo.CveDetail
|
||||
cveID := cveDetail.CveID
|
||||
jvn := cveDetail.Jvn
|
||||
|
||||
dtable := uitable.New()
|
||||
//TODO resize
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
if score := cveDetail.Jvn.CvssScore(); 0 < score {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Jvn.CvssScore(),
|
||||
jvn.Severity,
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
dtable.AddRow("Vector", jvn.Vector)
|
||||
dtable.AddRow("Title", jvn.Title)
|
||||
dtable.AddRow("Description", jvn.Summary)
|
||||
|
||||
dtable.AddRow("JVN", jvn.Link())
|
||||
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))
|
||||
dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("ja"))
|
||||
|
||||
dlinks := distroLinks(cveInfo, osFamily)
|
||||
for _, link := range dlinks {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
|
||||
dtable = addPackageInfos(dtable, cveInfo.Packages)
|
||||
dtable = addCpeNames(dtable, cveInfo.CpeNames)
|
||||
|
||||
return fmt.Sprintf("%s", dtable)
|
||||
}
|
||||
|
||||
func toPlainTextDetailsLangEn(d models.CveInfo, osFamily string) string {
|
||||
cveDetail := d.CveDetail
|
||||
cveID := cveDetail.CveID
|
||||
nvd := cveDetail.Nvd
|
||||
|
||||
dtable := uitable.New()
|
||||
//TODO resize
|
||||
dtable.MaxColWidth = 100
|
||||
dtable.Wrap = true
|
||||
dtable.AddRow(cveID)
|
||||
dtable.AddRow("-------------")
|
||||
|
||||
if score := cveDetail.Nvd.CvssScore(); 0 < score {
|
||||
dtable.AddRow("Score",
|
||||
fmt.Sprintf("%4.1f (%s)",
|
||||
cveDetail.Nvd.CvssScore(),
|
||||
nvd.Severity(),
|
||||
))
|
||||
} else {
|
||||
dtable.AddRow("Score", "?")
|
||||
}
|
||||
|
||||
dtable.AddRow("Vector", nvd.CvssVector())
|
||||
dtable.AddRow("Summary", nvd.Summary)
|
||||
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))
|
||||
dtable.AddRow("CVSS Claculator", cveDetail.CvssV2CalculatorLink("en"))
|
||||
|
||||
links := distroLinks(d, osFamily)
|
||||
for _, link := range links {
|
||||
dtable.AddRow(link.title, link.url)
|
||||
}
|
||||
dtable = addPackageInfos(dtable, d.Packages)
|
||||
dtable = addCpeNames(dtable, d.CpeNames)
|
||||
|
||||
return fmt.Sprintf("%s\n", dtable)
|
||||
}
|
||||
|
||||
type distroLink struct {
|
||||
title string
|
||||
url string
|
||||
}
|
||||
|
||||
// addVendorSite add Vendor site of the CVE to table
|
||||
func distroLinks(cveInfo models.CveInfo, osFamily string) []distroLink {
|
||||
cveID := cveInfo.CveDetail.CveID
|
||||
switch osFamily {
|
||||
case "rhel", "centos":
|
||||
links := []distroLink{
|
||||
{
|
||||
"RHEL-CVE",
|
||||
fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
|
||||
},
|
||||
}
|
||||
for _, advisory := range cveInfo.DistroAdvisories {
|
||||
aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
|
||||
links = append(links, distroLink{
|
||||
// "RHEL-errata",
|
||||
advisory.AdvisoryID,
|
||||
fmt.Sprintf(redhatRHSABaseBaseURL, aidURL),
|
||||
})
|
||||
}
|
||||
return links
|
||||
case "amazon":
|
||||
links := []distroLink{
|
||||
{
|
||||
"RHEL-CVE",
|
||||
fmt.Sprintf("%s/%s", redhatSecurityBaseURL, cveID),
|
||||
},
|
||||
}
|
||||
for _, advisory := range cveInfo.DistroAdvisories {
|
||||
links = append(links, distroLink{
|
||||
// "Amazon-ALAS",
|
||||
advisory.AdvisoryID,
|
||||
fmt.Sprintf(amazonSecurityBaseURL, advisory.AdvisoryID),
|
||||
})
|
||||
}
|
||||
return links
|
||||
case "ubuntu":
|
||||
return []distroLink{
|
||||
{
|
||||
"Ubuntu-CVE",
|
||||
fmt.Sprintf("%s/%s", ubuntuSecurityBaseURL, cveID),
|
||||
},
|
||||
//TODO Ubuntu USN
|
||||
}
|
||||
case "debian":
|
||||
return []distroLink{
|
||||
{
|
||||
"Debian-CVE",
|
||||
fmt.Sprintf("%s/%s", debianTrackerBaseURL, cveID),
|
||||
},
|
||||
// TODO Debian dsa
|
||||
}
|
||||
default:
|
||||
return []distroLink{}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO
|
||||
// addPackageInfos add package information related the CVE to table
|
||||
func addPackageInfos(table *uitable.Table, packs []models.PackageInfo) *uitable.Table {
|
||||
for i, p := range packs {
|
||||
var title string
|
||||
if i == 0 {
|
||||
title = "Package/CPE"
|
||||
}
|
||||
ver := fmt.Sprintf(
|
||||
"%s -> %s", p.ToStringCurrentVersion(), p.ToStringNewVersion())
|
||||
table.AddRow(title, ver)
|
||||
}
|
||||
return table
|
||||
}
|
||||
|
||||
func addCpeNames(table *uitable.Table, names []models.CpeName) *uitable.Table {
|
||||
for _, p := range names {
|
||||
table.AddRow("CPE", fmt.Sprintf("%s", p.Name))
|
||||
}
|
||||
return table
|
||||
}
|
||||
39
report/writer.go
Normal file
39
report/writer.go
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Vuls - Vulnerability Scanner
|
||||
Copyright (C) 2016 Future Architect, Inc. Japan.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package report
|
||||
|
||||
import "github.com/future-architect/vuls/models"
|
||||
|
||||
const (
|
||||
nvdBaseURL = "https://web.nvd.nist.gov/view/vuln/detail"
|
||||
mitreBaseURL = "https://cve.mitre.org/cgi-bin/cvename.cgi?name="
|
||||
cveDetailsBaseURL = "http://www.cvedetails.com/cve"
|
||||
cvssV2CalcURLTemplate = "https://nvd.nist.gov/cvss/v2-calculator?name=%s&vector=%s"
|
||||
|
||||
redhatSecurityBaseURL = "https://access.redhat.com/security/cve"
|
||||
redhatRHSABaseBaseURL = "https://rhn.redhat.com/errata/%s.html"
|
||||
amazonSecurityBaseURL = "https://alas.aws.amazon.com/%s.html"
|
||||
|
||||
ubuntuSecurityBaseURL = "http://people.ubuntu.com/~ubuntu-security/cve"
|
||||
debianTrackerBaseURL = "https://security-tracker.debian.org/tracker"
|
||||
)
|
||||
|
||||
// ResultWriter Interface
|
||||
type ResultWriter interface {
|
||||
Write([]models.ScanResult) error
|
||||
}
|
||||
Reference in New Issue
Block a user