diff --git a/Gopkg.lock b/Gopkg.lock
index 7fd151c8..a3e0571b 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -15,8 +15,8 @@
"autorest/azure",
"autorest/date"
]
- revision = "c2a68353555b68de3ee8455a4fd3e890a0ac6d99"
- version = "v9.8.1"
+ revision = "fc3b03a2d2d1f43fad3007038bd16f044f870722"
+ version = "v9.10.0"
[[projects]]
name = "github.com/BurntSushi/toml"
@@ -49,6 +49,7 @@
"aws/request",
"aws/session",
"aws/signer/v4",
+ "internal/sdkrand",
"internal/shareddefaults",
"private/protocol",
"private/protocol/query",
@@ -59,8 +60,8 @@
"service/s3",
"service/sts"
]
- revision = "decd990ddc5dcdf2f73309cbcab90d06b996ca28"
- version = "v1.12.67"
+ revision = "adeb60566bc8c9202b0f1be7e3a675beedbc11f0"
+ version = "v1.13.2"
[[projects]]
name = "github.com/boltdb/bolt"
@@ -75,10 +76,10 @@
version = "v1.1.0"
[[projects]]
+ branch = "master"
name = "github.com/cheggaaa/pb"
packages = ["."]
- revision = "5119518d88fbf604cab04dafd667dc7022e3b5ac"
- version = "v1.0.21"
+ revision = "521e54ab5f0d0e5260964d094a414759c65fcbb3"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
@@ -100,10 +101,11 @@
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
- "internal/proto"
+ "internal/proto",
+ "internal/singleflight"
]
- revision = "4021ace05686f632ff17fd824bbed229fc474cf8"
- version = "v6.8.2"
+ revision = "fa7f64f7f27348658ecfa71dd71dfb2d112e2f86"
+ version = "v6.9.0"
[[projects]]
name = "github.com/go-sql-driver/mysql"
@@ -191,7 +193,7 @@
"nvd",
"util"
]
- revision = "c640d74072007e0b3b86968388c12c54acd8f97e"
+ revision = "fde71467f9c6a941490af81dd1d34e8bff0aba01"
[[projects]]
name = "github.com/kotakanbe/go-pingscanner"
@@ -209,7 +211,7 @@
"log",
"models"
]
- revision = "eda9803e0e770516046db375c44b558de883856c"
+ revision = "85c10368a38d0c020d76b8bd93155f00324dcb08"
[[projects]]
branch = "master"
@@ -225,7 +227,7 @@
"hstore",
"oid"
]
- revision = "61fe37aa2ee24fabcdbe5c4ac1d4ac566f88f345"
+ revision = "88edab0803230a3898347e77b474f8c1820a1f20"
[[projects]]
name = "github.com/marstr/guid"
@@ -278,7 +280,7 @@
branch = "master"
name = "github.com/nsf/termbox-go"
packages = ["."]
- revision = "157ff97de075fe3e9e54bda782b8ca6b552dfff7"
+ revision = "88b7b944be8bc8d8ec6195fca97c5869ba20f99d"
[[projects]]
name = "github.com/parnurzeal/gorequest"
@@ -305,9 +307,10 @@
version = "v1.2.0"
[[projects]]
+ branch = "master"
name = "github.com/sirupsen/logrus"
packages = ["."]
- revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
+ revision = "8c0189d9f6bbf301e5d055d34268156b317016af"
[[projects]]
branch = "master"
@@ -328,7 +331,7 @@
"ssh/agent",
"ssh/terminal"
]
- revision = "3d37316aaa6bd9929127ac9a527abf408178ea7b"
+ revision = "432090b8f568c018896cd8a0fb0345872bbac6ce"
[[projects]]
branch = "master"
@@ -339,7 +342,7 @@
"publicsuffix",
"websocket"
]
- revision = "5ccada7d0a7ba9aeb5d3aca8d3501b4c2a509fec"
+ revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb"
[[projects]]
branch = "master"
@@ -348,10 +351,9 @@
"unix",
"windows"
]
- revision = "af50095a40f9041b3b38960738837185c26e9419"
+ revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd"
[[projects]]
- branch = "master"
name = "golang.org/x/text"
packages = [
"collate",
@@ -369,11 +371,12 @@
"unicode/norm",
"unicode/rangetable"
]
- revision = "e19ae1496984b1c655b8044a65c0300a3c878dd3"
+ revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
+ version = "v0.3.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
- inputs-digest = "bfe830562c6df05e01e16b1337e1e4d97844ae0235cfda39c55eee38ba838f68"
+ inputs-digest = "1a43293a9ffc91270316199bec5474167594f96a3612f899842532c0f224d694"
solver-name = "gps-cdcl"
solver-version = 1
diff --git a/Gopkg.toml b/Gopkg.toml
index 2eb487ef..afc118fc 100644
--- a/Gopkg.toml
+++ b/Gopkg.toml
@@ -101,8 +101,8 @@
version = "2.2.0"
[[constraint]]
+ branch = "master"
name = "github.com/sirupsen/logrus"
- version = "1.0.4"
[[constraint]]
branch = "master"
diff --git a/commands/report.go b/commands/report.go
index d18698c5..3a4b4a7e 100644
--- a/commands/report.go
+++ b/commands/report.go
@@ -58,6 +58,7 @@ type ReportCmd struct {
toSlack bool
toEMail bool
+ toSyslog bool
toLocalFile bool
toS3 bool
toAzureBlob bool
@@ -264,6 +265,7 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
f.BoolVar(&p.toEMail, "to-email", false, "Send report via Email")
+ f.BoolVar(&p.toSyslog, "to-syslog", false, "Send report via Syslog")
f.BoolVar(&p.toLocalFile,
"to-localfile",
false,
@@ -363,6 +365,10 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
reports = append(reports, report.EMailWriter{})
}
+ if p.toSyslog {
+ reports = append(reports, report.SyslogWriter{})
+ }
+
if p.toLocalFile {
reports = append(reports, report.LocalFileWriter{
CurrentDir: dir,
diff --git a/config/config.go b/config/config.go
index 30255f2b..76c0e38e 100644
--- a/config/config.go
+++ b/config/config.go
@@ -18,7 +18,9 @@ along with this program. If not, see .
package config
import (
+ "errors"
"fmt"
+ "log/syslog"
"os"
"runtime"
"strconv"
@@ -94,6 +96,7 @@ type Config struct {
EMail SMTPConf
Slack SlackConf
+ Syslog SyslogConf
Default ServerInfo
Servers map[string]ServerInfo
@@ -260,6 +263,10 @@ func (c Config) ValidateOnReport() bool {
errs = append(errs, slackerrs...)
}
+ if syslogerrs := c.Syslog.Validate(); 0 < len(syslogerrs) {
+ errs = append(errs, syslogerrs...)
+ }
+
for _, err := range errs {
log.Error(err)
}
@@ -444,6 +451,124 @@ func (c *SlackConf) Validate() (errs []error) {
return
}
+// SyslogConf is syslog config
+type SyslogConf struct {
+ Protocol string
+ Host string `valid:"host"`
+ Port string `valid:"port"`
+ Severity string
+ Facility string
+ Tag string
+
+ Verbose bool
+}
+
+// Validate validates configuration
+func (c *SyslogConf) Validate() (errs []error) {
+ // 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(`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 := valid.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, fmt.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, fmt.Errorf("Invalid facility: %s", c.Facility)
+ }
+}
+
// ServerInfo has SSH Info, additional CPE packages to scan.
type ServerInfo struct {
ServerName string
diff --git a/config/config_test.go b/config/config_test.go
new file mode 100644
index 00000000..6c2c517b
--- /dev/null
+++ b/config/config_test.go
@@ -0,0 +1,63 @@
+package config
+
+import (
+ "testing"
+)
+
+func TestSyslogConfValidate(t *testing.T) {
+ var tests = []struct {
+ conf SyslogConf
+ expectedErrLength int
+ }{
+ {
+ conf: SyslogConf{},
+ expectedErrLength: 0,
+ },
+ {
+ conf: SyslogConf{
+ Protocol: "tcp",
+ Port: "5140",
+ },
+ expectedErrLength: 0,
+ },
+ {
+ conf: SyslogConf{
+ Protocol: "udp",
+ Port: "12345",
+ Severity: "emerg",
+ Facility: "user",
+ },
+ expectedErrLength: 0,
+ },
+ {
+ conf: SyslogConf{
+ Protocol: "foo",
+ Port: "514",
+ },
+ expectedErrLength: 1,
+ },
+ {
+ conf: SyslogConf{
+ Protocol: "invalid",
+ Port: "-1",
+ },
+ expectedErrLength: 2,
+ },
+ {
+ conf: SyslogConf{
+ Protocol: "invalid",
+ Port: "invalid",
+ Severity: "invalid",
+ Facility: "invalid",
+ },
+ expectedErrLength: 4,
+ },
+ }
+
+ for i, tt := range tests {
+ errs := tt.conf.Validate()
+ if len(errs) != tt.expectedErrLength {
+ t.Errorf("test: %d, expected %d, actual %d", i, tt.expectedErrLength, len(errs))
+ }
+ }
+}
diff --git a/config/tomlloader.go b/config/tomlloader.go
index 071c0f94..3ed52ce1 100644
--- a/config/tomlloader.go
+++ b/config/tomlloader.go
@@ -44,6 +44,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error {
Conf.EMail = conf.EMail
Conf.Slack = conf.Slack
+ Conf.Syslog = conf.Syslog
d := conf.Default
Conf.Default = d
diff --git a/report/syslog.go b/report/syslog.go
new file mode 100644
index 00000000..ee5bd30f
--- /dev/null
+++ b/report/syslog.go
@@ -0,0 +1,97 @@
+/* Vuls - Vulnerability Scanner
+Copyright (C) 2018 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"
+ "log/syslog"
+ "strings"
+
+ "github.com/pkg/errors"
+
+ "github.com/future-architect/vuls/config"
+ "github.com/future-architect/vuls/models"
+)
+
+// SyslogWriter send report to syslog
+type SyslogWriter struct{}
+
+func (w SyslogWriter) Write(rs ...models.ScanResult) (err error) {
+ conf := config.Conf.Syslog
+ facility, _ := conf.GetFacility()
+ severity, _ := conf.GetSeverity()
+ raddr := fmt.Sprintf("%s:%s", conf.Host, conf.Port)
+
+ sysLog, err := syslog.Dial(conf.Protocol, raddr, severity|facility, conf.Tag)
+ if err != nil {
+ return errors.Wrap(err, "Failed to initialize syslog client")
+ }
+
+ for _, r := range rs {
+ messages := w.encodeSyslog(r)
+ for _, m := range messages {
+ if _, err = fmt.Fprintf(sysLog, m); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (w SyslogWriter) encodeSyslog(result models.ScanResult) (messages []string) {
+ ipv4Addrs := strings.Join(result.IPv4Addrs, ",")
+ ipv6Addrs := strings.Join(result.IPv6Addrs, ",")
+
+ for cveID, vinfo := range result.ScannedCves {
+ var kvPairs []string
+ kvPairs = append(kvPairs, fmt.Sprintf(`server_name="%s"`, result.ServerName))
+ kvPairs = append(kvPairs, fmt.Sprintf(`os_family="%s"`, result.Family))
+ kvPairs = append(kvPairs, fmt.Sprintf(`os_release="%s"`, result.Release))
+ kvPairs = append(kvPairs, fmt.Sprintf(`ipv4_addr="%s"`, ipv4Addrs))
+ kvPairs = append(kvPairs, fmt.Sprintf(`ipv6_addr="%s"`, ipv6Addrs))
+
+ var pkgNames []string
+ for _, pkg := range vinfo.AffectedPackages {
+ pkgNames = append(pkgNames, pkg.Name)
+ }
+ pkgs := strings.Join(pkgNames, ",")
+ kvPairs = append(kvPairs, fmt.Sprintf(`packages="%s"`, pkgs))
+
+ kvPairs = append(kvPairs, fmt.Sprintf(`cve_id="%s"`, cveID))
+ for _, cvss := range vinfo.Cvss2Scores() {
+ if cvss.Type != models.NVD {
+ continue
+ }
+ kvPairs = append(kvPairs, fmt.Sprintf(`severity="%s"`, cvss.Value.Severity))
+ kvPairs = append(kvPairs, fmt.Sprintf(`cvss_score_v2="%.2f"`, cvss.Value.Score))
+ kvPairs = append(kvPairs, fmt.Sprintf(`cvss_vector_v2="%s"`, cvss.Value.Vector))
+ }
+
+ if content, ok := vinfo.CveContents[models.NVD]; ok {
+ kvPairs = append(kvPairs, fmt.Sprintf(`cwe_id="%s"`, content.CweID))
+ if config.Conf.Syslog.Verbose {
+ kvPairs = append(kvPairs, fmt.Sprintf(`source_link="%s"`, content.SourceLink))
+ kvPairs = append(kvPairs, fmt.Sprintf(`summary="%s"`, content.Summary))
+ }
+ }
+
+ // message: key1="value1" key2="value2"...
+ messages = append(messages, strings.Join(kvPairs, " "))
+ }
+ return messages
+}
diff --git a/report/syslog_test.go b/report/syslog_test.go
new file mode 100644
index 00000000..6e18417b
--- /dev/null
+++ b/report/syslog_test.go
@@ -0,0 +1,93 @@
+package report
+
+import (
+ "sort"
+ "testing"
+
+ "github.com/future-architect/vuls/models"
+)
+
+func TestSyslogWriterEncodeSyslog(t *testing.T) {
+ var tests = []struct {
+ result models.ScanResult
+ expectedMessages []string
+ }{
+ {
+ result: models.ScanResult{
+ ServerName: "teste01",
+ Family: "ubuntu",
+ Release: "16.04",
+ IPv4Addrs: []string{"192.168.0.1", "10.0.2.15"},
+ ScannedCves: models.VulnInfos{
+ "CVE-2017-0001": models.VulnInfo{
+ AffectedPackages: models.PackageStatuses{
+ models.PackageStatus{Name: "pkg1"},
+ models.PackageStatus{Name: "pkg2"},
+ },
+ },
+ "CVE-2017-0002": models.VulnInfo{
+ AffectedPackages: models.PackageStatuses{
+ models.PackageStatus{Name: "pkg3"},
+ models.PackageStatus{Name: "pkg4"},
+ },
+ CveContents: models.CveContents{
+ models.NVD: models.CveContent{
+ Cvss2Score: 5.0,
+ Cvss2Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C",
+ CweID: "CWE-20",
+ },
+ },
+ },
+ },
+ },
+ expectedMessages: []string{
+ `server_name="teste01" os_family="ubuntu" os_release="16.04" ipv4_addr="192.168.0.1,10.0.2.15" ipv6_addr="" packages="pkg1,pkg2" cve_id="CVE-2017-0001"`,
+ `server_name="teste01" os_family="ubuntu" os_release="16.04" ipv4_addr="192.168.0.1,10.0.2.15" ipv6_addr="" packages="pkg3,pkg4" cve_id="CVE-2017-0002" severity="MEDIUM" cvss_score_v2="5.00" cvss_vector_v2="AV:L/AC:L/Au:N/C:N/I:N/A:C" cwe_id="CWE-20"`,
+ },
+ },
+ {
+ result: models.ScanResult{
+ ServerName: "teste02",
+ Family: "centos",
+ Release: "6",
+ IPv6Addrs: []string{"2001:0DB8::1"},
+ ScannedCves: models.VulnInfos{
+ "CVE-2017-0003": models.VulnInfo{
+ AffectedPackages: models.PackageStatuses{
+ models.PackageStatus{Name: "pkg5"},
+ },
+ CveContents: models.CveContents{
+ models.RedHat: models.CveContent{
+ Cvss3Score: 5.0,
+ Cvss3Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C",
+ CweID: "CWE-284",
+ },
+ },
+ },
+ },
+ },
+ expectedMessages: []string{
+ `server_name="teste02" os_family="centos" os_release="6" ipv4_addr="" ipv6_addr="2001:0DB8::1" packages="pkg5" cve_id="CVE-2017-0003"`,
+ },
+ },
+ }
+
+ for i, tt := range tests {
+ messages := SyslogWriter{}.encodeSyslog(tt.result)
+ if len(messages) != len(tt.expectedMessages) {
+ t.Fatalf("test: %d, Message Length: expected %d, actual: %d",
+ i, len(tt.expectedMessages), len(messages))
+ }
+
+ sort.Slice(messages, func(i, j int) bool {
+ return messages[i] < messages[j]
+ })
+
+ for j, m := range messages {
+ e := tt.expectedMessages[j]
+ if e != m {
+ t.Errorf("test: %d, Messsage %d: expected %s, actual %s", i, j, e, m)
+ }
+ }
+ }
+}
diff --git a/scan/freebsd.go b/scan/freebsd.go
index 5446b801..cfb988de 100644
--- a/scan/freebsd.go
+++ b/scan/freebsd.go
@@ -79,7 +79,7 @@ func (o *bsd) checkDependencies() error {
}
func (o *bsd) preCure() error {
- if err := o.detectIPAddr(); err != nil{
+ if err := o.detectIPAddr(); err != nil {
o.log.Debugf("Failed to detect IP addresses: %s", err)
}
// Ignore this error as it just failed to detect the IP addresses