diff --git a/Makefile b/Makefile index b4199289..306dfb8d 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,6 @@ fmtcheck \ pretest \ test \ - integration \ cov \ clean @@ -16,16 +15,16 @@ PKGS = ./. ./db ./config ./models ./report ./cveapi ./scan ./util ./commands all: test -vendor: - @ go get -v github.com/mjibson/party - party -d external -c -u +# vendor: +# @ go get -v github.com/mjibson/party +# party -d external -c -u lint: @ go get -v github.com/golang/lint/golint $(foreach file,$(SRCS),golint $(file) || exit;) vet: - @-go get -v golang.org/x/tools/cmd/vet + # @-go get -v golang.org/x/tools/cmd/vet $(foreach pkg,$(PKGS),go vet $(pkg);) fmt: diff --git a/README.md b/README.md index de1ce3bb..62c66a7f 100644 --- a/README.md +++ b/README.md @@ -253,7 +253,9 @@ see https://github.com/future-architect/vuls/tree/master/docker ## Vuls - Scan vulnerabilities on the servers and create a list of the CVE ID -- For more detailed information of the detected CVE, send HTTP request to go-cve-dictinary + - To scan Docker containers, Vuls connect via ssh to the Docker host and then `docker exec` to the containers. So, no need to run sshd daemon on the containers. +- Fetch more detailed information of the detected CVE from go-cve-dictionary +- Insert scan result into SQLite3 - Send a report by Slack, Email - System operator can view the latest report by terminal @@ -335,6 +337,7 @@ host = "172.31.4.82" #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] +#containers = ["${running}"] ``` You can customize your configuration using this template. @@ -400,6 +403,10 @@ You can customize your configuration using this template. #port = "22" #user = "username" #keyPath = "/home/username/.ssh/id_rsa" + #cpeNames = [ + # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", + #] + #containers = ["${running}"] ``` Items of the default section will be used if not specified. @@ -415,6 +422,7 @@ You can customize your configuration using this template. #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] + #containers = ["${running}"] ``` You can overwrite the default value specified in default section. Vuls supports multiple SSH authentication methods. @@ -578,6 +586,43 @@ To detect the vulnerbility of Ruby on Rails v4.2.1, cpeNames needs to be set in "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", ] ``` + +# Usage: Scan Docker containers + +It is common that keep Docker containers runnning without SSHd daemon. +see [Docker Blog:Why you don't need to run SSHd in your Docker containers](https://blog.docker.com/2014/06/why-you-dont-need-to-run-sshd-in-docker/) + +Vuls scans Docker containers via `docker exec` instead of SSH. +For more details, see [Architecture section](https://github.com/future-architect/vuls#architecture) + +- To scan all of running containers + "${running}" needs to be set in the containers item. + ``` + [servers] + + [servers.172-31-4-82] + host = "172.31.4.82" + user = "ec2-user" + keyPath = "/home/username/.ssh/id_rsa" + containers = ["${running}"] + ``` + +- To scan specific containers + The container ID or container name needs to be set in the containers item. + In the following example, only "container_name_a" and "4aa37a8b63b9" will be scanned. + Be sure to check these containers are running state before scanning. + If specified containers are exited, vuls gives up scanning with printing error message. + ``` + [servers] + + [servers.172-31-4-82] + host = "172.31.4.82" + user = "ec2-user" + keyPath = "/home/username/.ssh/id_rsa" + containers = ["container_name_a", "4aa37a8b63b9"] + ``` + + # Usage: Update NVD Data. @@ -616,6 +661,10 @@ $ go-cve-dictionary fetchnvd -last2y # Misc +- Unable to go get vuls +Update git to the latest version. Old version of git can't get some repositories. +see https://groups.google.com/forum/#!topic/mgo-users/rO1-gUDFo_g + - HTTP Proxy Support If your system is behind HTTP proxy, you have to specify --http-proxy option. @@ -653,6 +702,13 @@ Use Microsoft Baseline Security Analyzer. [MBSA](https://technet.microsoft.com/e ---- +# Related Projects + +- [k1LoW/ssh_config_to_vuls_config](https://github.com/k1LoW/ssh_config_to_vuls_config) +ssh_config to vuls config TOML format + +---- + # Data Source - [NVD](https://nvd.nist.gov/) diff --git a/commands/discover.go b/commands/discover.go index ed3ed081..c900ba45 100644 --- a/commands/discover.go +++ b/commands/discover.go @@ -112,6 +112,10 @@ subjectPrefix = "[vuls]" #port = "22" #user = "username" #keyPath = "/home/username/.ssh/id_rsa" +#cpeNames = [ +# "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", +#] +#containers = ["${running}"] [servers] {{- $names:= .Names}} @@ -124,6 +128,7 @@ host = "{{$ip}}" #cpeNames = [ # "cpe:/a:rubyonrails:ruby_on_rails:4.2.1", #] +#containers = ["${running}"] {{end}} ` diff --git a/commands/scan.go b/commands/scan.go index c637c1f0..7302dd1b 100644 --- a/commands/scan.go +++ b/commands/scan.go @@ -231,7 +231,7 @@ func (p *ScanCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) return subcommands.ExitFailure } - Log.Info("Detecting the type of OS... ") + Log.Info("Detecting Server OS... ") err = scan.InitServers(Log) if err != nil { Log.Errorf("Failed to init servers. Check the configuration. err: %s", err) diff --git a/config/config.go b/config/config.go index 97171a09..68d512a5 100644 --- a/config/config.go +++ b/config/config.go @@ -188,8 +188,6 @@ func (c *SlackConf) Validate() (errs []error) { errs = append(errs, err) } - // TODO check if slack configration is valid - return } @@ -202,12 +200,33 @@ type ServerInfo struct { Port string KeyPath string KeyPassword string - SudoOpt SudoOption CpeNames []string - // DebugLog Color - LogMsgAnsiColor string + // Container Names or IDs + Containers []string + + // userd internal + LogMsgAnsiColor string // DebugLog Color + SudoOpt SudoOption + Container Container +} + +// 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 } // SudoOption is flag of sudo option. diff --git a/config/tomlloader.go b/config/tomlloader.go index 81471685..172b7dd1 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -101,6 +101,11 @@ func (c TOMLLoader) Load(pathToToml, keyPass, sudoPass string) (err error) { s.CpeNames = d.CpeNames } + s.Containers = v.Containers + if len(s.Containers) == 0 { + s.Containers = d.Containers + } + s.LogMsgAnsiColor = Colors[i%len(Colors)] i++ diff --git a/db/db.go b/db/db.go index 3bc1d2a1..516fba7c 100644 --- a/db/db.go +++ b/db/db.go @@ -49,6 +49,7 @@ func MigrateDB() error { &m.ScanHistory{}, &m.ScanResult{}, // &m.NWLink{}, + &m.Container{}, &m.CveInfo{}, &m.CpeName{}, &m.PackageInfo{}, @@ -67,6 +68,10 @@ func MigrateDB() error { // AddIndex("idx_n_w_links_scan_result_id", "scan_result_id").Error; err != nil { // return fmt.Errorf(errMsg, err) // } + if err := db.Model(&m.Container{}). + AddIndex("idx_containers_scan_result_id", "scan_result_id").Error; err != nil { + return fmt.Errorf(errMsg, err) + } if err := db.Model(&m.CveInfo{}). AddIndex("idx_cve_infos_scan_result_id", "scan_result_id").Error; err != nil { return fmt.Errorf(errMsg, err) @@ -85,11 +90,11 @@ func MigrateDB() error { return fmt.Errorf(errMsg, err) } if err := db.Model(&cve.CveDetail{}). - AddIndex("idx_cve_detail_cve_info_id", "cve_info_id").Error; err != nil { + AddIndex("idx_cve_details_cve_info_id", "cve_info_id").Error; err != nil { return fmt.Errorf(errMsg, err) } if err := db.Model(&cve.CveDetail{}). - AddIndex("idx_cve_detail_cveid", "cve_id").Error; err != nil { + AddIndex("idx_cve_details_cveid", "cve_id").Error; err != nil { return fmt.Errorf(errMsg, err) } if err := db.Model(&cve.Nvd{}). @@ -141,6 +146,10 @@ func Insert(results []m.ScanResult) error { if err := db.Create(&scanResult).Error; err != nil { return err } + scanResult.Container.ScanResultID = scanResult.ID + if err := db.Create(&scanResult.Container).Error; err != nil { + return err + } if err := insertCveInfos(scanResult.ID, scanResult.KnownCves); err != nil { return err } @@ -229,7 +238,8 @@ func SelectLatestScanHistory() (m.ScanHistory, error) { return m.ScanHistory{}, fmt.Errorf("No scanHistory records") } - results := []m.ScanResult{} + // results := []m.ScanResult{} + results := m.ScanResults{} db.Model(&scanHistory).Related(&results, "ScanResults") scanHistory.ScanResults = results @@ -238,10 +248,16 @@ func SelectLatestScanHistory() (m.ScanHistory, error) { // db.Model(&r).Related(&nw, "NWLinks") // scanHistory.ScanResults[i].NWLinks = nw + di := m.Container{} + db.Model(&r).Related(&di, "Container") + scanHistory.ScanResults[i].Container = di + knownCves := selectCveInfos(&r, "KnownCves") sort.Sort(m.CveInfos(knownCves)) scanHistory.ScanResults[i].KnownCves = knownCves } + + sort.Sort(scanHistory.ScanResults) return scanHistory, nil } diff --git a/img/vuls-architecture.graphml b/img/vuls-architecture.graphml index 9eb4713f..2059a71c 100644 --- a/img/vuls-architecture.graphml +++ b/img/vuls-architecture.graphml @@ -17,7 +17,7 @@ - + @@ -37,14 +37,14 @@ - + - Vulnerbility Database + Vulnerbility Database - + @@ -63,7 +63,7 @@ - + JVN @@ -81,7 +81,7 @@ - + NVD @@ -103,14 +103,14 @@ - + Linux Support - + @@ -129,7 +129,7 @@ - + apptitude @@ -147,7 +147,7 @@ changelog - + yum @@ -165,7 +165,7 @@ changelog - + RHSA (RedHat) @@ -185,10 +185,10 @@ ALAS (Amazon) - + - + @@ -199,48 +199,20 @@ ALAS (Amazon) - - - - - - - servers - - - - - - - - - - - - - - - - - - - - - - + - + - Vuls + Vuls - + @@ -255,11 +227,11 @@ ALAS (Amazon) - - + + - + Report @@ -273,10 +245,10 @@ ALAS (Amazon) - + - + TUI View @@ -290,11 +262,11 @@ ALAS (Amazon) - + - - + + Scan @@ -309,10 +281,10 @@ ALAS (Amazon) - + - + System Operator @@ -324,15 +296,15 @@ ALAS (Amazon) - + - + - + @@ -353,24 +325,24 @@ ALAS (Amazon) - + - + - + - + - + @@ -390,20 +362,20 @@ ALAS (Amazon) - + - + - go-cve-dictionary + go-cve-dictionary - + @@ -418,11 +390,11 @@ ALAS (Amazon) - - + + - + @@ -443,10 +415,10 @@ ALAS (Amazon) - + - + HTTP server @@ -460,10 +432,10 @@ ALAS (Amazon) - + - + Fetcher @@ -479,28 +451,151 @@ ALAS (Amazon) - - - - - - - - + + + + + + + + + + + Docker Containers + + + + + + + + + + Folder 5 + + + + + + + - - + + + + + + + + DockerHost + + + + + + + + + + + + + + + + + Docker +Container + + + + + + + + + + + + + + + + + + + + + + + Linux Servers + + + + + + + + + + Folder 6 + + + + + + + + + + + + + + + + Server + + + + + + + + + + + + + + + + + Server + + + + + + + + + + + + + - Fetch + Fetch Vulnerability data - + @@ -514,17 +609,7 @@ Vulnerability data - - - - - - - - - - - HTTP + HTTP @@ -536,67 +621,17 @@ Vulnerability data - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SSH + HTTP - + @@ -604,7 +639,137 @@ Vulnerability data - + + + + + + + HTTP + + + + + + + + + + + + + + + + + + send + + + + + + + + + + + + + + + + + + Generate + + + + + + + + + + + + + + + + + + Detail Information + + + + + + + + + + + + + + + + + + + SSH + + + + + + + + + + + + + + + + + + + SSH + + + + + + + + + + + + + + + + + + + docker exec + + + + + + + + + + + + + @@ -614,22 +779,120 @@ Vulnerability data - + + - + - + + - + + Insert + + + + + + + + + + + + + + + + + + + Select + + + + + + + + + + + + + + + + + + + Insert +Scan Result + + + + + + + + + + + + + + + + + + + Select + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Notify + + + + + + + @@ -638,162 +901,6 @@ Vulnerability data <?xml version="1.0" encoding="utf-8"?> -<svg version="1.1" - xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - x="0px" y="0px" width="68px" height="60px" viewBox="-0.435 -0.869 68 60" enable-background="new -0.435 -0.869 68 60" - xml:space="preserve"> -<defs> -</defs> -<path fill="#666666" d="M52.462,30.881c-0.021,0-0.037,0.01-0.059,0.012c-0.021-0.002-0.037-0.012-0.059-0.012h-18.5v-7.555 - c0-0.414-0.335-0.75-0.75-0.75c-0.414,0-0.75,0.336-0.75,0.75v7.555h-18.5c-0.02,0-0.037,0.01-0.057,0.012 - c-0.02-0.002-0.037-0.012-0.057-0.012c-0.414,0-0.75,0.336-0.75,0.75v3.834c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75 - v-3.084H51.71v3.084c0,0.414,0.336,0.75,0.75,0.75s0.75-0.336,0.75-0.75v-3.834C53.212,31.217,52.876,30.881,52.462,30.881z"/> -<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="130.7236" y1="-184.1631" x2="130.7236" y2="-191.9565" gradientTransform="matrix(1 0 0 -1 -97.6001 -158.6377)"> - <stop offset="0" style="stop-color:#9CD7FF"/> - <stop offset="1" style="stop-color:#3C89C9"/> -</linearGradient> -<path fill="url(#SVGID_1_)" d="M36.296,29.976c-0.832,0-1.513-0.681-1.513-1.513v-1.424c0-0.832-0.681-1.513-1.513-1.513h-0.214 - c-0.832,0-1.513,0.681-1.513,1.513v1.424c0,0.832-0.681,1.513-1.513,1.513h-2.499c-0.832,0-1.513,0.681-1.513,1.513v0.317 - c0,0.832,0.681,1.513,1.513,1.513h11.187c0.832,0,1.513-0.681,1.513-1.513v-0.317c0-0.833-0.681-1.513-1.513-1.513H36.296z"/> -<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="605.8877" y1="2040.6665" x2="593.1709" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_2_)" d="M20.205,57.452c0,0.519-3.619,0.752-6.627,0.752c-2.083,0-5.846-0.186-6.089-0.678 - c0,0.238,0,0.806,0,0.89c0,0.389,2.573,0.661,6.084,0.661c3.511,0,6.632-0.344,6.632-0.729C20.205,58.264,20.205,57.7,20.205,57.452 - z"/> -<path fill="#808080" d="M13.846,56.806c3.512,0,6.358,0.313,6.358,0.699s-2.846,0.763-6.358,0.763c-3.59,0-6.358-0.375-6.358-0.763 - S10.335,56.806,13.846,56.806z"/> -<linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="600.833" y1="2037.4702" x2="598.1563" y2="2037.4702" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#999999"/> - <stop offset="0.0417" style="stop-color:#8D8D8D"/> - <stop offset="0.1617" style="stop-color:#717171"/> - <stop offset="0.2821" style="stop-color:#5D5D5D"/> - <stop offset="0.4021" style="stop-color:#515151"/> - <stop offset="0.5212" style="stop-color:#4D4D4D"/> - <stop offset="0.6202" style="stop-color:#565656"/> - <stop offset="0.7817" style="stop-color:#6E6E6E"/> - <stop offset="0.9844" style="stop-color:#969696"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_3_)" d="M15.215,57.657c0,0-0.792,0.053-1.339,0.053s-1.338-0.053-1.338-0.053v-5.231h2.677V57.657z"/> -<radialGradient id="SVGID_4_" cx="465.1113" cy="2023.4497" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#F2F2F2"/> - <stop offset="1" style="stop-color:#666666"/> -</radialGradient> -<path fill="url(#SVGID_4_)" d="M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108 - c0,0.589-0.482,1.07-1.071,1.07H1.137c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/> -<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d=" - M0.065,36.888c0-0.59,0.482-1.071,1.072-1.071H26.98c0.589,0,1.071,0.481,1.071,1.071v16.108c0,0.589-0.482,1.07-1.071,1.07H1.137 - c-0.59,0.002-1.072-0.481-1.072-1.07V36.888z"/> -<radialGradient id="SVGID_5_" cx="439.1309" cy="2019.0845" r="28.5715" fx="461.6079" fy="2015.234" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</radialGradient> -<path fill="url(#SVGID_5_)" d="M0.613,37.436c0-0.591,0.482-1.072,1.071-1.072h24.871c0.589,0,1.071,0.481,1.071,1.072v14.893 - c0,0.59-0.482,1.072-1.071,1.072H1.685c-0.589,0-1.071-0.482-1.071-1.072V37.436z"/> -<radialGradient id="SVGID_6_" cx="440.0439" cy="2019.1304" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#9CD7FF"/> - <stop offset="1" style="stop-color:#3C89C9"/> -</radialGradient> -<path fill="url(#SVGID_6_)" d="M0.917,37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071v14.406 - c0,0.588-0.482,1.069-1.071,1.069H1.989c-0.59,0-1.072-0.481-1.072-1.069V37.679z"/> -<path opacity="0.24" fill="#F2F2F2" d="M0.917,49.11V37.679c0-0.59,0.482-1.071,1.072-1.071h24.262c0.589,0,1.071,0.481,1.071,1.071 - v7.252l-12.407,2.646c-0.57,0.146-1.52,0.293-2.107,0.326L0.917,49.11z"/> -<linearGradient id="SVGID_7_" gradientUnits="userSpaceOnUse" x1="644.3887" y1="2040.6665" x2="631.6719" y2="2040.6665" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_7_)" d="M58.706,57.452c0,0.518-3.621,0.752-6.627,0.752c-2.084,0-5.848-0.186-6.09-0.678 - c0,0.237,0,0.805,0,0.889c0,0.389,2.572,0.662,6.084,0.662s6.633-0.344,6.633-0.729C58.706,58.263,58.706,57.7,58.706,57.452z"/> -<path fill="#808080" d="M52.347,56.805c3.512,0,6.357,0.313,6.357,0.699s-2.847,0.762-6.357,0.762c-3.59,0-6.357-0.373-6.357-0.762 - C45.989,57.118,48.837,56.805,52.347,56.805z"/> -<linearGradient id="SVGID_8_" gradientUnits="userSpaceOnUse" x1="639.333" y1="2037.4683" x2="636.6553" y2="2037.4683" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#999999"/> - <stop offset="0.0417" style="stop-color:#8D8D8D"/> - <stop offset="0.1617" style="stop-color:#717171"/> - <stop offset="0.2821" style="stop-color:#5D5D5D"/> - <stop offset="0.4021" style="stop-color:#515151"/> - <stop offset="0.5212" style="stop-color:#4D4D4D"/> - <stop offset="0.6202" style="stop-color:#565656"/> - <stop offset="0.7817" style="stop-color:#6E6E6E"/> - <stop offset="0.9844" style="stop-color:#969696"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_8_)" d="M53.716,57.657c0,0-0.791,0.052-1.34,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.678V57.657z" - /> -<radialGradient id="SVGID_9_" cx="498.5898" cy="2023.4487" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#F2F2F2"/> - <stop offset="1" style="stop-color:#666666"/> -</radialGradient> -<path fill="url(#SVGID_9_)" d="M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107 - c0,0.59-0.481,1.072-1.07,1.072H39.638c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/> -<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d=" - M38.566,36.887c0-0.59,0.481-1.072,1.071-1.072h25.844c0.589,0,1.07,0.482,1.07,1.072v16.107c0,0.59-0.481,1.072-1.07,1.072H39.638 - c-0.59,0-1.071-0.482-1.071-1.072V36.887z"/> -<radialGradient id="SVGID_10_" cx="471.3896" cy="2019.0845" r="28.5697" fx="493.8652" fy="2015.2343" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</radialGradient> -<path fill="url(#SVGID_10_)" d="M39.114,37.434c0-0.59,0.482-1.072,1.071-1.072h24.87c0.589,0,1.07,0.482,1.07,1.072v14.895 - c0,0.589-0.481,1.07-1.07,1.07h-24.87c-0.589,0-1.071-0.481-1.071-1.07V37.434z"/> -<radialGradient id="SVGID_11_" cx="472.334" cy="2019.1294" r="18.3139" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#9CD7FF"/> - <stop offset="1" style="stop-color:#3C89C9"/> -</radialGradient> -<path fill="url(#SVGID_11_)" d="M39.419,37.678c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072v14.406 - c0,0.588-0.482,1.07-1.07,1.07H40.489c-0.589,0-1.07-0.482-1.07-1.07V37.678z"/> -<path opacity="0.24" fill="#F2F2F2" d="M39.419,49.108v-11.43c0-0.59,0.481-1.072,1.07-1.072h24.264c0.588,0,1.07,0.482,1.07,1.072 - v7.252l-12.408,2.645c-0.57,0.146-1.52,0.295-2.106,0.326L39.419,49.108z"/> -<linearGradient id="SVGID_12_" gradientUnits="userSpaceOnUse" x1="624.8936" y1="2004.9155" x2="612.1787" y2="2004.9155" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_12_)" d="M39.212,21.701c0,0.518-3.621,0.752-6.626,0.752c-2.083,0-5.847-0.186-6.089-0.678 - c0,0.238,0,0.805,0,0.889c0,0.389,2.573,0.662,6.084,0.662c3.51,0,6.631-0.344,6.631-0.729 - C39.212,22.513,39.212,21.949,39.212,21.701z"/> -<path fill="#808080" d="M32.854,21.055c3.511,0,6.358,0.313,6.358,0.699c0,0.386-2.848,0.762-6.358,0.762 - c-3.589,0-6.358-0.374-6.358-0.762C26.496,21.367,29.342,21.055,32.854,21.055z"/> -<linearGradient id="SVGID_13_" gradientUnits="userSpaceOnUse" x1="619.8379" y1="2001.7183" x2="617.1611" y2="2001.7183" gradientTransform="matrix(1 0 0 1 -585.5996 -1982.4023)"> - <stop offset="0" style="stop-color:#999999"/> - <stop offset="0.0417" style="stop-color:#8D8D8D"/> - <stop offset="0.1617" style="stop-color:#717171"/> - <stop offset="0.2821" style="stop-color:#5D5D5D"/> - <stop offset="0.4021" style="stop-color:#515151"/> - <stop offset="0.5212" style="stop-color:#4D4D4D"/> - <stop offset="0.6202" style="stop-color:#565656"/> - <stop offset="0.7817" style="stop-color:#6E6E6E"/> - <stop offset="0.9844" style="stop-color:#969696"/> - <stop offset="1" style="stop-color:#999999"/> -</linearGradient> -<path fill="url(#SVGID_13_)" d="M34.222,21.906c0,0-0.791,0.052-1.338,0.052c-0.547,0-1.338-0.052-1.338-0.052v-5.232h2.677 - L34.222,21.906L34.222,21.906z"/> -<radialGradient id="SVGID_14_" cx="481.6387" cy="1987.6978" r="12.8975" gradientTransform="matrix(1.15 0 0 1 -526.6041 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#F2F2F2"/> - <stop offset="1" style="stop-color:#666666"/> -</radialGradient> -<path fill="url(#SVGID_14_)" d="M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108 - c0,0.589-0.482,1.071-1.071,1.071H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/> -<path fill="none" stroke="#666666" stroke-width="0.1305" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d=" - M19.072,1.137c0-0.59,0.482-1.072,1.071-1.072h25.843c0.589,0,1.071,0.482,1.071,1.072v16.108c0,0.589-0.482,1.071-1.071,1.071 - H20.145c-0.589,0-1.071-0.482-1.071-1.071L19.072,1.137L19.072,1.137z"/> -<radialGradient id="SVGID_15_" cx="455.0566" cy="1983.3345" r="28.5689" fx="477.5316" fy="1979.4844" gradientTransform="matrix(1.1935 0 0 1 -509.6013 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#4D4D4D"/> - <stop offset="1" style="stop-color:#999999"/> -</radialGradient> -<path fill="url(#SVGID_15_)" d="M19.621,1.685c0-0.59,0.482-1.072,1.072-1.072h24.87c0.589,0,1.071,0.482,1.071,1.072v14.894 - c0,0.589-0.482,1.071-1.071,1.071h-24.87c-0.589,0-1.072-0.482-1.072-1.071V1.685z"/> -<radialGradient id="SVGID_16_" cx="455.9854" cy="1983.3784" r="18.3134" gradientTransform="matrix(1.1923 0 0 1 -510.0601 -1982.4023)" gradientUnits="userSpaceOnUse"> - <stop offset="0" style="stop-color:#9CD7FF"/> - <stop offset="1" style="stop-color:#3C89C9"/> -</radialGradient> -<path fill="url(#SVGID_16_)" d="M19.924,1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072v14.406 - c0,0.588-0.481,1.07-1.07,1.07H20.997c-0.589,0-1.072-0.482-1.072-1.07V1.928z"/> -<path opacity="0.24" fill="#F2F2F2" d="M19.924,13.358V1.928c0-0.59,0.482-1.072,1.072-1.072h24.262c0.589,0,1.07,0.482,1.07,1.072 - V9.18l-12.408,2.646c-0.569,0.146-1.519,0.294-2.106,0.326L19.924,13.358z"/> -</svg> - - <?xml version="1.0" encoding="utf-8"?> <svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="57px" height="65px" viewBox="0 0 57 65" enable-background="new 0 0 57 65" xml:space="preserve"> <g> @@ -871,7 +978,7 @@ Vulnerability data </g> </svg> - <?xml version="1.0" encoding="UTF-8" standalone="no"?> + <?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" diff --git a/img/vuls-architecture.png b/img/vuls-architecture.png index 9a2de58f..cb4302c7 100644 Binary files a/img/vuls-architecture.png and b/img/vuls-architecture.png differ diff --git a/models/models.go b/models/models.go index 05a99318..114af15f 100644 --- a/models/models.go +++ b/models/models.go @@ -30,16 +30,34 @@ import ( // ScanHistory is the history of Scanning. type ScanHistory struct { gorm.Model - ScanResults []ScanResult + ScanResults ScanResults ScannedAt time.Time } // ScanResults is slice of ScanResult. type ScanResults []ScanResult +// Len implement Sort Interface +func (s ScanResults) Len() int { + return len(s) +} + +// Swap implement Sort Interface +func (s ScanResults) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less implement Sort Interface +func (s ScanResults) Less(i, j int) bool { + if s[i].ServerName == s[j].ServerName { + return s[i].Container.ContainerID < s[i].Container.ContainerID + } + return s[i].ServerName < s[j].ServerName +} + // FilterByCvssOver is filter function. -func (results ScanResults) FilterByCvssOver() (filtered ScanResults) { - for _, result := range results { +func (s ScanResults) FilterByCvssOver() (filtered ScanResults) { + for _, result := range s { cveInfos := []CveInfo{} for _, cveInfo := range result.KnownCves { if config.Conf.CvssScoreOver < cveInfo.CveDetail.CvssScore(config.Conf.Lang) { @@ -61,12 +79,60 @@ type ScanResult struct { // Hostname string Family string Release string + + Container Container + // Fqdn string // NWLinks []NWLink KnownCves []CveInfo UnknownCves []CveInfo } +// ServerInfo returns server name one line +func (r ScanResult) ServerInfo() string { + hostinfo := "" + if r.Container.ContainerID == "" { + hostinfo = fmt.Sprintf( + "%s (%s%s)", + r.ServerName, + r.Family, + r.Release, + ) + } else { + hostinfo = fmt.Sprintf( + "%s / %s (%s%s) on %s", + r.Container.Name, + r.Container.ContainerID, + r.Family, + r.Release, + r.ServerName, + ) + } + return hostinfo +} + +// ServerInfoTui returns server infromation for TUI sidebar +func (r ScanResult) ServerInfoTui() string { + hostinfo := "" + if r.Container.ContainerID == "" { + hostinfo = fmt.Sprintf( + "%s (%s%s)", + r.ServerName, + r.Family, + r.Release, + ) + } else { + hostinfo = fmt.Sprintf( + "|-- %s (%s%s)", + r.Container.Name, + r.Family, + r.Release, + // r.Container.ContainerID, + ) + } + return hostinfo +} + // CveSummary summarize the number of CVEs group by CVSSv2 Severity func (r ScanResult) CveSummary() string { var high, middle, low, unknown int @@ -232,7 +298,6 @@ func (p PackageInfo) ToStringNewVersion() string { } // DistroAdvisory has Amazon Linux AMI Security Advisory information. -//TODO Rename to DistroAdvisory type DistroAdvisory struct { gorm.Model CveInfoID uint @@ -242,3 +307,12 @@ type DistroAdvisory struct { Issued time.Time Updated time.Time } + +// Container has Container information +type Container struct { + gorm.Model + ScanResultID uint + + ContainerID string + Name string +} diff --git a/report/mail.go b/report/mail.go index 5ee6793d..34cd3feb 100644 --- a/report/mail.go +++ b/report/mail.go @@ -40,7 +40,7 @@ func (w MailWriter) Write(scanResults []models.ScanResult) (err error) { subject := fmt.Sprintf("%s%s %s", conf.Mail.SubjectPrefix, - s.ServerName, + s.ServerInfo(), s.CveSummary(), ) m.SetHeader("Subject", subject) diff --git a/report/slack.go b/report/slack.go index 6a575b10..064850de 100644 --- a/report/slack.go +++ b/report/slack.go @@ -103,13 +103,8 @@ func msgText(r models.ScanResult) string { 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()) + serverInfo := fmt.Sprintf("*%s*", r.ServerInfo()) + return fmt.Sprintf("%s\n%s\n>%s", notifyUsers, serverInfo, r.CveSummary()) } func toSlackAttachments(scanResult models.ScanResult) (attaches []*attachment) { diff --git a/report/tui.go b/report/tui.go index 6f8bc595..9f38d7f2 100644 --- a/report/tui.go +++ b/report/tui.go @@ -410,7 +410,7 @@ func changeHost(g *gocui.Gui, v *gocui.View) error { serverName := strings.TrimSpace(l) for _, r := range scanHistory.ScanResults { - if serverName == r.ServerName { + if serverName == strings.TrimSpace(r.ServerInfoTui()) { currentScanResult = r break } @@ -509,14 +509,14 @@ func layout(g *gocui.Gui) error { func setSideLayout(g *gocui.Gui) error { _, maxY := g.Size() - if v, err := g.SetView("side", -1, -1, 30, maxY); err != nil { + if v, err := g.SetView("side", -1, -1, 40, maxY); err != nil { if err != gocui.ErrUnknownView { return err } v.Highlight = true for _, result := range scanHistory.ScanResults { - fmt.Fprintln(v, result.ServerName) + fmt.Fprintln(v, result.ServerInfoTui()) } currentScanResult = scanHistory.ScanResults[0] if err := g.SetCurrentView("side"); err != nil { @@ -528,7 +528,7 @@ func setSideLayout(g *gocui.Gui) error { 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 v, err := g.SetView("summary", 40, -1, maxX, int(float64(maxY)*0.2)); err != nil { if err != gocui.ErrUnknownView { return err } @@ -616,7 +616,7 @@ func setDetailLayout(g *gocui.Gui) error { _, oy := summaryView.Origin() currentCveInfo = cy + oy - if v, err := g.SetView("detail", 30, int(float64(maxY)*0.2), maxX, maxY); err != nil { + if v, err := g.SetView("detail", 40, int(float64(maxY)*0.2), maxX, maxY); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/report/util.go b/report/util.go index d3e87639..6982e19e 100644 --- a/report/util.go +++ b/report/util.go @@ -28,18 +28,13 @@ import ( ) func toPlainText(scanResult models.ScanResult) (string, error) { - hostinfo := fmt.Sprintf( - "%s (%s %s)", - scanResult.ServerName, - scanResult.Family, - scanResult.Release, - ) + serverInfo := scanResult.ServerInfo() var buffer bytes.Buffer - for i := 0; i < len(hostinfo); i++ { + for i := 0; i < len(serverInfo); i++ { buffer.WriteString("=") } - header := fmt.Sprintf("%s\n%s", hostinfo, buffer.String()) + header := fmt.Sprintf("%s\n%s", serverInfo, buffer.String()) if len(scanResult.KnownCves) == 0 && len(scanResult.UnknownCves) == 0 { return fmt.Sprintf(` diff --git a/scan/linux.go b/scan/linux.go index 14861a2b..57c538fe 100644 --- a/scan/linux.go +++ b/scan/linux.go @@ -20,6 +20,7 @@ package scan import ( "fmt" "sort" + "strings" "github.com/Sirupsen/logrus" "github.com/future-architect/vuls/config" @@ -34,6 +35,8 @@ type linux struct { Release string osPackages log *logrus.Entry + + errs []error } func (l *linux) ssh(cmd string, sudo bool) sshResult { @@ -44,7 +47,7 @@ func (l *linux) setServerInfo(c config.ServerInfo) { l.ServerInfo = c } -func (l *linux) getServerInfo() config.ServerInfo { +func (l linux) getServerInfo() config.ServerInfo { return l.ServerInfo } @@ -57,6 +60,77 @@ func (l *linux) getDistributionInfo() string { return fmt.Sprintf("%s %s", l.Family, l.Release) } +func (l linux) allContainers() (containers []config.Container, err error) { + switch l.ServerInfo.Container.Type { + case "", "docker": + stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}}'") + if err != nil { + return containers, err + } + return l.parseDockerPs(stdout) + default: + return containers, fmt.Errorf( + "Not supported yet: %s", l.ServerInfo.Container.Type) + } +} + +func (l *linux) runningContainers() (containers []config.Container, err error) { + switch l.ServerInfo.Container.Type { + case "", "docker": + stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}}'") + if err != nil { + return containers, err + } + return l.parseDockerPs(stdout) + default: + return containers, fmt.Errorf( + "Not supported yet: %s", l.ServerInfo.Container.Type) + } +} + +func (l *linux) exitedContainers() (containers []config.Container, err error) { + switch l.ServerInfo.Container.Type { + case "", "docker": + stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}}'") + if err != nil { + return containers, err + } + return l.parseDockerPs(stdout) + default: + return containers, fmt.Errorf( + "Not supported yet: %s", l.ServerInfo.Container.Type) + } +} + +func (l *linux) dockerPs(option string) (string, error) { + cmd := fmt.Sprintf("docker ps %s", option) + r := l.ssh(cmd, noSudo) + if !r.isSuccess() { + return "", fmt.Errorf( + "Failed to %s. status: %d, stdout: %s, stderr: %s", + cmd, r.ExitStatus, r.Stdout, r.Stderr) + } + return r.Stdout, nil +} + +func (l *linux) parseDockerPs(stdout string) (containers []config.Container, err error) { + lines := strings.Split(stdout, "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) == 0 { + break + } + if len(fields) != 2 { + return containers, fmt.Errorf("Unknown format: %s", line) + } + containers = append(containers, config.Container{ + ContainerID: fields[0], + Name: fields[1], + }) + } + return +} + func (l *linux) convertToModel() (models.ScanResult, error) { var cves, unknownScoreCves []models.CveInfo for _, p := range l.UnsecurePackages { @@ -84,10 +158,16 @@ func (l *linux) convertToModel() (models.ScanResult, error) { cves = append(cves, cve) } + container := models.Container{ + ContainerID: l.ServerInfo.Container.ContainerID, + Name: l.ServerInfo.Container.Name, + } + return models.ScanResult{ ServerName: l.ServerInfo.ServerName, Family: l.Family, Release: l.Release, + Container: container, KnownCves: cves, UnknownCves: unknownScoreCves, }, nil @@ -132,3 +212,11 @@ func (l *linux) scanVulnByCpeName() error { l.setUnsecurePackages(unsecurePacks) return nil } + +func (l *linux) setErrs(errs []error) { + l.errs = errs +} + +func (l linux) getErrs() []error { + return l.errs +} diff --git a/scan/linux_test.go b/scan/linux_test.go index ef421884..c2a6ccf2 100644 --- a/scan/linux_test.go +++ b/scan/linux_test.go @@ -16,3 +16,43 @@ along with this program. If not, see . */ package scan + +import ( + "reflect" + "testing" + + "github.com/future-architect/vuls/config" +) + +func TestParseDockerPs(t *testing.T) { + + var test = struct { + in string + expected []config.Container + }{ + `c7ca0992415a romantic_goldberg +f570ae647edc agitated_lovelace`, + []config.Container{ + { + ContainerID: "c7ca0992415a", + Name: "romantic_goldberg", + }, + { + ContainerID: "f570ae647edc", + Name: "agitated_lovelace", + }, + }, + } + + r := newRedhat(config.ServerInfo{}) + actual, err := r.parseDockerPs(test.in) + if err != nil { + t.Errorf("Error occurred. in: %s, err: %s", test.in, err) + return + } + for i, e := range test.expected { + if !reflect.DeepEqual(e, actual[i]) { + t.Errorf("expected %v, actual %v", e, actual[i]) + } + } +} diff --git a/scan/redhat.go b/scan/redhat.go index 1c024f67..298b633f 100644 --- a/scan/redhat.go +++ b/scan/redhat.go @@ -861,3 +861,7 @@ func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacks } return result, nil } + +func (o *redhat) clone() osTypeInterface { + return o +} diff --git a/scan/serverapi.go b/scan/serverapi.go index dccecd74..8e18f5bc 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -26,6 +26,13 @@ type osTypeInterface interface { scanVulnByCpeName() error install() error convertToModel() (models.ScanResult, error) + + runningContainers() ([]config.Container, error) + exitedContainers() ([]config.Container, error) + allContainers() ([]config.Container, error) + + getErrs() []error + setErrs([]error) } // osPackages included by linux struct @@ -94,49 +101,68 @@ func (s CvePacksList) Less(i, j int) bool { return s[i].CveDetail.CvssScore("en") > s[j].CveDetail.CvssScore("en") } -func detectOs(c config.ServerInfo) (osType osTypeInterface) { +func detectOS(c config.ServerInfo) (osType osTypeInterface) { var itsMe bool itsMe, osType = detectDebian(c) if itsMe { return } itsMe, osType = detectRedhat(c) + if itsMe { + return + } + + osType.setErrs([]error{fmt.Errorf("Unknown OS Type")}) return } // InitServers detect the kind of OS distribution of target servers -func InitServers(localLogger *logrus.Entry) (err error) { +func InitServers(localLogger *logrus.Entry) error { Log = localLogger - if servers, err = detectServersOS(); err != nil { - err = fmt.Errorf("Failed to detect the type of OS. err: %s", err) + + hosts, err := detectServerOSes() + if err != nil { + return fmt.Errorf("Failed to detect server OSes. err: %s", err) } - return + servers = hosts + + Log.Info("Detecting Container OS...") + containers, err := detectContainerOSes() + if err != nil { + return fmt.Errorf("Failed to detect Container OSes. err: %s", err) + } + servers = append(servers, containers...) + return nil } -func detectServersOS() (osi []osTypeInterface, err error) { +func detectServerOSes() (oses []osTypeInterface, err error) { osTypeChan := make(chan osTypeInterface, len(config.Conf.Servers)) defer close(osTypeChan) for _, s := range config.Conf.Servers { go func(s config.ServerInfo) { - osTypeChan <- detectOs(s) + //TODO handling Unknown OS + osTypeChan <- detectOS(s) }(s) } - timeout := time.After(60 * time.Second) + timeout := time.After(300 * time.Second) for i := 0; i < len(config.Conf.Servers); i++ { select { case res := <-osTypeChan: - Log.Infof("(%d/%d) Successfully detected. %s: %s", + if 0 < len(res.getErrs()) { + continue + } + Log.Infof("(%d/%d) Detected %s: %s", i+1, len(config.Conf.Servers), res.getServerInfo().ServerName, res.getDistributionInfo()) - osi = append(osi, res) + oses = append(oses, res) case <-timeout: - Log.Error("Timeout occured while detecting") - err = fmt.Errorf("Timeout") + msg := "Timeout occurred while detecting" + Log.Error(msg) for servername := range config.Conf.Servers { found := false - for _, o := range osi { + for _, o := range oses { if servername == o.getServerInfo().ServerName { found = true break @@ -146,12 +172,158 @@ func detectServersOS() (osi []osTypeInterface, err error) { Log.Errorf("Failed to detect. servername: %s", servername) } } - return + return oses, fmt.Errorf(msg) } } + + errorOccurred := false + for _, osi := range oses { + if errs := osi.getErrs(); 0 < len(errs) { + errorOccurred = true + Log.Errorf("Some errors occurred on %s", + osi.getServerInfo().ServerName) + for _, err := range errs { + Log.Error(err) + } + } + } + if errorOccurred { + return oses, fmt.Errorf("Some errors occurred") + } return } +func detectContainerOSes() (oses []osTypeInterface, err error) { + osTypesChan := make(chan []osTypeInterface, len(servers)) + defer close(osTypesChan) + for _, s := range servers { + go func(s osTypeInterface) { + osTypesChan <- detectContainerOSesOnServer(s) + }(s) + } + + timeout := time.After(300 * time.Second) + for i := 0; i < len(config.Conf.Servers); i++ { + select { + case res := <-osTypesChan: + for _, osi := range res { + if 0 < len(osi.getErrs()) { + continue + } + sinfo := osi.getServerInfo() + Log.Infof("Detected %s/%s on %s: %s", + sinfo.Container.ContainerID, sinfo.Container.Name, + sinfo.ServerName, osi.getDistributionInfo()) + } + oses = append(oses, res...) + case <-timeout: + msg := "Timeout occurred while detecting" + Log.Error(msg) + for servername := range config.Conf.Servers { + found := false + for _, o := range oses { + if servername == o.getServerInfo().ServerName { + found = true + break + } + } + if !found { + Log.Errorf("Failed to detect. servername: %s", servername) + } + } + return oses, fmt.Errorf(msg) + } + } + + errorOccurred := false + for _, osi := range oses { + if errs := osi.getErrs(); 0 < len(errs) { + errorOccurred = true + Log.Errorf("Some errors occurred on %s", + osi.getServerInfo().ServerName) + for _, err := range errs { + Log.Error(err) + } + } + } + if errorOccurred { + return oses, fmt.Errorf("Some errors occurred") + } + return +} + +func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeInterface) { + containerHostInfo := containerHost.getServerInfo() + if len(containerHostInfo.Containers) == 0 { + return + } + + running, err := containerHost.runningContainers() + if err != nil { + containerHost.setErrs([]error{fmt.Errorf( + "Failed to get running containers on %s. err: %s", + containerHost.getServerInfo().ServerName, err)}) + return append(oses, containerHost) + } + + if containerHostInfo.Containers[0] == "${running}" { + for _, containerInfo := range running { + copied := containerHostInfo + copied.SetContainer(config.Container{ + ContainerID: containerInfo.ContainerID, + Name: containerInfo.Name, + }) + os := detectOS(copied) + oses = append(oses, os) + } + return oses + } + + exitedContainers, err := containerHost.exitedContainers() + if err != nil { + containerHost.setErrs([]error{fmt.Errorf( + "Failed to get exited containers on %s. err: %s", + containerHost.getServerInfo().ServerName, err)}) + return append(oses, containerHost) + } + + var exited, unknown []string + for _, container := range containerHostInfo.Containers { + found := false + for _, c := range running { + if c.ContainerID == container || c.Name == container { + copied := containerHostInfo + copied.SetContainer(c) + os := detectOS(copied) + oses = append(oses, os) + found = true + break + } + } + + if !found { + foundInExitedContainers := false + for _, c := range exitedContainers { + if c.ContainerID == container || c.Name == container { + exited = append(exited, container) + foundInExitedContainers = true + break + } + } + if !foundInExitedContainers { + unknown = append(unknown, container) + } + } + } + if 0 < len(exited) || 0 < len(unknown) { + containerHost.setErrs([]error{fmt.Errorf( + "Some containers on %s are exited or unknown. exited: %s, unknown: %s", + containerHost.getServerInfo().ServerName, exited, unknown)}) + return append(oses, containerHost) + } + return oses +} + // Prepare installs requred packages to scan vulnerabilities. func Prepare() []error { return parallelSSHExec(func(o osTypeInterface) error { diff --git a/scan/sshutil.go b/scan/sshutil.go index d9ea4fbc..6f4a6280 100644 --- a/scan/sshutil.go +++ b/scan/sshutil.go @@ -124,7 +124,7 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re } c.SudoOpt.ExecBySudo = true var err error - if sudo && c.User != "root" { + if sudo && c.User != "root" && !c.IsContainer() { switch { case c.SudoOpt.ExecBySudo: cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd) @@ -140,6 +140,13 @@ func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (re logger.Debugf("Command: %s", strings.Replace(maskPassword(cmd, c.Password), "\n", "", -1)) + if c.IsContainer() { + switch c.Container.Type { + case "", "docker": + cmd = fmt.Sprintf(`docker exec %s /bin/bash -c "%s"`, c.Container.ContainerID, cmd) + } + } + var client *ssh.Client client, err = sshConnect(c) defer client.Close() diff --git a/util/logutil.go b/util/logutil.go index 25cbc58d..74900f6b 100644 --- a/util/logutil.go +++ b/util/logutil.go @@ -53,11 +53,16 @@ func NewCustomLogger(c config.ServerInfo) *logrus.Entry { whereami := "localhost" if 0 < len(c.ServerName) { - whereami = fmt.Sprintf("%s:%s", c.ServerName, c.Port) - + if 0 < len(c.Container.ContainerID) { + whereami = fmt.Sprintf( + "%s_%s", c.ServerName, c.Container.Name) + } else { + whereami = fmt.Sprintf("%s", c.ServerName) + } } + if _, err := os.Stat(logDir); err == nil { - path := fmt.Sprintf("%s/%s.log", logDir, whereami) + path := filepath.Join(logDir, whereami) log.Hooks.Add(lfshook.NewHook(lfshook.PathMap{ logrus.DebugLevel: path, logrus.InfoLevel: path,