281 lines
6.9 KiB
Go
281 lines
6.9 KiB
Go
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
|
|
}
|
|
|
|
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"`
|
|
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,
|
|
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
|
|
}
|