Solved the problem of trying to read from STDIN and stopping on the way when running from CRON or AWS Lambda.
		
			
				
	
	
		
			401 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/* 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 config
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"runtime"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	log "github.com/Sirupsen/logrus"
 | 
						|
	valid "github.com/asaskevich/govalidator"
 | 
						|
)
 | 
						|
 | 
						|
// Conf has Configuration
 | 
						|
var Conf Config
 | 
						|
 | 
						|
//Config is struct of Configuration
 | 
						|
type Config struct {
 | 
						|
	Debug    bool
 | 
						|
	DebugSQL bool
 | 
						|
	Lang     string
 | 
						|
 | 
						|
	EMail   smtpConf
 | 
						|
	Slack   SlackConf
 | 
						|
	Default ServerInfo
 | 
						|
	Servers map[string]ServerInfo
 | 
						|
 | 
						|
	CveDictionaryURL string `valid:"url"`
 | 
						|
 | 
						|
	CvssScoreOver      float64
 | 
						|
	IgnoreUnscoredCves bool
 | 
						|
 | 
						|
	AssumeYes      bool
 | 
						|
	SSHExternal    bool
 | 
						|
	ContainersOnly bool
 | 
						|
	SkipBroken     bool
 | 
						|
 | 
						|
	HTTPProxy   string `valid:"url"`
 | 
						|
	ResultsDir  string
 | 
						|
	CveDBType   string
 | 
						|
	CveDBPath   string
 | 
						|
	CacheDBPath string
 | 
						|
 | 
						|
	FormatXML         bool
 | 
						|
	FormatJSON        bool
 | 
						|
	FormatOneLineText bool
 | 
						|
	FormatShortText   bool
 | 
						|
	FormatFullText    bool
 | 
						|
 | 
						|
	GZIP bool
 | 
						|
 | 
						|
	AwsProfile string
 | 
						|
	AwsRegion  string
 | 
						|
	S3Bucket   string
 | 
						|
 | 
						|
	AzureAccount   string
 | 
						|
	AzureKey       string
 | 
						|
	AzureContainer string
 | 
						|
 | 
						|
	Pipe bool
 | 
						|
}
 | 
						|
 | 
						|
// ValidateOnConfigtest validates
 | 
						|
func (c Config) ValidateOnConfigtest() bool {
 | 
						|
	errs := []error{}
 | 
						|
 | 
						|
	if runtime.GOOS == "windows" && c.SSHExternal {
 | 
						|
		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := valid.ValidateStruct(c)
 | 
						|
	if err != nil {
 | 
						|
		errs = append(errs, err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, err := range errs {
 | 
						|
		log.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return len(errs) == 0
 | 
						|
}
 | 
						|
 | 
						|
// ValidateOnPrepare validates configuration
 | 
						|
func (c Config) ValidateOnPrepare() bool {
 | 
						|
	return c.ValidateOnConfigtest()
 | 
						|
}
 | 
						|
 | 
						|
// ValidateOnScan validates configuration
 | 
						|
func (c Config) ValidateOnScan() bool {
 | 
						|
	errs := []error{}
 | 
						|
 | 
						|
	if len(c.ResultsDir) != 0 {
 | 
						|
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if runtime.GOOS == "windows" && c.SSHExternal {
 | 
						|
		errs = append(errs, fmt.Errorf("-ssh-external cannot be used on windows"))
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.ResultsDir) != 0 {
 | 
						|
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.CacheDBPath) != 0 {
 | 
						|
		if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := valid.ValidateStruct(c)
 | 
						|
	if err != nil {
 | 
						|
		errs = append(errs, err)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, err := range errs {
 | 
						|
		log.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return len(errs) == 0
 | 
						|
}
 | 
						|
 | 
						|
// ValidateOnReport validates configuration
 | 
						|
func (c Config) ValidateOnReport() bool {
 | 
						|
	errs := []error{}
 | 
						|
 | 
						|
	if len(c.ResultsDir) != 0 {
 | 
						|
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
 | 
						|
		errs = append(errs, fmt.Errorf(
 | 
						|
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
						|
	}
 | 
						|
 | 
						|
	if c.CveDBType == "sqlite3" {
 | 
						|
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := valid.ValidateStruct(c)
 | 
						|
	if err != nil {
 | 
						|
		errs = append(errs, err)
 | 
						|
	}
 | 
						|
 | 
						|
	if mailerrs := c.EMail.Validate(); 0 < len(mailerrs) {
 | 
						|
		errs = append(errs, mailerrs...)
 | 
						|
	}
 | 
						|
 | 
						|
	if slackerrs := c.Slack.Validate(); 0 < len(slackerrs) {
 | 
						|
		errs = append(errs, slackerrs...)
 | 
						|
	}
 | 
						|
 | 
						|
	for _, err := range errs {
 | 
						|
		log.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return len(errs) == 0
 | 
						|
}
 | 
						|
 | 
						|
// ValidateOnTui validates configuration
 | 
						|
func (c Config) ValidateOnTui() bool {
 | 
						|
	errs := []error{}
 | 
						|
 | 
						|
	if len(c.ResultsDir) != 0 {
 | 
						|
		if ok, _ := valid.IsFilePath(c.ResultsDir); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if c.CveDBType != "sqlite3" && c.CveDBType != "mysql" {
 | 
						|
		errs = append(errs, fmt.Errorf(
 | 
						|
			"CVE DB type must be either 'sqlite3' or 'mysql'.  -cve-dictionary-dbtype: %s", c.CveDBType))
 | 
						|
	}
 | 
						|
 | 
						|
	if c.CveDBType == "sqlite3" {
 | 
						|
		if ok, _ := valid.IsFilePath(c.CveDBPath); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"SQLite3 DB(CVE-Dictionary) path must be a *Absolute* file path. -cve-dictionary-dbpath: %s", c.CveDBPath))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, err := range errs {
 | 
						|
		log.Error(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return len(errs) == 0
 | 
						|
}
 | 
						|
 | 
						|
// smtpConf is smtp config
 | 
						|
type smtpConf struct {
 | 
						|
	SMTPAddr string
 | 
						|
	SMTPPort string `valid:"port"`
 | 
						|
 | 
						|
	User          string
 | 
						|
	Password      string
 | 
						|
	From          string
 | 
						|
	To            []string
 | 
						|
	Cc            []string
 | 
						|
	SubjectPrefix string
 | 
						|
 | 
						|
	UseThisTime bool
 | 
						|
}
 | 
						|
 | 
						|
func checkEmails(emails []string) (errs []error) {
 | 
						|
	for _, addr := range emails {
 | 
						|
		if len(addr) == 0 {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if ok := valid.IsEmail(addr); !ok {
 | 
						|
			errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Validate SMTP configuration
 | 
						|
func (c *smtpConf) Validate() (errs []error) {
 | 
						|
 | 
						|
	if !c.UseThisTime {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Check Emails fromat
 | 
						|
	emails := []string{}
 | 
						|
	emails = append(emails, c.From)
 | 
						|
	emails = append(emails, c.To...)
 | 
						|
	emails = append(emails, c.Cc...)
 | 
						|
 | 
						|
	if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
 | 
						|
		errs = append(errs, emailErrs...)
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.SMTPAddr) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("smtpAddr must not be empty"))
 | 
						|
	}
 | 
						|
	if len(c.SMTPPort) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("smtpPort must not be empty"))
 | 
						|
	}
 | 
						|
	if len(c.To) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("To required at least one address"))
 | 
						|
	}
 | 
						|
	if len(c.From) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("From required at least one address"))
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := valid.ValidateStruct(c)
 | 
						|
	if err != nil {
 | 
						|
		errs = append(errs, err)
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// SlackConf is slack config
 | 
						|
type SlackConf struct {
 | 
						|
	HookURL   string `valid:"url"`
 | 
						|
	Channel   string `json:"channel"`
 | 
						|
	IconEmoji string `json:"icon_emoji"`
 | 
						|
	AuthUser  string `json:"username"`
 | 
						|
 | 
						|
	NotifyUsers []string
 | 
						|
	Text        string `json:"text"`
 | 
						|
 | 
						|
	UseThisTime bool
 | 
						|
}
 | 
						|
 | 
						|
// Validate validates configuration
 | 
						|
func (c *SlackConf) Validate() (errs []error) {
 | 
						|
	if !c.UseThisTime {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.HookURL) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("hookURL must not be empty"))
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.Channel) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("channel must not be empty"))
 | 
						|
	} else {
 | 
						|
		if !(strings.HasPrefix(c.Channel, "#") ||
 | 
						|
			c.Channel == "${servername}") {
 | 
						|
			errs = append(errs, fmt.Errorf(
 | 
						|
				"channel's prefix must be '#', channel: %s", c.Channel))
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(c.AuthUser) == 0 {
 | 
						|
		errs = append(errs, fmt.Errorf("authUser must not be empty"))
 | 
						|
	}
 | 
						|
 | 
						|
	_, err := valid.ValidateStruct(c)
 | 
						|
	if err != nil {
 | 
						|
		errs = append(errs, err)
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// ServerInfo has SSH Info, additional CPE packages to scan.
 | 
						|
type ServerInfo struct {
 | 
						|
	ServerName  string
 | 
						|
	User        string
 | 
						|
	Host        string
 | 
						|
	Port        string
 | 
						|
	KeyPath     string
 | 
						|
	KeyPassword string
 | 
						|
 | 
						|
	CpeNames               []string
 | 
						|
	DependencyCheckXMLPath string
 | 
						|
 | 
						|
	// Container Names or IDs
 | 
						|
	Containers []string
 | 
						|
 | 
						|
	IgnoreCves []string
 | 
						|
 | 
						|
	// Optional key-value set that will be outputted to JSON
 | 
						|
	Optional [][]interface{}
 | 
						|
 | 
						|
	// For CentOS, RHEL, Amazon
 | 
						|
	Enablerepo string
 | 
						|
 | 
						|
	// used internal
 | 
						|
	LogMsgAnsiColor string // DebugLog Color
 | 
						|
	Container       Container
 | 
						|
	Distro          Distro
 | 
						|
}
 | 
						|
 | 
						|
// GetServerName returns ServerName if this serverInfo is about host.
 | 
						|
// If this serverInfo is abount a container, returns containerID@ServerName
 | 
						|
func (s ServerInfo) GetServerName() string {
 | 
						|
	if len(s.Container.ContainerID) == 0 {
 | 
						|
		return s.ServerName
 | 
						|
	}
 | 
						|
	return fmt.Sprintf("%s@%s", s.Container.ContainerID, s.ServerName)
 | 
						|
}
 | 
						|
 | 
						|
// Distro has distribution info
 | 
						|
type Distro struct {
 | 
						|
	Family  string
 | 
						|
	Release string
 | 
						|
}
 | 
						|
 | 
						|
func (l Distro) String() string {
 | 
						|
	return fmt.Sprintf("%s %s", l.Family, l.Release)
 | 
						|
}
 | 
						|
 | 
						|
// MajorVersion returns Major version
 | 
						|
func (l Distro) MajorVersion() (ver int, err error) {
 | 
						|
	if 0 < len(l.Release) {
 | 
						|
		ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0])
 | 
						|
	} else {
 | 
						|
		err = fmt.Errorf("Release is empty")
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// IsContainer returns whether this ServerInfo is about container
 | 
						|
func (s ServerInfo) IsContainer() bool {
 | 
						|
	return 0 < len(s.Container.ContainerID)
 | 
						|
}
 | 
						|
 | 
						|
// SetContainer set container
 | 
						|
func (s *ServerInfo) SetContainer(d Container) {
 | 
						|
	s.Container = d
 | 
						|
}
 | 
						|
 | 
						|
// Container has Container information.
 | 
						|
type Container struct {
 | 
						|
	ContainerID string
 | 
						|
	Name        string
 | 
						|
	Type        string
 | 
						|
}
 |