diff --git a/cmd/scanner/main.go b/cmd/scanner/main.go index 99d4b41b..648a8cf1 100644 --- a/cmd/scanner/main.go +++ b/cmd/scanner/main.go @@ -20,6 +20,7 @@ func main() { 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") diff --git a/contrib/future-vuls/cmd/main.go b/contrib/future-vuls/cmd/main.go index 52fe857d..e7ad9e0f 100644 --- a/contrib/future-vuls/cmd/main.go +++ b/contrib/future-vuls/cmd/main.go @@ -10,7 +10,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" - "github.com/future-architect/vuls/report" + "github.com/future-architect/vuls/saas" "github.com/spf13/cobra" ) @@ -73,7 +73,7 @@ func main() { config.Conf.Saas.GroupID = groupID config.Conf.Saas.Token = token config.Conf.Saas.URL = url - if err = (report.SaasWriter{}).Write(scanResult); err != nil { + if err = (saas.Writer{}).Write(scanResult); err != nil { fmt.Println(err) os.Exit(1) return diff --git a/report/report.go b/report/report.go index 8bcb6f89..dcd4a588 100644 --- a/report/report.go +++ b/report/report.go @@ -3,19 +3,12 @@ package report import ( - "bytes" - "fmt" - "io/ioutil" "os" - "reflect" - "regexp" - "sort" "strings" "time" "github.com/future-architect/vuls/libmanager" - "github.com/BurntSushi/toml" "github.com/future-architect/vuls/config" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" @@ -28,7 +21,6 @@ import ( "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/util" "github.com/future-architect/vuls/wordpress" - "github.com/hashicorp/go-uuid" gostdb "github.com/knqyf263/gost/db" cvedb "github.com/kotakanbe/go-cve-dictionary/db" cvemodels "github.com/kotakanbe/go-cve-dictionary/models" @@ -534,269 +526,3 @@ func fillCweDict(r *models.ScanResult) { r.CweDict = dict return } - -const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}" - -// Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated, -// so check it. Otherwise create a UUID of the Container Host and set it. -func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) { - if id, ok := server.UUIDs[r.ServerName]; !ok { - if serverUUID, err = uuid.GenerateUUID(); err != nil { - return "", xerrors.Errorf("Failed to generate UUID: %w", err) - } - } else { - matched, err := regexp.MatchString(reUUID, id) - if !matched || err != nil { - if serverUUID, err = uuid.GenerateUUID(); err != nil { - return "", xerrors.Errorf("Failed to generate UUID: %w", err) - } - } - } - return serverUUID, nil -} - -// EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet. -// And then set the generated UUID to config.toml and scan results. -func EnsureUUIDs(configPath string, results models.ScanResults) (err error) { - // Sort Host->Container - sort.Slice(results, func(i, j int) bool { - if results[i].ServerName == results[j].ServerName { - return results[i].Container.ContainerID < results[j].Container.ContainerID - } - return results[i].ServerName < results[j].ServerName - }) - - re := regexp.MustCompile(reUUID) - for i, r := range results { - server := c.Conf.Servers[r.ServerName] - if server.UUIDs == nil { - server.UUIDs = map[string]string{} - } - - name := "" - if r.IsContainer() { - name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName) - serverUUID, err := getOrCreateServerUUID(r, server) - if err != nil { - return err - } - if serverUUID != "" { - server.UUIDs[r.ServerName] = serverUUID - } - } else { - name = r.ServerName - } - - if id, ok := server.UUIDs[name]; ok { - ok := re.MatchString(id) - if !ok || err != nil { - util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err) - } else { - if r.IsContainer() { - results[i].Container.UUID = id - results[i].ServerUUID = server.UUIDs[r.ServerName] - } else { - results[i].ServerUUID = id - } - // continue if the UUID has already assigned and valid - continue - } - } - - // Generate a new UUID and set to config and scan result - serverUUID, err := uuid.GenerateUUID() - if err != nil { - return err - } - server.UUIDs[name] = serverUUID - server = cleanForTOMLEncoding(server, c.Conf.Default) - c.Conf.Servers[r.ServerName] = server - - if r.IsContainer() { - results[i].Container.UUID = serverUUID - results[i].ServerUUID = server.UUIDs[r.ServerName] - } else { - results[i].ServerUUID = serverUUID - } - } - - for name, server := range c.Conf.Servers { - server = cleanForTOMLEncoding(server, c.Conf.Default) - c.Conf.Servers[name] = server - } - - email := &c.Conf.EMail - if email.SMTPAddr == "" { - email = nil - } - - slack := &c.Conf.Slack - if slack.HookURL == "" { - slack = nil - } - - cveDict := &c.Conf.CveDict - ovalDict := &c.Conf.OvalDict - gost := &c.Conf.Gost - exploit := &c.Conf.Exploit - metasploit := &c.Conf.Metasploit - http := &c.Conf.HTTP - if http.URL == "" { - http = nil - } - - syslog := &c.Conf.Syslog - if syslog.Host == "" { - syslog = nil - } - - aws := &c.Conf.AWS - if aws.S3Bucket == "" { - aws = nil - } - - azure := &c.Conf.Azure - if azure.AccountName == "" { - azure = nil - } - - stride := &c.Conf.Stride - if stride.HookURL == "" { - stride = nil - } - - hipChat := &c.Conf.HipChat - if hipChat.AuthToken == "" { - hipChat = nil - } - - chatWork := &c.Conf.ChatWork - if chatWork.APIToken == "" { - chatWork = nil - } - - saas := &c.Conf.Saas - if saas.GroupID == 0 { - saas = nil - } - - c := struct { - CveDict *c.GoCveDictConf `toml:"cveDict"` - OvalDict *c.GovalDictConf `toml:"ovalDict"` - Gost *c.GostConf `toml:"gost"` - Exploit *c.ExploitConf `toml:"exploit"` - Metasploit *c.MetasploitConf `toml:"metasploit"` - Slack *c.SlackConf `toml:"slack"` - Email *c.SMTPConf `toml:"email"` - HTTP *c.HTTPConf `toml:"http"` - Syslog *c.SyslogConf `toml:"syslog"` - AWS *c.AWS `toml:"aws"` - Azure *c.Azure `toml:"azure"` - Stride *c.StrideConf `toml:"stride"` - HipChat *c.HipChatConf `toml:"hipChat"` - ChatWork *c.ChatWorkConf `toml:"chatWork"` - Saas *c.SaasConf `toml:"saas"` - - Default c.ServerInfo `toml:"default"` - Servers map[string]c.ServerInfo `toml:"servers"` - }{ - CveDict: cveDict, - OvalDict: ovalDict, - Gost: gost, - Exploit: exploit, - Metasploit: metasploit, - Slack: slack, - Email: email, - HTTP: http, - Syslog: syslog, - AWS: aws, - Azure: azure, - Stride: stride, - HipChat: hipChat, - ChatWork: chatWork, - Saas: saas, - - Default: c.Conf.Default, - Servers: c.Conf.Servers, - } - - // rename the current config.toml to config.toml.bak - info, err := os.Lstat(configPath) - if err != nil { - return xerrors.Errorf("Failed to lstat %s: %w", configPath, err) - } - realPath := configPath - if info.Mode()&os.ModeSymlink == os.ModeSymlink { - if realPath, err = os.Readlink(configPath); err != nil { - return xerrors.Errorf("Failed to Read link %s: %w", configPath, err) - } - } - if err := os.Rename(realPath, realPath+".bak"); err != nil { - return xerrors.Errorf("Failed to rename %s: %w", configPath, err) - } - - var buf bytes.Buffer - if err := toml.NewEncoder(&buf).Encode(c); err != nil { - return xerrors.Errorf("Failed to encode to toml: %w", err) - } - str := strings.Replace(buf.String(), "\n [", "\n\n [", -1) - str = fmt.Sprintf("%s\n\n%s", - "# See README for details: https://vuls.io/docs/en/usage-settings.html", - str) - - return ioutil.WriteFile(realPath, []byte(str), 0600) -} - -func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo { - if reflect.DeepEqual(server.Optional, def.Optional) { - server.Optional = nil - } - - if def.User == server.User { - server.User = "" - } - - if def.Host == server.Host { - server.Host = "" - } - - if def.Port == server.Port { - server.Port = "" - } - - if def.KeyPath == server.KeyPath { - server.KeyPath = "" - } - - if reflect.DeepEqual(server.ScanMode, def.ScanMode) { - server.ScanMode = nil - } - - if def.Type == server.Type { - server.Type = "" - } - - if reflect.DeepEqual(server.CpeNames, def.CpeNames) { - server.CpeNames = nil - } - - if def.OwaspDCXMLPath == server.OwaspDCXMLPath { - server.OwaspDCXMLPath = "" - } - - if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) { - server.IgnoreCves = nil - } - - if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) { - server.Enablerepo = nil - } - - for k, v := range def.Optional { - if vv, ok := server.Optional[k]; ok && v == vv { - delete(server.Optional, k) - } - } - - return server -} diff --git a/report/saas.go b/saas/saas.go similarity index 88% rename from report/saas.go rename to saas/saas.go index f48503d4..eb46cca1 100644 --- a/report/saas.go +++ b/saas/saas.go @@ -1,16 +1,14 @@ -package report +package saas import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" "net/url" "os" "path" "strings" - "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -23,8 +21,8 @@ import ( "golang.org/x/xerrors" ) -// SaasWriter writes results to SaaS -type SaasWriter struct{} +// Writer writes results to SaaS +type Writer struct{} // TempCredential : TempCredential type TempCredential struct { @@ -42,7 +40,7 @@ type payload struct { } // UploadSaas : UploadSaas -func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { +func (w Writer) Write(rs ...models.ScanResult) (err error) { // dir string, configPath string, config *c.Config if len(rs) == 0 { return nil @@ -142,11 +140,3 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { util.Log.Infof("done") return nil } - -func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string { - timestr := scannedAt.UTC().Format(time.RFC3339) - if len(container.ContainerID) == 0 { - return fmt.Sprintf("%s/%s.json", timestr, uuid) - } - return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid) -} diff --git a/saas/uuid.go b/saas/uuid.go new file mode 100644 index 00000000..6bf67d36 --- /dev/null +++ b/saas/uuid.go @@ -0,0 +1,294 @@ +package saas + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "reflect" + "regexp" + "sort" + "strings" + "time" + + "github.com/BurntSushi/toml" + c "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/util" + "github.com/hashicorp/go-uuid" + "golang.org/x/xerrors" +) + +func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string { + timestr := scannedAt.UTC().Format(time.RFC3339) + if len(container.ContainerID) == 0 { + return fmt.Sprintf("%s/%s.json", timestr, uuid) + } + return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid) +} + +const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}" + +// Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated, +// so check it. Otherwise create a UUID of the Container Host and set it. +func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) { + if id, ok := server.UUIDs[r.ServerName]; !ok { + if serverUUID, err = uuid.GenerateUUID(); err != nil { + return "", xerrors.Errorf("Failed to generate UUID: %w", err) + } + } else { + matched, err := regexp.MatchString(reUUID, id) + if !matched || err != nil { + if serverUUID, err = uuid.GenerateUUID(); err != nil { + return "", xerrors.Errorf("Failed to generate UUID: %w", err) + } + } + } + return serverUUID, nil +} + +// EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet. +// And then set the generated UUID to config.toml and scan results. +func EnsureUUIDs(configPath string, results models.ScanResults) (err error) { + // Sort Host->Container + sort.Slice(results, func(i, j int) bool { + if results[i].ServerName == results[j].ServerName { + return results[i].Container.ContainerID < results[j].Container.ContainerID + } + return results[i].ServerName < results[j].ServerName + }) + + re := regexp.MustCompile(reUUID) + for i, r := range results { + server := c.Conf.Servers[r.ServerName] + if server.UUIDs == nil { + server.UUIDs = map[string]string{} + } + + name := "" + if r.IsContainer() { + name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName) + serverUUID, err := getOrCreateServerUUID(r, server) + if err != nil { + return err + } + if serverUUID != "" { + server.UUIDs[r.ServerName] = serverUUID + } + } else { + name = r.ServerName + } + + if id, ok := server.UUIDs[name]; ok { + ok := re.MatchString(id) + if !ok || err != nil { + util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err) + } else { + if r.IsContainer() { + results[i].Container.UUID = id + results[i].ServerUUID = server.UUIDs[r.ServerName] + } else { + results[i].ServerUUID = id + } + // continue if the UUID has already assigned and valid + continue + } + } + + // Generate a new UUID and set to config and scan result + serverUUID, err := uuid.GenerateUUID() + if err != nil { + return err + } + server.UUIDs[name] = serverUUID + server = cleanForTOMLEncoding(server, c.Conf.Default) + c.Conf.Servers[r.ServerName] = server + + if r.IsContainer() { + results[i].Container.UUID = serverUUID + results[i].ServerUUID = server.UUIDs[r.ServerName] + } else { + results[i].ServerUUID = serverUUID + } + } + + for name, server := range c.Conf.Servers { + server = cleanForTOMLEncoding(server, c.Conf.Default) + c.Conf.Servers[name] = server + } + + email := &c.Conf.EMail + if email.SMTPAddr == "" { + email = nil + } + + slack := &c.Conf.Slack + if slack.HookURL == "" { + slack = nil + } + + cveDict := &c.Conf.CveDict + ovalDict := &c.Conf.OvalDict + gost := &c.Conf.Gost + exploit := &c.Conf.Exploit + metasploit := &c.Conf.Metasploit + http := &c.Conf.HTTP + if http.URL == "" { + http = nil + } + + syslog := &c.Conf.Syslog + if syslog.Host == "" { + syslog = nil + } + + aws := &c.Conf.AWS + if aws.S3Bucket == "" { + aws = nil + } + + azure := &c.Conf.Azure + if azure.AccountName == "" { + azure = nil + } + + stride := &c.Conf.Stride + if stride.HookURL == "" { + stride = nil + } + + hipChat := &c.Conf.HipChat + if hipChat.AuthToken == "" { + hipChat = nil + } + + chatWork := &c.Conf.ChatWork + if chatWork.APIToken == "" { + chatWork = nil + } + + saas := &c.Conf.Saas + if saas.GroupID == 0 { + saas = nil + } + + c := struct { + CveDict *c.GoCveDictConf `toml:"cveDict"` + OvalDict *c.GovalDictConf `toml:"ovalDict"` + Gost *c.GostConf `toml:"gost"` + Exploit *c.ExploitConf `toml:"exploit"` + Metasploit *c.MetasploitConf `toml:"metasploit"` + Slack *c.SlackConf `toml:"slack"` + Email *c.SMTPConf `toml:"email"` + HTTP *c.HTTPConf `toml:"http"` + Syslog *c.SyslogConf `toml:"syslog"` + AWS *c.AWS `toml:"aws"` + Azure *c.Azure `toml:"azure"` + Stride *c.StrideConf `toml:"stride"` + HipChat *c.HipChatConf `toml:"hipChat"` + ChatWork *c.ChatWorkConf `toml:"chatWork"` + Saas *c.SaasConf `toml:"saas"` + + Default c.ServerInfo `toml:"default"` + Servers map[string]c.ServerInfo `toml:"servers"` + }{ + CveDict: cveDict, + OvalDict: ovalDict, + Gost: gost, + Exploit: exploit, + Metasploit: metasploit, + Slack: slack, + Email: email, + HTTP: http, + Syslog: syslog, + AWS: aws, + Azure: azure, + Stride: stride, + HipChat: hipChat, + ChatWork: chatWork, + Saas: saas, + + Default: c.Conf.Default, + Servers: c.Conf.Servers, + } + + // rename the current config.toml to config.toml.bak + info, err := os.Lstat(configPath) + if err != nil { + return xerrors.Errorf("Failed to lstat %s: %w", configPath, err) + } + realPath := configPath + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + if realPath, err = os.Readlink(configPath); err != nil { + return xerrors.Errorf("Failed to Read link %s: %w", configPath, err) + } + } + if err := os.Rename(realPath, realPath+".bak"); err != nil { + return xerrors.Errorf("Failed to rename %s: %w", configPath, err) + } + + var buf bytes.Buffer + if err := toml.NewEncoder(&buf).Encode(c); err != nil { + return xerrors.Errorf("Failed to encode to toml: %w", err) + } + str := strings.Replace(buf.String(), "\n [", "\n\n [", -1) + str = fmt.Sprintf("%s\n\n%s", + "# See README for details: https://vuls.io/docs/en/usage-settings.html", + str) + + return ioutil.WriteFile(realPath, []byte(str), 0600) +} + +func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo { + if reflect.DeepEqual(server.Optional, def.Optional) { + server.Optional = nil + } + + if def.User == server.User { + server.User = "" + } + + if def.Host == server.Host { + server.Host = "" + } + + if def.Port == server.Port { + server.Port = "" + } + + if def.KeyPath == server.KeyPath { + server.KeyPath = "" + } + + if reflect.DeepEqual(server.ScanMode, def.ScanMode) { + server.ScanMode = nil + } + + if def.Type == server.Type { + server.Type = "" + } + + if reflect.DeepEqual(server.CpeNames, def.CpeNames) { + server.CpeNames = nil + } + + if def.OwaspDCXMLPath == server.OwaspDCXMLPath { + server.OwaspDCXMLPath = "" + } + + if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) { + server.IgnoreCves = nil + } + + if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) { + server.Enablerepo = nil + } + + for k, v := range def.Optional { + if vv, ok := server.Optional[k]; ok && v == vv { + delete(server.Optional, k) + } + } + + return server +} diff --git a/report/report_test.go b/saas/uuid_test.go similarity index 98% rename from report/report_test.go rename to saas/uuid_test.go index 5a58556a..75fd4286 100644 --- a/report/report_test.go +++ b/saas/uuid_test.go @@ -1,10 +1,9 @@ -package report +package saas import ( "testing" "github.com/future-architect/vuls/config" - "github.com/future-architect/vuls/models" ) diff --git a/subcmds/report.go b/subcmds/report.go index 2fec8a65..7fd4de72 100644 --- a/subcmds/report.go +++ b/subcmds/report.go @@ -63,7 +63,6 @@ func (*ReportCmd) Usage() string { [-to-localfile] [-to-s3] [-to-azure-blob] - [-to-saas] [-format-json] [-format-xml] [-format-one-email] @@ -167,12 +166,8 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) { 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", "", @@ -277,88 +272,78 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} 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) + 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.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 - } - } - - if c.Conf.Metasploit.URL != "" { - err := msf.CheckHTTPHealth() - if err != nil { - util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err) - util.Log.Errorf("Run go-msfdb 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, - MetasploitCnf: c.Conf.Metasploit, - 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 c.Conf.OvalDict.URL != "" { + err := oval.Base{}.CheckHTTPHealth() if err != nil { - util.Log.Errorf("Failed to init DB Clients. err: %+v", err) + 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 } - defer dbclient.CloseDB() + } - if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil { - util.Log.Errorf("%+v", err) + 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 + } + } + + if c.Conf.Metasploit.URL != "" { + err := msf.CheckHTTPHealth() + if err != nil { + util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err) + util.Log.Errorf("Run go-msfdb 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, + MetasploitCnf: c.Conf.Metasploit, + 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 + } + // report reports := []report.ResultWriter{ report.StdoutWriter{}, @@ -432,14 +417,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{} 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{}) - } - for _, w := range reports { if err := w.Write(res...); err != nil { util.Log.Errorf("Failed to report. err: %+v", err) diff --git a/subcmds/saas.go b/subcmds/saas.go new file mode 100644 index 00000000..35f42485 --- /dev/null +++ b/subcmds/saas.go @@ -0,0 +1,132 @@ +package subcmds + +import ( + "context" + "flag" + "os" + "path/filepath" + + c "github.com/future-architect/vuls/config" + "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/report" + "github.com/future-architect/vuls/saas" + "github.com/future-architect/vuls/util" + "github.com/google/subcommands" + "github.com/k0kubun/pp" +) + +// SaaSCmd is subcommand for FutureVuls +type SaaSCmd struct { + configPath string +} + +// Name return subcommand name +func (*SaaSCmd) Name() string { return "saas" } + +// Synopsis return synopsis +func (*SaaSCmd) Synopsis() string { return "upload to FutureVuls" } + +// Usage return usage +func (*SaaSCmd) Usage() string { + return `saas: + saas + [-config=/path/to/config.toml] + [-results-dir=/path/to/results] + [-log-dir=/path/to/log] + [-http-proxy=http://192.168.0.1:8080] + [-debug] + [-debug-sql] + [-quiet] + [-no-progress] +` +} + +// SetFlags set flag +func (p *SaaSCmd) 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") + f.BoolVar(&c.Conf.NoProgress, "no-progress", false, "Suppress progress bar") + + 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.StringVar( + &c.Conf.HTTPProxy, "http-proxy", "", + "http://proxy-url:port (default: empty)") +} + +// Execute execute +func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus { + util.Log = util.NewCustomLogger(c.ServerInfo{}) + if err := c.Load(p.configPath, ""); err != nil { + util.Log.Errorf("Error loading %s, %+v", p.configPath, err) + return subcommands.ExitUsageError + } + + dir, err := report.JSONDir(f.Args()) + if err != nil { + util.Log.Errorf("Failed to read from JSON: %+v", err) + return subcommands.ExitFailure + } + + 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])) + } + + // Ensure UUIDs of scan target servers in config.toml + if err := saas.EnsureUUIDs(p.configPath, res); err != nil { + util.Log.Errorf("Failed to ensure UUIDs. err: %+v", err) + return subcommands.ExitFailure + } + + var w report.ResultWriter = saas.Writer{} + if err := w.Write(res...); err != nil { + util.Log.Errorf("Failed to upload. err: %+v", err) + return subcommands.ExitFailure + } + + if hasError { + return subcommands.ExitFailure + } + + return subcommands.ExitSuccess +}