Add: source code

This commit is contained in:
kota kanbe
2016-04-01 14:36:50 +09:00
parent 9ee9641a8a
commit f4fb0b5463
39 changed files with 7546 additions and 0 deletions

655
scan/debian.go Normal file
View File

@@ -0,0 +1,655 @@
/* 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 scan
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
// inherit OsTypeInterface
type debian struct {
linux
}
// NewDebian is constructor
func newDebian(c config.ServerInfo) *debian {
d := &debian{}
d.log = util.NewCustomLogger(c)
return d
}
// Ubuntu, Debian
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface) {
deb = newDebian(c)
// set sudo option flag
c.SudoOpt = config.SudoOption{ExecBySudo: true}
deb.setServerInfo(c)
if r := sshExec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
return false, deb
}
if r := sshExec(c, "lsb_release -ir", noSudo); r.isSuccess() {
// e.g.
// root@fa3ec524be43:/# lsb_release -ir
// Distributor ID: Ubuntu
// Release: 14.04
re, _ := regexp.Compile(
`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
result := re.FindStringSubmatch(trim(r.Stdout))
if len(result) == 0 {
deb.setDistributionInfo("debian/ubuntu", "unknown")
Log.Warnf(
"Unknown Debian/Ubuntu version. lsb_release -ir: %s, Host: %s:%s",
r.Stdout, c.Host, c.Port)
} else {
distro := strings.ToLower(trim(result[1]))
deb.setDistributionInfo(distro, trim(result[2]))
}
return true, deb
}
if r := sshExec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
// e.g.
// DISTRIB_ID=Ubuntu
// DISTRIB_RELEASE=14.04
// DISTRIB_CODENAME=trusty
// DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
re, _ := regexp.Compile(
`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
result := re.FindStringSubmatch(trim(r.Stdout))
if len(result) == 0 {
Log.Warnf(
"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s, Host: %s:%s",
r.Stdout, c.Host, c.Port)
deb.setDistributionInfo("debian/ubuntu", "unknown")
} else {
distro := strings.ToLower(trim(result[1]))
deb.setDistributionInfo(distro, trim(result[2]))
}
return true, deb
}
// Debian
cmd := "cat /etc/debian_version"
if r := sshExec(c, cmd, noSudo); r.isSuccess() {
deb.setDistributionInfo("debian", trim(r.Stdout))
return true, deb
}
Log.Debugf("Not Debian like Linux. Host: %s:%s", c.Host, c.Port)
return false, deb
}
func trim(str string) string {
return strings.TrimSpace(str)
}
func (o *debian) install() error {
// apt-get update
o.log.Infof("apt-get update...")
cmd := util.PrependProxyEnv("apt-get update")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
if o.Family == "debian" {
// install aptitude
cmd = util.PrependProxyEnv("apt-get install --force-yes -y aptitude")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
o.log.Infof("Installed: aptitude")
}
// install unattended-upgrades
if !config.Conf.UseUnattendedUpgrades {
return nil
}
if r := o.ssh("type unattended-upgrade", noSudo); r.isSuccess() {
o.log.Infof(
"Ignored: unattended-upgrade already installed")
return nil
}
cmd = util.PrependProxyEnv(
"apt-get install --force-yes -y unattended-upgrades")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
msg := fmt.Sprintf("Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
o.log.Infof("Installed: unattended-upgrades")
return nil
}
func (o *debian) scanPackages() error {
var err error
var packs []models.PackageInfo
if packs, err = o.scanInstalledPackages(); err != nil {
o.log.Errorf("Failed to scan installed packages")
return err
}
o.setPackages(packs)
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(packs); err != nil {
o.log.Errorf("Failed to scan valnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
return nil
}
func (o *debian) scanInstalledPackages() (packs []models.PackageInfo, err error) {
r := o.ssh("dpkg-query -W", noSudo)
if !r.isSuccess() {
return packs, fmt.Errorf(
"Failed to scan packages. status: %d, stdout:%s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
}
// e.g.
// curl 7.19.7-40.el6_6.4
// openldap 2.4.39-8.el6
lines := strings.Split(r.Stdout, "\n")
for _, line := range lines {
if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
name, version, err := o.parseScanedPackagesLine(trimmed)
if err != nil {
return nil, fmt.Errorf(
"Debian: Failed to parse package line: %s", line)
}
packs = append(packs, models.PackageInfo{
Name: name,
Version: version,
})
}
}
return
}
func (o *debian) parseScanedPackagesLine(line string) (name, version string, err error) {
re, _ := regexp.Compile(`^([^\t']+)\t(.+)$`)
result := re.FindStringSubmatch(line)
if len(result) == 3 {
// remove :amd64, i386...
name = regexp.MustCompile(":.+").ReplaceAllString(result[1], "")
version = result[2]
return
}
return "", "", fmt.Errorf("Unknown format: %s", line)
}
// unattended-upgrade command need to check security upgrades).
func (o *debian) checkRequiredPackagesInstalled() error {
if o.Family == "debian" {
if r := o.ssh("test -f /usr/bin/aptitude", sudo); !r.isSuccess() {
msg := "aptitude is not installed"
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
if !config.Conf.UseUnattendedUpgrades {
return nil
}
if r := o.ssh("type unattended-upgrade", noSudo); !r.isSuccess() {
msg := "unattended-upgrade is not installed"
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
return nil
}
//TODO return whether already expired.
func (o *debian) scanUnsecurePackages(packs []models.PackageInfo) ([]CvePacksInfo, error) {
// cmd := prependProxyEnv(conf.HTTPProxy, "apt-get update | cat; echo 1")
cmd := util.PrependProxyEnv("apt-get update")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr,
)
}
var upgradablePackNames []string
var err error
if config.Conf.UseUnattendedUpgrades {
upgradablePackNames, err = o.GetUnsecurePackNamesUsingUnattendedUpgrades()
if err != nil {
return []CvePacksInfo{}, err
}
} else {
upgradablePackNames, err = o.GetUpgradablePackNames()
if err != nil {
return []CvePacksInfo{}, err
}
}
// Convert package name to PackageInfo struct
var unsecurePacks []models.PackageInfo
for _, name := range upgradablePackNames {
for _, pack := range packs {
if pack.Name == name {
unsecurePacks = append(unsecurePacks, pack)
break
}
}
}
unsecurePacks, err = o.fillCandidateVersion(unsecurePacks)
if err != nil {
return nil, err
}
// Collect CVE information of upgradable packages
cvePacksInfos, err := o.scanPackageCveInfos(unsecurePacks)
if err != nil {
return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err)
}
return cvePacksInfos, nil
}
func (o *debian) fillCandidateVersion(packs []models.PackageInfo) ([]models.PackageInfo, error) {
reqChan := make(chan models.PackageInfo, len(packs))
resChan := make(chan models.PackageInfo, len(packs))
errChan := make(chan error, len(packs))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
for _, pack := range packs {
reqChan <- pack
}
}()
timeout := time.After(5 * 60 * time.Second)
concurrency := 5
tasks := util.GenWorkers(concurrency)
for range packs {
tasks <- func() {
select {
case pack := <-reqChan:
func(p models.PackageInfo) {
cmd := fmt.Sprintf("apt-cache policy %s", p.Name)
r := o.ssh(cmd, sudo)
if !r.isSuccess() {
errChan <- fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
return
}
ver, err := o.parseAptCachePolicy(r.Stdout, p.Name)
if err != nil {
errChan <- fmt.Errorf("Failed to parse %s", err)
}
p.NewVersion = ver.Candidate
resChan <- p
}(pack)
}
}
}
result := []models.PackageInfo{}
for i := 0; i < len(packs); i++ {
select {
case pack := <-resChan:
result = append(result, pack)
o.log.Infof("(%d/%d) Upgradable: %s-%s -> %s",
i+1, len(packs), pack.Name, pack.Version, pack.NewVersion)
case err := <-errChan:
return nil, err
case <-timeout:
return nil, fmt.Errorf("Timeout fillCandidateVersion.")
}
}
return result, nil
}
func (o *debian) GetUnsecurePackNamesUsingUnattendedUpgrades() (packNames []string, err error) {
cmd := util.PrependProxyEnv("unattended-upgrades --dry-run -d 2>&1 ")
release, err := strconv.ParseFloat(o.Release, 64)
if err != nil {
return packNames, fmt.Errorf(
"OS Release Version is invalid, %s, %s", o.Family, o.Release)
}
switch {
case release < 12:
return packNames, fmt.Errorf(
"Support expired. %s, %s", o.Family, o.Release)
case 12 < release && release < 14:
cmd += `| grep 'pkgs that look like they should be upgraded:' |
sed -e 's/pkgs that look like they should be upgraded://g'`
case 14 < release:
cmd += `| grep 'Packages that will be upgraded:' |
sed -e 's/Packages that will be upgraded://g'`
default:
return packNames, fmt.Errorf(
"Not supported yet. %s, %s", o.Family, o.Release)
}
r := o.ssh(cmd, sudo)
if r.isSuccess(0, 1) {
packNames = strings.Split(strings.TrimSpace(r.Stdout), " ")
return packNames, nil
}
return packNames, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *debian) GetUpgradablePackNames() (packNames []string, err error) {
cmd := util.PrependProxyEnv("apt-get upgrade --dry-run")
r := o.ssh(cmd, sudo)
if r.isSuccess(0, 1) {
return o.parseAptGetUpgrade(r.Stdout)
}
return packNames, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *debian) parseAptGetUpgrade(stdout string) (upgradableNames []string, err error) {
startRe, _ := regexp.Compile(`The following packages will be upgraded:`)
stopRe, _ := regexp.Compile(`^(\d+) upgraded.*`)
startLineFound, stopLineFound := false, false
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if !startLineFound {
if matche := startRe.MatchString(line); matche {
startLineFound = true
}
continue
}
result := stopRe.FindStringSubmatch(line)
if len(result) == 2 {
numUpgradablePacks, err := strconv.Atoi(result[1])
if err != nil {
return nil, fmt.Errorf(
"Failed to scan upgradable packages number. line: %s", line)
}
if numUpgradablePacks != len(upgradableNames) {
return nil, fmt.Errorf(
"Failed to scan upgradable packages, expected: %s, detected: %d",
result[1], len(upgradableNames))
}
stopLineFound = true
o.log.Debugf("Found the stop line. line: %s", line)
break
}
upgradableNames = append(upgradableNames, strings.Fields(line)...)
}
if !startLineFound {
// no upgrades
return
}
if !stopLineFound {
// There are upgrades, but not found the stop line.
return nil, fmt.Errorf("Failed to scan upgradable packages")
}
return
}
func (o *debian) scanPackageCveInfos(unsecurePacks []models.PackageInfo) (cvePacksList CvePacksList, err error) {
// { CVE ID: [packageInfo] }
cvePackages := make(map[string][]models.PackageInfo)
type strarray []string
resChan := make(chan struct {
models.PackageInfo
strarray
}, len(unsecurePacks))
errChan := make(chan error, len(unsecurePacks))
reqChan := make(chan models.PackageInfo, len(unsecurePacks))
defer close(resChan)
defer close(errChan)
defer close(reqChan)
go func() {
for _, pack := range unsecurePacks {
reqChan <- pack
}
}()
timeout := time.After(30 * 60 * time.Second)
concurrency := 10
tasks := util.GenWorkers(concurrency)
for range unsecurePacks {
tasks <- func() {
select {
case pack := <-reqChan:
func(p models.PackageInfo) {
if cveIds, err := o.scanPackageCveIds(p); err != nil {
errChan <- err
} else {
resChan <- struct {
models.PackageInfo
strarray
}{p, cveIds}
}
}(pack)
}
}
}
for i := 0; i < len(unsecurePacks); i++ {
select {
case pair := <-resChan:
pack := pair.PackageInfo
cveIds := pair.strarray
for _, cveID := range cveIds {
cvePackages[cveID] = appendPackIfMissing(cvePackages[cveID], pack)
}
o.log.Infof("(%d/%d) Scanned %s-%s : %s",
i+1, len(unsecurePacks), pair.Name, pair.PackageInfo.Version, cveIds)
case err := <-errChan:
if err != nil {
return nil, err
}
case <-timeout:
return nil, fmt.Errorf("Timeout scanPackageCveIds.")
}
}
var cveIds []string
for k := range cvePackages {
cveIds = append(cveIds, k)
}
o.log.Debugf("%d Cves are found. cves: %v", len(cveIds), cveIds)
o.log.Info("Fetching CVE details...")
cveDetails, err := cveapi.CveClient.FetchCveDetails(cveIds)
if err != nil {
return nil, err
}
o.log.Info("Done")
for _, detail := range cveDetails {
cvePacksList = append(cvePacksList, CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
Packs: cvePackages[detail.CveID],
// CvssScore: cinfo.CvssScore(conf.Lang),
})
}
sort.Sort(CvePacksList(cvePacksList))
return
}
func (o *debian) scanPackageCveIds(pack models.PackageInfo) (cveIds []string, err error) {
cmd := ""
switch o.Family {
case "ubuntu":
cmd = fmt.Sprintf(`apt-get changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
case "debian":
cmd = fmt.Sprintf(`aptitude changelog %s | grep '\(urgency\|CVE\)'`, pack.Name)
}
cmd = util.PrependProxyEnv(cmd)
r := o.ssh(cmd, noSudo)
if !r.isSuccess() {
o.log.Warnf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
// Ignore this Error.
return nil, nil
}
cveIds, err = o.getCveIDParsingChangelog(r.Stdout, pack.Name, pack.Version)
if err != nil {
trimUbuntu := strings.Split(pack.Version, "ubuntu")[0]
return o.getCveIDParsingChangelog(r.Stdout, pack.Name, trimUbuntu)
}
return
}
func (o *debian) getCveIDParsingChangelog(changelog string,
packName string, versionOrLater string) (cveIDs []string, err error) {
cveIDs, err = o.parseChangelog(changelog, packName, versionOrLater)
if err == nil {
return
}
ver := strings.Split(versionOrLater, "ubuntu")[0]
cveIDs, err = o.parseChangelog(changelog, packName, ver)
if err == nil {
return
}
splittedByColon := strings.Split(versionOrLater, ":")
if 1 < len(splittedByColon) {
ver = splittedByColon[1]
}
cveIDs, err = o.parseChangelog(changelog, packName, ver)
if err == nil {
return
}
//TODO report as unable to parse changelog.
o.log.Warn(err)
return []string{}, nil
}
// Collect CVE-IDs included in the changelog.
// The version which specified in argument(versionOrLater) is excluded.
func (o *debian) parseChangelog(changelog string,
packName string, versionOrLater string) (cveIDs []string, err error) {
cveRe, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
stopRe, _ := regexp.Compile(fmt.Sprintf(`\(%s\)`, regexp.QuoteMeta(versionOrLater)))
stopLineFound := false
lines := strings.Split(changelog, "\n")
for _, line := range lines {
if matche := stopRe.MatchString(line); matche {
o.log.Debugf("Found the stop line. line: %s", line)
stopLineFound = true
break
} else if matches := cveRe.FindAllString(line, -1); len(matches) > 0 {
for _, m := range matches {
cveIDs = util.AppendIfMissing(cveIDs, m)
}
}
}
if !stopLineFound {
return []string{}, fmt.Errorf(
"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
packName,
versionOrLater,
)
}
return
}
type packCandidateVer struct {
Name string
Installed string
Candidate string
}
// parseAptCachePolicy the stdout of parse pat-get cache policy
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
ver := packCandidateVer{Name: name}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) != 2 {
continue
}
switch fields[0] {
case "Installed:":
ver.Installed = fields[1]
case "Candidate:":
ver.Candidate = fields[1]
return ver, nil
default:
// nop
}
}
return ver, fmt.Errorf("Unknown Format: %s", stdout)
}
func appendPackIfMissing(slice []models.PackageInfo, s models.PackageInfo) []models.PackageInfo {
for _, ele := range slice {
if ele.Name == s.Name &&
ele.Version == s.Version &&
ele.Release == s.Release {
return slice
}
}
return append(slice, s)
}

601
scan/debian_test.go Normal file
View File

@@ -0,0 +1,601 @@
/* 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 scan
import (
"reflect"
"testing"
"github.com/future-architect/vuls/config"
"github.com/k0kubun/pp"
)
func TestParseScanedPackagesLineDebian(t *testing.T) {
var packagetests = []struct {
in string
name string
version string
}{
{"base-passwd 3.5.33", "base-passwd", "3.5.33"},
{"bzip2 1.0.6-5", "bzip2", "1.0.6-5"},
{"adduser 3.113+nmu3ubuntu3", "adduser", "3.113+nmu3ubuntu3"},
{"bash 4.3-7ubuntu1.5", "bash", "4.3-7ubuntu1.5"},
{"bsdutils 1:2.20.1-5.1ubuntu20.4", "bsdutils", "1:2.20.1-5.1ubuntu20.4"},
{"ca-certificates 20141019ubuntu0.14.04.1", "ca-certificates", "20141019ubuntu0.14.04.1"},
{"apt 1.0.1ubuntu2.8", "apt", "1.0.1ubuntu2.8"},
}
d := newDebian(config.ServerInfo{})
for _, tt := range packagetests {
n, v, _ := d.parseScanedPackagesLine(tt.in)
if n != tt.name {
t.Errorf("name: expected %s, actual %s", tt.name, n)
}
if v != tt.version {
t.Errorf("version: expected %s, actual %s", tt.version, v)
}
}
}
func TestgetCveIDParsingChangelog(t *testing.T) {
var tests = []struct {
in []string
expected []string
}{
{
// verubuntu1
[]string{
"systemd",
"228-4ubuntu1",
`systemd (229-2) unstable; urgency=medium
systemd (229-1) unstable; urgency=medium
systemd (228-6) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
systemd (228-5) unstable; urgency=medium
systemd (228-4) unstable; urgency=medium
systemd (228-3) unstable; urgency=medium
systemd (228-2) unstable; urgency=medium
systemd (228-1) unstable; urgency=medium
systemd (227-3) unstable; urgency=medium
systemd (227-2) unstable; urgency=medium
systemd (227-1) unstable; urgency=medium`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
},
},
{
// ver
[]string{
"libpcre3",
"2:8.38-1ubuntu1",
`pcre3 (2:8.38-2) unstable; urgency=low
pcre3 (2:8.38-1) unstable; urgency=low
pcre3 (2:8.35-8) unstable; urgency=low
pcre3 (2:8.35-7.4) unstable; urgency=medium
pcre3 (2:8.35-7.3) unstable; urgency=medium
pcre3 (2:8.35-7.2) unstable; urgency=low
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
pcre3 (2:8.35-7.1) unstable; urgency=medium
pcre3 (2:8.35-7) unstable; urgency=medium`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
},
},
{
// ver-ubuntu3
[]string{
"sysvinit",
"2.88dsf-59.2ubuntu3",
`sysvinit (2.88dsf-59.3ubuntu1) xenial; urgency=low
sysvinit (2.88dsf-59.3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
sysvinit (2.88dsf-59.2ubuntu3) xenial; urgency=medium
sysvinit (2.88dsf-59.2ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.2ubuntu1) wily; urgency=medium
CVE-2015-2321: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.2) unstable; urgency=medium
sysvinit (2.88dsf-59.1ubuntu3) wily; urgency=medium
CVE-2015-2322: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59.1ubuntu2) wily; urgency=medium
sysvinit (2.88dsf-59.1ubuntu1) wily; urgency=medium
sysvinit (2.88dsf-59.1) unstable; urgency=medium
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
sysvinit (2.88dsf-59) unstable; urgency=medium
sysvinit (2.88dsf-58) unstable; urgency=low
sysvinit (2.88dsf-57) unstable; urgency=low`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
},
},
{
// 1:ver-ubuntu3
[]string{
"bsdutils",
"1:2.27.1-1ubuntu3",
` util-linux (2.27.1-3ubuntu1) xenial; urgency=medium
util-linux (2.27.1-3) unstable; urgency=medium
CVE-2015-2325: heap buffer overflow in compile_branch(). (Closes: #781795)
CVE-2015-2326: heap buffer overflow in pcre_compile2(). (Closes: #783285)
CVE-2015-3210: heap buffer overflow in pcre_compile2() /
util-linux (2.27.1-2) unstable; urgency=medium
util-linux (2.27.1-1ubuntu4) xenial; urgency=medium
util-linux (2.27.1-1ubuntu3) xenial; urgency=medium
util-linux (2.27.1-1ubuntu2) xenial; urgency=medium
util-linux (2.27.1-1ubuntu1) xenial; urgency=medium
util-linux (2.27.1-1) unstable; urgency=medium
util-linux (2.27-3ubuntu1) xenial; urgency=medium
util-linux (2.27-3) unstable; urgency=medium
util-linux (2.27-2) unstable; urgency=medium
util-linux (2.27-1) unstable; urgency=medium
util-linux (2.27~rc2-2) experimental; urgency=medium
util-linux (2.27~rc2-1) experimental; urgency=medium
util-linux (2.27~rc1-1) experimental; urgency=medium
util-linux (2.26.2-9) unstable; urgency=medium
util-linux (2.26.2-8) experimental; urgency=medium
util-linux (2.26.2-7) experimental; urgency=medium
util-linux (2.26.2-6ubuntu3) wily; urgency=medium
CVE-2015-2329: heap buffer overflow in compile_branch(). (Closes: #781795)
util-linux (2.26.2-6ubuntu2) wily; urgency=medium
util-linux (2.26.2-6ubuntu1) wily; urgency=medium
util-linux (2.26.2-6) unstable; urgency=medium`,
},
[]string{
"CVE-2015-2325",
"CVE-2015-2326",
"CVE-2015-3210",
},
},
}
d := newDebian(config.ServerInfo{})
for _, tt := range tests {
actual, _ := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], tt.in[1])
if len(actual) != len(tt.expected) {
t.Errorf("Len of return array are'nt same. expected %#v, actual %#v", tt.expected, actual)
continue
}
for i := range tt.expected {
if actual[i] != tt.expected[i] {
t.Errorf("expected %s, actual %s", tt.expected[i], actual[i])
}
}
}
for _, tt := range tests {
_, err := d.getCveIDParsingChangelog(tt.in[2], tt.in[0], "version number do'nt match case")
if err != nil {
t.Errorf("Returning error is unexpected.")
}
}
}
func TestGetUpdatablePackNames(t *testing.T) {
var tests = []struct {
in string
expected []string
}{
{ // Ubuntu 12.04
`Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages will be upgraded:
apt ca-certificates cpio dpkg e2fslibs e2fsprogs gnupg gpgv libc-bin libc6 libcomerr2 libpcre3
libpng12-0 libss2 libssl1.0.0 libudev0 multiarch-support openssl tzdata udev upstart
21 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Inst dpkg [1.16.1.2ubuntu7.5] (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
Conf dpkg (1.16.1.2ubuntu7.7 Ubuntu:12.04/precise-updates [amd64])
Inst upstart [1.5-0ubuntu7.2] (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
Inst libc-bin [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
Conf libc-bin (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64]) [libc6:amd64 ]
Inst libc6 [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
Conf libc6 (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
Inst libudev0 [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
Inst tzdata [2015a-0ubuntu0.12.04] (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
Conf tzdata (2015g-0ubuntu0.12.04 Ubuntu:12.04/precise-updates [all])
Inst e2fslibs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
Conf e2fslibs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64]) [e2fsprogs:amd64 ]
Inst e2fsprogs [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Conf e2fsprogs (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Inst gpgv [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
Conf gpgv (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
Inst gnupg [1.4.11-3ubuntu2.7] (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
Conf gnupg (1.4.11-3ubuntu2.9 Ubuntu:12.04/precise-updates [amd64])
Inst apt [0.8.16~exp12ubuntu10.22] (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
Conf apt (0.8.16~exp12ubuntu10.26 Ubuntu:12.04/precise-updates [amd64])
Inst libcomerr2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Conf libcomerr2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Inst libss2 [1.42-1ubuntu2] (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Conf libss2 (1.42-1ubuntu2.3 Ubuntu:12.04/precise-updates [amd64])
Inst libssl1.0.0 [1.0.1-4ubuntu5.21] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
Conf libssl1.0.0 (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
Inst libpcre3 [8.12-4] (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
Inst libpng12-0 [1.2.46-3ubuntu4] (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
Inst multiarch-support [2.15-0ubuntu10.10] (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
Conf multiarch-support (2.15-0ubuntu10.13 Ubuntu:12.04/precise-updates [amd64])
Inst cpio [2.11-7ubuntu3.1] (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
Inst udev [175-0ubuntu9.9] (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
Inst openssl [1.0.1-4ubuntu5.33] (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
Inst ca-certificates [20141019ubuntu0.12.04.1] (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])
Conf libudev0 (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
Conf upstart (1.5-0ubuntu7.3 Ubuntu:12.04/precise-updates [amd64])
Conf libpcre3 (8.12-4ubuntu0.1 Ubuntu:12.04/precise-updates [amd64])
Conf libpng12-0 (1.2.46-3ubuntu4.2 Ubuntu:12.04/precise-updates [amd64])
Conf cpio (2.11-7ubuntu3.2 Ubuntu:12.04/precise-updates [amd64])
Conf udev (175-0ubuntu9.10 Ubuntu:12.04/precise-updates [amd64])
Conf openssl (1.0.1-4ubuntu5.34 Ubuntu:12.04/precise-updates [amd64])
Conf ca-certificates (20160104ubuntu0.12.04.1 Ubuntu:12.04/precise-updates [all])`,
[]string{
"apt",
"ca-certificates",
"cpio",
"dpkg",
"e2fslibs",
"e2fsprogs",
"gnupg",
"gpgv",
"libc-bin",
"libc6",
"libcomerr2",
"libpcre3",
"libpng12-0",
"libss2",
"libssl1.0.0",
"libudev0",
"multiarch-support",
"openssl",
"tzdata",
"udev",
"upstart",
},
},
{ // Ubuntu 14.04
`Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
The following packages will be upgraded:
apt apt-utils base-files bsdutils coreutils cpio dh-python dpkg e2fslibs
e2fsprogs gcc-4.8-base gcc-4.9-base gnupg gpgv ifupdown initscripts iproute2
isc-dhcp-client isc-dhcp-common libapt-inst1.5 libapt-pkg4.12 libblkid1
libc-bin libc6 libcgmanager0 libcomerr2 libdrm2 libexpat1 libffi6 libgcc1
libgcrypt11 libgnutls-openssl27 libgnutls26 libmount1 libpcre3 libpng12-0
libpython3.4-minimal libpython3.4-stdlib libsqlite3-0 libss2 libssl1.0.0
libstdc++6 libtasn1-6 libudev1 libuuid1 login mount multiarch-support
ntpdate passwd python3.4 python3.4-minimal rsyslog sudo sysv-rc
sysvinit-utils tzdata udev util-linux
59 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Inst base-files [7.2ubuntu5.2] (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
Conf base-files (7.2ubuntu5.4 Ubuntu:14.04/trusty-updates [amd64])
Inst coreutils [8.21-1ubuntu5.1] (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
Conf coreutils (8.21-1ubuntu5.3 Ubuntu:14.04/trusty-updates [amd64])
Inst dpkg [1.17.5ubuntu5.3] (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
Conf dpkg (1.17.5ubuntu5.5 Ubuntu:14.04/trusty-updates [amd64])
Inst libc-bin [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Inst libc6 [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Inst libgcc1 [1:4.9.1-0ubuntu1] (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64]) []
Inst gcc-4.9-base [4.9.1-0ubuntu1] (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
Conf gcc-4.9-base (4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
Conf libgcc1 (1:4.9.3-0ubuntu4 Ubuntu:14.04/trusty-updates [amd64])
Conf libc6 (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Conf libc-bin (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Inst e2fslibs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 on e2fslibs:amd64] [e2fsprogs:amd64 ]
Conf e2fslibs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64]) [e2fsprogs:amd64 ]
Inst e2fsprogs [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Conf e2fsprogs (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Inst login [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
Conf login (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
Inst mount [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf mount (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst tzdata [2015a-0ubuntu0.14.04] (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
Conf tzdata (2015g-0ubuntu0.14.04 Ubuntu:14.04/trusty-updates [all])
Inst sysvinit-utils [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
Inst sysv-rc [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
Conf sysv-rc (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [all])
Conf sysvinit-utils (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
Inst util-linux [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf util-linux (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst gcc-4.8-base [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
Conf gcc-4.8-base (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64]) [libstdc++6:amd64 ]
Inst libstdc++6 [4.8.2-19ubuntu1] (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libstdc++6 (4.8.4-2ubuntu1~14.04.1 Ubuntu:14.04/trusty-updates [amd64])
Inst libapt-pkg4.12 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Conf libapt-pkg4.12 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Inst gpgv [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
Conf gpgv (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
Inst gnupg [1.4.16-1ubuntu2.1] (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
Conf gnupg (1.4.16-1ubuntu2.3 Ubuntu:14.04/trusty-updates [amd64])
Inst apt [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Conf apt (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Inst bsdutils [1:2.20.1-5.1ubuntu20.4] (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf bsdutils (1:2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst passwd [1:4.1.5.1-1ubuntu9] (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
Conf passwd (1:4.1.5.1-1ubuntu9.2 Ubuntu:14.04/trusty-updates [amd64])
Inst libuuid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf libuuid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst libblkid1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf libblkid1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst libcomerr2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libcomerr2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Inst libmount1 [2.20.1-5.1ubuntu20.4] (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Conf libmount1 (2.20.1-5.1ubuntu20.7 Ubuntu:14.04/trusty-updates [amd64])
Inst libpcre3 [1:8.31-2ubuntu2] (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libpcre3 (1:8.31-2ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
Inst libss2 [1.42.9-3ubuntu1] (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libss2 (1.42.9-3ubuntu1.3 Ubuntu:14.04/trusty-updates [amd64])
Inst libapt-inst1.5 [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Inst libexpat1 [2.1.0-4ubuntu1] (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
Inst libffi6 [3.1~rc1+r3.0.13-12] (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
Inst libgcrypt11 [1.5.3-2ubuntu4.1] (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
Inst libtasn1-6 [3.4-3ubuntu0.1] (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
Inst libgnutls-openssl27 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64]) []
Inst libgnutls26 [2.12.23-12ubuntu2.1] (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
Inst libsqlite3-0 [3.8.2-1ubuntu2] (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
Inst python3.4 [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
Inst libpython3.4-stdlib [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
Inst python3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64]) []
Inst libssl1.0.0 [1.0.1f-1ubuntu2.8] (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64]) []
Inst libpython3.4-minimal [3.4.0-2ubuntu1] (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
Inst ntpdate [1:4.2.6.p5+dfsg-3ubuntu2.14.04.2] (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
Inst libdrm2 [2.4.56-1~ubuntu2] (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
Inst libpng12-0 [1.2.50-1ubuntu2] (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
Inst initscripts [2.88dsf-41ubuntu6] (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
Inst libcgmanager0 [0.24-0ubuntu7.3] (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
Inst udev [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64]) []
Inst libudev1 [204-5ubuntu20.10] (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
Inst multiarch-support [2.19-0ubuntu6.5] (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Conf multiarch-support (2.19-0ubuntu6.7 Ubuntu:14.04/trusty-updates [amd64])
Inst apt-utils [1.0.1ubuntu2.6] (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Inst dh-python [1.20140128-1ubuntu8] (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
Inst iproute2 [3.12.0-2] (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
Inst ifupdown [0.7.47.2ubuntu4.1] (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
Inst isc-dhcp-client [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64]) []
Inst isc-dhcp-common [4.2.4-7ubuntu12] (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
Inst rsyslog [7.4.4-1ubuntu2.5] (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
Inst sudo [1.8.9p5-1ubuntu1] (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
Inst cpio [2.11+dfsg-1ubuntu1.1] (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
Conf libapt-inst1.5 (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Conf libexpat1 (2.1.0-4ubuntu1.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libffi6 (3.1~rc1+r3.0.13-12ubuntu0.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libgcrypt11 (1.5.3-2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libtasn1-6 (3.4-3ubuntu0.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libgnutls26 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
Conf libgnutls-openssl27 (2.12.23-12ubuntu2.4 Ubuntu:14.04/trusty-updates [amd64])
Conf libsqlite3-0 (3.8.2-1ubuntu2.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libssl1.0.0 (1.0.1f-1ubuntu2.16 Ubuntu:14.04/trusty-updates [amd64])
Conf libpython3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
Conf python3.4-minimal (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libpython3.4-stdlib (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
Conf python3.4 (3.4.3-1ubuntu1~14.04.3 Ubuntu:14.04/trusty-updates [amd64])
Conf ntpdate (1:4.2.6.p5+dfsg-3ubuntu2.14.04.8 Ubuntu:14.04/trusty-updates [amd64])
Conf libdrm2 (2.4.64-1~ubuntu14.04.1 Ubuntu:14.04/trusty-updates [amd64])
Conf libpng12-0 (1.2.50-1ubuntu2.14.04.2 Ubuntu:14.04/trusty-updates [amd64])
Conf initscripts (2.88dsf-41ubuntu6.3 Ubuntu:14.04/trusty-updates [amd64])
Conf libcgmanager0 (0.24-0ubuntu7.5 Ubuntu:14.04/trusty-updates [amd64])
Conf libudev1 (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
Conf udev (204-5ubuntu20.18 Ubuntu:14.04/trusty-updates [amd64])
Conf apt-utils (1.0.1ubuntu2.11 Ubuntu:14.04/trusty-updates [amd64])
Conf dh-python (1.20140128-1ubuntu8.2 Ubuntu:14.04/trusty-updates [all])
Conf iproute2 (3.12.0-2ubuntu1 Ubuntu:14.04/trusty-updates [amd64])
Conf ifupdown (0.7.47.2ubuntu4.3 Ubuntu:14.04/trusty-updates [amd64])
Conf isc-dhcp-common (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
Conf isc-dhcp-client (4.2.4-7ubuntu12.4 Ubuntu:14.04/trusty-updates [amd64])
Conf rsyslog (7.4.4-1ubuntu2.6 Ubuntu:14.04/trusty-updates [amd64])
Conf sudo (1.8.9p5-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
Conf cpio (2.11+dfsg-1ubuntu1.2 Ubuntu:14.04/trusty-updates [amd64])
`,
[]string{
"apt",
"apt-utils",
"base-files",
"bsdutils",
"coreutils",
"cpio",
"dh-python",
"dpkg",
"e2fslibs",
"e2fsprogs",
"gcc-4.8-base",
"gcc-4.9-base",
"gnupg",
"gpgv",
"ifupdown",
"initscripts",
"iproute2",
"isc-dhcp-client",
"isc-dhcp-common",
"libapt-inst1.5",
"libapt-pkg4.12",
"libblkid1",
"libc-bin",
"libc6",
"libcgmanager0",
"libcomerr2",
"libdrm2",
"libexpat1",
"libffi6",
"libgcc1",
"libgcrypt11",
"libgnutls-openssl27",
"libgnutls26",
"libmount1",
"libpcre3",
"libpng12-0",
"libpython3.4-minimal",
"libpython3.4-stdlib",
"libsqlite3-0",
"libss2",
"libssl1.0.0",
"libstdc++6",
"libtasn1-6",
"libudev1",
"libuuid1",
"login",
"mount",
"multiarch-support",
"ntpdate",
"passwd",
"python3.4",
"python3.4-minimal",
"rsyslog",
"sudo",
"sysv-rc",
"sysvinit-utils",
"tzdata",
"udev",
"util-linux",
},
},
{
//Ubuntu12.04
`Reading package lists... Done
Building dependency tree
Reading state information... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
[]string{},
},
{
//Ubuntu14.04
`Reading package lists... Done
Building dependency tree
Reading state information... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.`,
[]string{},
},
}
d := newDebian(config.ServerInfo{})
for _, tt := range tests {
actual, err := d.parseAptGetUpgrade(tt.in)
if err != nil {
t.Errorf("Returning error is unexpected.")
}
if len(tt.expected) != len(actual) {
t.Errorf("Result length is not as same as expected. expected: %d, actual: %d", len(tt.expected), len(actual))
pp.Println(tt.expected)
pp.Println(actual)
return
}
for i := range tt.expected {
if tt.expected[i] != actual[i] {
t.Errorf("[%d] expected %s, actual %s", i, tt.expected[i], actual[i])
}
}
}
}
func TestParseAptCachePolicy(t *testing.T) {
var tests = []struct {
stdout string
name string
expected packCandidateVer
}{
{
// Ubuntu 16.04
`openssl:
Installed: 1.0.2f-2ubuntu1
Candidate: 1.0.2g-1ubuntu2
Version table:
1.0.2g-1ubuntu2 500
500 http://archive.ubuntu.com/ubuntu xenial/main amd64 Packages
*** 1.0.2f-2ubuntu1 100
100 /var/lib/dpkg/status`,
"openssl",
packCandidateVer{
Name: "openssl",
Installed: "1.0.2f-2ubuntu1",
Candidate: "1.0.2g-1ubuntu2",
},
},
{
// Ubuntu 14.04
`openssl:
Installed: 1.0.1f-1ubuntu2.16
Candidate: 1.0.1f-1ubuntu2.17
Version table:
1.0.1f-1ubuntu2.17 0
500 http://archive.ubuntu.com/ubuntu/ trusty-updates/main amd64 Packages
500 http://archive.ubuntu.com/ubuntu/ trusty-security/main amd64 Packages
*** 1.0.1f-1ubuntu2.16 0
100 /var/lib/dpkg/status
1.0.1f-1ubuntu2 0
500 http://archive.ubuntu.com/ubuntu/ trusty/main amd64 Packages`,
"openssl",
packCandidateVer{
Name: "openssl",
Installed: "1.0.1f-1ubuntu2.16",
Candidate: "1.0.1f-1ubuntu2.17",
},
},
{
// Ubuntu 12.04
`openssl:
Installed: 1.0.1-4ubuntu5.33
Candidate: 1.0.1-4ubuntu5.34
Version table:
1.0.1-4ubuntu5.34 0
500 http://archive.ubuntu.com/ubuntu/ precise-updates/main amd64 Packages
500 http://archive.ubuntu.com/ubuntu/ precise-security/main amd64 Packages
*** 1.0.1-4ubuntu5.33 0
100 /var/lib/dpkg/status
1.0.1-4ubuntu3 0
500 http://archive.ubuntu.com/ubuntu/ precise/main amd64 Packages`,
"openssl",
packCandidateVer{
Name: "openssl",
Installed: "1.0.1-4ubuntu5.33",
Candidate: "1.0.1-4ubuntu5.34",
},
},
}
d := newDebian(config.ServerInfo{})
for _, tt := range tests {
actual, err := d.parseAptCachePolicy(tt.stdout, tt.name)
if err != nil {
t.Errorf("Error has occurred: %s, actual: %#v", err, actual)
}
if !reflect.DeepEqual(tt.expected, actual) {
e := pp.Sprintf("%v", tt.expected)
a := pp.Sprintf("%v", actual)
t.Errorf("expected %s, actual %s", e, a)
}
}
}

129
scan/linux.go Normal file
View File

@@ -0,0 +1,129 @@
/* 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 scan
import (
"sort"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
)
type linux struct {
ServerInfo config.ServerInfo
Family string
Release string
osPackages
log *logrus.Entry
}
func (l *linux) ssh(cmd string, sudo bool) sshResult {
return sshExec(l.ServerInfo, cmd, sudo, l.log)
}
func (l *linux) setServerInfo(c config.ServerInfo) {
l.ServerInfo = c
}
func (l *linux) getServerInfo() config.ServerInfo {
return l.ServerInfo
}
func (l *linux) setDistributionInfo(fam, rel string) {
l.Family = fam
l.Release = rel
}
func (l *linux) convertToModel() (models.ScanResult, error) {
var cves, unknownScoreCves []models.CveInfo
for _, p := range l.UnsecurePackages {
if p.CveDetail.CvssScore(config.Conf.Lang) < 0 {
unknownScoreCves = append(unknownScoreCves, models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
})
continue
}
cpenames := []models.CpeName{}
for _, cpename := range p.CpeNames {
cpenames = append(cpenames,
models.CpeName{Name: cpename})
}
cve := models.CveInfo{
CveDetail: p.CveDetail,
Packages: p.Packs,
DistroAdvisories: p.DistroAdvisories, // only Amazon Linux
CpeNames: cpenames,
}
cves = append(cves, cve)
}
return models.ScanResult{
ServerName: l.ServerInfo.ServerName,
Family: l.Family,
Release: l.Release,
KnownCves: cves,
UnknownCves: unknownScoreCves,
}, nil
}
// scanVulnByCpeName search vulnerabilities that specified in config file.
func (l *linux) scanVulnByCpeName() error {
unsecurePacks := CvePacksList{}
serverInfo := l.getServerInfo()
cpeNames := serverInfo.CpeNames
// remove duplicate
set := map[string]CvePacksInfo{}
for _, name := range cpeNames {
details, err := cveapi.CveClient.FetchCveDetailsByCpeName(name)
if err != nil {
return err
}
for _, detail := range details {
if val, ok := set[detail.CveID]; ok {
names := val.CpeNames
names = append(names, name)
val.CpeNames = names
set[detail.CveID] = val
} else {
set[detail.CveID] = CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
CpeNames: []string{name},
}
}
}
}
for key := range set {
unsecurePacks = append(unsecurePacks, set[key])
}
unsecurePacks = append(unsecurePacks, l.UnsecurePackages...)
sort.Sort(CvePacksList(unsecurePacks))
l.setUnsecurePackages(unsecurePacks)
return nil
}

18
scan/linux_test.go Normal file
View File

@@ -0,0 +1,18 @@
/* 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 scan

861
scan/redhat.go Normal file
View File

@@ -0,0 +1,861 @@
/* 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 scan
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/cveapi"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/k0kubun/pp"
)
// inherit OsTypeInterface
type redhat struct {
linux
}
// NewRedhat is constructor
func newRedhat(c config.ServerInfo) *redhat {
r := &redhat{}
r.log = util.NewCustomLogger(c)
return r
}
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb
func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) {
red = newRedhat(c)
// set sudo option flag
c.SudoOpt = config.SudoOption{ExecBySudoSh: true}
red.setServerInfo(c)
if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() {
red.setDistributionInfo("fedora", "unknown")
Log.Warn("Fedora not tested yet. Host: %s:%s", c.Host, c.Port)
return true, red
}
if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() {
// https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/
// e.g.
// $ cat /etc/redhat-release
// CentOS release 6.5 (Final)
if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() {
re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 3 {
Log.Warn(
"Failed to parse RedHat/CentOS version. stdout: %s, Host: %s:%s",
r.Stdout, c.Host, c.Port)
return true, red
}
release := result[2]
switch strings.ToLower(result[1]) {
case "centos", "centos linux":
red.setDistributionInfo("centos", release)
default:
red.setDistributionInfo("rhel", release)
}
return true, red
}
return true, red
}
if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() {
family := "amazon"
release := "unknown"
if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() {
fields := strings.Fields(r.Stdout)
if len(fields) == 5 {
release = fields[4]
}
}
red.setDistributionInfo(family, release)
return true, red
}
Log.Debugf("Not RedHat like Linux. Host: %s:%s", c.Host, c.Port)
return false, red
}
// CentOS 5 ... yum-plugin-security, yum-changelog
// CentOS 6 ... yum-plugin-security, yum-plugin-changelog
// CentOS 7 ... yum-plugin-security, yum-plugin-changelog
// RHEL, Amazon ... no additinal packages needed
func (o *redhat) install() error {
switch o.Family {
case "rhel", "amazon":
o.log.Infof("Nothing to do")
return nil
}
if err := o.installYumPluginSecurity(); err != nil {
return err
}
return o.installYumChangelog()
}
func (o *redhat) installYumPluginSecurity() error {
if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() {
o.log.Infof("Ignored: yum-plugin-security already installed")
return nil
}
cmd := util.PrependProxyEnv("yum install -y yum-plugin-security")
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
return nil
}
func (o *redhat) installYumChangelog() error {
o.log.Info("Installing yum-plugin-security...")
if o.Family == "centos" {
var majorVersion int
if 0 < len(o.Release) {
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
} else {
return fmt.Errorf(
"Not implemented yet. family: %s, release: %s",
o.Family, o.Release)
}
var packName = ""
if majorVersion < 6 {
packName = "yum-changelog"
} else {
packName = "yum-plugin-changelog"
}
cmd := "rpm -q " + packName
if r := o.ssh(cmd, noSudo); r.isSuccess() {
o.log.Infof("Ignored: %s already installed.", packName)
return nil
}
cmd = util.PrependProxyEnv("yum install -y " + packName)
if r := o.ssh(cmd, sudo); !r.isSuccess() {
return fmt.Errorf(
"Failed to install %s. status: %d, stdout: %s, stderr: %s",
packName, r.ExitStatus, r.Stdout, r.Stderr)
}
o.log.Infof("Installed: %s.", packName)
}
return nil
}
func (o *redhat) checkRequiredPackagesInstalled() error {
if config.Conf.UseYumPluginSecurity {
// check if yum-plugin-security is installed.
// Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security
cmd := "rpm -q yum-plugin-security"
if o.Family == "centos" {
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
msg := "yum-plugin-security is not installed"
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
return nil
}
if o.Family == "centos" {
var majorVersion int
if 0 < len(o.Release) {
majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0])
} else {
msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
var packName = ""
if majorVersion < 6 {
packName = "yum-changelog"
} else {
packName = "yum-plugin-changelog"
}
cmd := "rpm -q " + packName
if r := o.ssh(cmd, noSudo); !r.isSuccess() {
msg := fmt.Sprintf("%s is not installed", packName)
o.log.Errorf(msg)
return fmt.Errorf(msg)
}
}
return nil
}
func (o *redhat) scanPackages() error {
var err error
var packs []models.PackageInfo
if packs, err = o.scanInstalledPackages(); err != nil {
o.log.Errorf("Failed to scan installed packages")
return err
}
o.setPackages(packs)
var unsecurePacks []CvePacksInfo
if unsecurePacks, err = o.scanUnsecurePackages(); err != nil {
o.log.Errorf("Failed to scan valnerable packages")
return err
}
o.setUnsecurePackages(unsecurePacks)
return nil
}
func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) {
cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'"
r := o.ssh(cmd, noSudo)
if r.isSuccess() {
// e.g.
// openssl 1.0.1e 30.el6.11
lines := strings.Split(r.Stdout, "\n")
for _, line := range lines {
if trimed := strings.TrimSpace(line); len(trimed) != 0 {
var packinfo models.PackageInfo
if packinfo, err = o.parseScanedPackagesLine(line); err != nil {
return
}
installedPackages = append(installedPackages, packinfo)
}
}
return
}
return installedPackages, fmt.Errorf(
"Scan packages failed. status: %d, stdout: %s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
}
func (o *redhat) parseScanedPackagesLine(line string) (pack models.PackageInfo, err error) {
re, _ := regexp.Compile(`^([^\t']+)\t([^\t]+)\t(.+)$`)
result := re.FindStringSubmatch(line)
if len(result) == 4 {
pack.Name = result[1]
pack.Version = result[2]
pack.Release = strings.TrimSpace(result[3])
} else {
err = fmt.Errorf("redhat: Failed to parse package line: %s", line)
}
return
}
func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) {
if o.Family != "centos" || config.Conf.UseYumPluginSecurity {
// Amazon, RHEL has yum updateinfo as default
// yum updateinfo can collenct vendor advisory information.
return o.scanUnsecurePackagesUsingYumPluginSecurity()
}
// CentOS does not have security channel...
// So, yum check-update then parse chnagelog.
return o.scanUnsecurePackagesUsingYumCheckUpdate()
}
//TODO return whether already expired.
func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) {
cmd := "yum check-update"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
// get Updateble package name, installed, candidate version.
packInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%s", packInfoList))
// Collect CVE-IDs in changelog
type PackInfoCveIDs struct {
PackInfo models.PackageInfo
CveIDs []string
}
var results []PackInfoCveIDs
for i, packInfo := range packInfoList {
changelog, err := o.getChangelog(packInfo.Name)
if err != nil {
o.log.Errorf("Failed to collect CVE. err: %s", err)
return nil, err
}
// Collect unique set of CVE-ID in each changelog
uniqueCveIDMap := make(map[string]bool)
lines := strings.Split(changelog, "\n")
for _, line := range lines {
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
for _, c := range cveIDs {
uniqueCveIDMap[c] = true
}
}
// keys
var cveIDs []string
for k := range uniqueCveIDMap {
cveIDs = append(cveIDs, k)
}
p := PackInfoCveIDs{
PackInfo: packInfo,
CveIDs: cveIDs,
}
results = append(results, p)
o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s",
i+1,
len(packInfoList),
p.PackInfo.Name,
p.PackInfo.Version,
p.PackInfo.Release,
p.PackInfo.NewVersion,
p.PackInfo.NewRelease,
p.CveIDs)
}
// transform datastructure
// - From
// [
// {
// PackInfo: models.PackageInfo,
// CveIDs: []string,
// },
// ]
// - To
// map {
// CveID: []models.PackageInfo
// }
cveIDPackInfoMap := make(map[string][]models.PackageInfo)
for _, res := range results {
for _, cveID := range res.CveIDs {
// packInfo, found := o.Packages.FindByName(res.Packname)
// if !found {
// return CvePacksList{}, fmt.Errorf(
// "Faild to transform data structure: %v", res.Packname)
// }
cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo)
}
}
var uniqueCveIDs []string
for cveID := range cveIDPackInfoMap {
uniqueCveIDs = append(uniqueCveIDs, cveID)
}
// cveIDs => []cve.CveInfo
o.log.Info("Fetching CVE details...")
cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs)
if err != nil {
return nil, err
}
o.log.Info("Done")
cvePacksList := []CvePacksInfo{}
for _, detail := range cveDetails {
// Amazon, RHEL do not use this method, so VendorAdvisory do not set.
cvePacksList = append(cvePacksList, CvePacksInfo{
CveID: detail.CveID,
CveDetail: detail,
Packs: cveIDPackInfoMap[detail.CveID],
// CvssScore: cinfo.CvssScore(conf.Lang),
})
}
sort.Sort(CvePacksList(cvePacksList))
return cvePacksList, nil
}
// parseYumCheckUpdateLines parse yum check-update to get package name, candidate version
func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.PackageInfoList, err error) {
needToParse := false
lines := strings.Split(stdout, "\n")
for _, line := range lines {
// update information of packages begin after blank line.
if trimed := strings.TrimSpace(line); len(trimed) == 0 {
needToParse = true
continue
}
if needToParse {
candidate, err := o.parseYumCheckUpdateLine(line)
if err != nil {
return models.PackageInfoList{}, err
}
installed, found := o.Packages.FindByName(candidate.Name)
if !found {
return models.PackageInfoList{}, fmt.Errorf(
"Failed to parse yum check update line: %s-%s-%s",
candidate.Name, candidate.Version, candidate.Release)
}
installed.NewVersion = candidate.NewVersion
installed.NewRelease = candidate.NewRelease
results = append(results, installed)
}
}
return
}
func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) {
fields := strings.Fields(line)
if len(fields) != 3 {
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
}
splitted := strings.Split(fields[0], ".")
packName := ""
if len(splitted) == 1 {
packName = fields[0]
} else {
packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".")
}
fields = strings.Split(fields[1], "-")
if len(fields) != 2 {
return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line)
}
version := fields[0]
release := fields[1]
return models.PackageInfo{
Name: packName,
NewVersion: version,
NewRelease: release,
}, nil
}
func (o *redhat) getChangelog(packageNames string) (stdout string, err error) {
command := "echo N | "
if 0 < len(config.Conf.HTTPProxy) {
command += util.ProxyEnv()
}
command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames)
r := o.ssh(command, sudo)
if !r.isSuccess(0, 1) {
return "", fmt.Errorf(
"Failed to get changelog. status: %d, stdout: %s, stderr: %s",
r.ExitStatus, r.Stdout, r.Stderr)
}
return r.Stdout, nil
}
type distroAdvisoryCveIDs struct {
DistroAdvisory models.DistroAdvisory
CveIDs []string
}
// Scaning unsecure packages using yum-plugin-security.
//TODO return whether already expired.
func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) {
if o.Family == "centos" {
// CentOS has no security channel.
// So use yum check-update && parse changelog
return CvePacksList{}, fmt.Errorf(
"yum updateinfo is not suppported on CentOS")
}
cmd := "yum repolist"
r := o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
// get advisoryID(RHSA, ALAS) - package name,version
cmd = "yum updateinfo list available --security"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout)
// get package name, version, rel to be upgrade.
cmd = "yum check-update --security"
r = o.ssh(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess(0, 100) {
//returns an exit code of 100 if there are available updates.
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
vulnerablePackInfoList, err := o.parseYumCheckUpdateLines(r.Stdout)
if err != nil {
return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err)
}
o.log.Debugf("%s", pp.Sprintf("%s", vulnerablePackInfoList))
for i, packInfo := range vulnerablePackInfoList {
installedPack, found := o.Packages.FindByName(packInfo.Name)
if !found {
return nil, fmt.Errorf(
"Parsed package not found. packInfo: %#v", packInfo)
}
vulnerablePackInfoList[i].Version = installedPack.Version
vulnerablePackInfoList[i].Release = installedPack.Release
}
dict := map[string][]models.PackageInfo{}
for _, advIDPackNames := range advIDPackNamesList {
packInfoList := models.PackageInfoList{}
for _, packName := range advIDPackNames.PackNames {
packInfo, found := vulnerablePackInfoList.FindByName(packName)
if !found {
return nil, fmt.Errorf(
"PackInfo not found. packInfo: %#v", packName)
}
packInfoList = append(packInfoList, packInfo)
continue
}
dict[advIDPackNames.AdvisoryID] = packInfoList
}
// get advisoryID(RHSA, ALAS) - CVE IDs
cmd = "yum updateinfo --security update"
r = o.ssh(util.PrependProxyEnv(cmd), noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf(
"Failed to %s. status: %d, stdout: %s, stderr: %s",
cmd, r.ExitStatus, r.Stdout, r.Stderr)
}
advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout)
if err != nil {
return CvePacksList{}, err
}
// pp.Println(advisoryCveIDsList)
// All information collected.
// Convert to CvePacksList.
o.log.Info("Fetching CVE details...")
result := CvePacksList{}
for _, advIDCveIDs := range advisoryCveIDsList {
cveDetails, err :=
cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs)
if err != nil {
return nil, err
}
for _, cveDetail := range cveDetails {
found := false
for i, p := range result {
if cveDetail.CveID == p.CveID {
advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory)
result[i].DistroAdvisories = advAppended
packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID]
result[i].Packs = append(result[i].Packs, packs...)
found = true
break
}
}
if !found {
cpinfo := CvePacksInfo{
CveID: cveDetail.CveID,
CveDetail: cveDetail,
DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory},
Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID],
}
result = append(result, cpinfo)
}
}
}
o.log.Info("Done")
return result, nil
}
func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) {
sectionState := Outside
lines := strings.Split(stdout, "\n")
lines = append(lines, "=============")
// Amazon Linux AMI Security Information
advisory := models.DistroAdvisory{}
cveIDsSetInThisSection := make(map[string]bool)
// use this flag to Collect CVE IDs in CVEs field.
var inDesctiption = false
for _, line := range lines {
line = strings.TrimSpace(line)
// find the new section pattern
if match, _ := o.isHorizontalRule(line); match {
// set previous section's result to return-variable
if sectionState == Content {
foundCveIDs := []string{}
for cveID := range cveIDsSetInThisSection {
foundCveIDs = append(foundCveIDs, cveID)
}
sort.Strings(foundCveIDs)
result = append(result, distroAdvisoryCveIDs{
DistroAdvisory: advisory,
CveIDs: foundCveIDs,
})
// reset for next section.
cveIDsSetInThisSection = make(map[string]bool)
inDesctiption = false
}
// Go to next section
sectionState = o.changeSectionState(sectionState)
continue
}
switch sectionState {
case Header:
switch o.Family {
case "centos":
// CentOS has no security channel.
// So use yum check-update && parse changelog
return result, fmt.Errorf(
"yum updateinfo is not suppported on CentOS")
case "rhel", "amazon":
// nop
}
case Content:
if found := o.isDescriptionLine(line); found {
inDesctiption = true
}
// severity
severity, found := o.parseYumUpdateinfoToGetSeverity(line)
if found {
advisory.Severity = severity
}
// No need to parse in description except severity
if inDesctiption {
continue
}
cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line)
for _, cveID := range cveIDs {
cveIDsSetInThisSection[cveID] = true
}
advisoryID, found := o.parseYumUpdateinfoToGetAdvisoryID(line)
if found {
advisory.AdvisoryID = advisoryID
}
issued, found := o.parseYumUpdateinfoLineToGetIssued(line)
if found {
advisory.Issued = issued
}
updated, found := o.parseYumUpdateinfoLineToGetUpdated(line)
if found {
advisory.Updated = updated
}
}
}
return
}
// state
const (
Outside = iota
Header = iota
Content = iota
)
func (o *redhat) changeSectionState(state int) (newState int) {
switch state {
case Outside, Content:
newState = Header
case Header:
newState = Content
}
return newState
}
func (o *redhat) isHorizontalRule(line string) (bool, error) {
return regexp.MatchString("^=+$", line)
}
// see test case
func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) {
pkgs := strings.Split(strings.TrimSpace(line), ",")
for _, pkg := range pkgs {
packs = append(packs, models.PackageInfo{})
s := strings.Split(pkg, "-")
if len(s) == 3 {
packs[len(packs)-1].Name = s[0]
packs[len(packs)-1].Version = s[1]
packs[len(packs)-1].Release = s[2]
} else {
return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line)
}
}
return
}
func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) {
re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`)
result := re.FindStringSubmatch(line)
if len(result) == 4 {
a.AdvisoryID = result[1]
a.Severity = result[2]
spaceSeparatedPacknames := result[3]
names = strings.Fields(spaceSeparatedPacknames)
return
}
err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line)
return
}
func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string {
re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`)
return re.FindAllString(line, -1)
}
func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) {
re, _ := regexp.Compile(`^ *Update ID : (.*)$`)
result := re.FindStringSubmatch(line)
if len(result) != 2 {
return "", false
}
return strings.TrimSpace(result[1]), true
}
func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) {
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`)
}
func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) {
return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`)
}
func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) {
re, _ := regexp.Compile(regexpFormat)
result := re.FindStringSubmatch(line)
if len(result) != 2 {
return date, false
}
t, err := time.Parse("2006-01-02", result[1])
if err != nil {
return date, false
}
return t, true
}
func (o *redhat) isDescriptionLine(line string) bool {
re, _ := regexp.Compile(`^\s*Description : `)
return re.MatchString(line)
}
func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) {
re, _ := regexp.Compile(`^ *Severity : (.*)$`)
result := re.FindStringSubmatch(line)
if len(result) != 2 {
return "", false
}
return strings.TrimSpace(result[1]), true
}
type advisoryIDPacks struct {
AdvisoryID string
PackNames []string
}
type advisoryIDPacksList []advisoryIDPacks
func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool) {
for _, a := range list {
if a.AdvisoryID == advisoryID {
return a, true
}
}
return advisoryIDPacks{}, false
}
func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) {
fields := strings.Split(nameVerRel, ".")
archTrimed := strings.Join(fields[0:len(fields)-1], ".")
fields = strings.Split(archTrimed, "-")
rel = fields[len(fields)-1]
ver = fields[len(fields)-2]
name = strings.Join(fields[0:(len(fields)-2)], "-")
return
}
// parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages
func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) {
result := []advisoryIDPacks{}
lines := strings.Split(stdout, "\n")
for _, line := range lines {
if !(strings.HasPrefix(line, "RHSA") ||
strings.HasPrefix(line, "ALAS")) {
continue
}
fields := strings.Fields(line)
if len(fields) != 3 {
return []advisoryIDPacks{}, fmt.Errorf(
"Unknown format. line: %s", line)
}
// extract fields
advisoryID := fields[0]
packVersion := fields[2]
packName, _, _ := o.extractPackNameVerRel(packVersion)
found := false
for i, s := range result {
if s.AdvisoryID == advisoryID {
names := s.PackNames
names = append(names, packName)
result[i].PackNames = names
found = true
break
}
}
if !found {
result = append(result, advisoryIDPacks{
AdvisoryID: advisoryID,
PackNames: []string{packName},
})
}
}
return result, nil
}

843
scan/redhat_test.go Normal file
View File

@@ -0,0 +1,843 @@
/* 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 scan
import (
"reflect"
"testing"
"time"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
)
// func unixtimeNoerr(s string) time.Time {
// t, _ := unixtime(s)
// return t
// }
func TestParseScanedPackagesLineRedhat(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var packagetests = []struct {
in string
pack models.PackageInfo
}{
{
"openssl 1.0.1e 30.el6.11",
models.PackageInfo{
Name: "openssl",
Version: "1.0.1e",
Release: "30.el6.11",
},
},
}
for _, tt := range packagetests {
p, _ := r.parseScanedPackagesLine(tt.in)
if p.Name != tt.pack.Name {
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
}
if p.Version != tt.pack.Version {
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
}
if p.Release != tt.pack.Release {
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
}
}
}
func TestChangeSectionState(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
oldState int
newState int
}{
{Outside, Header},
{Header, Content},
{Content, Header},
}
for _, tt := range tests {
if n := r.changeSectionState(tt.oldState); n != tt.newState {
t.Errorf("expected %d, actual %d", tt.newState, n)
}
}
}
func TestParseYumUpdateinfoHeader(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out []models.PackageInfo
}{
{
" nodejs-0.10.36-3.el6,libuv-0.10.34-1.el6,v8-3.14.5.10-17.el6 ",
[]models.PackageInfo{
{
Name: "nodejs",
Version: "0.10.36",
Release: "3.el6",
},
{
Name: "libuv",
Version: "0.10.34",
Release: "1.el6",
},
{
Name: "v8",
Version: "3.14.5.10",
Release: "17.el6",
},
},
},
}
for _, tt := range tests {
if a, err := r.parseYumUpdateinfoHeaderCentOS(tt.in); err != nil {
t.Errorf("err: %s", err)
} else {
if !reflect.DeepEqual(a, tt.out) {
e := pp.Sprintf("%#v", tt.out)
a := pp.Sprintf("%#v", a)
t.Errorf("expected %s, actual %s", e, a)
}
}
}
}
func TestParseYumUpdateinfoLineToGetCveIDs(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out []string
}{
{
"Bugs : 1194651 - CVE-2015-0278 libuv:",
[]string{"CVE-2015-0278"},
},
{
": 1195457 - nodejs-0.10.35 causes undefined symbolsCVE-2015-0278, CVE-2015-0278, CVE-2015-0277",
[]string{
"CVE-2015-0278",
"CVE-2015-0278",
"CVE-2015-0277",
},
},
}
for _, tt := range tests {
act := r.parseYumUpdateinfoLineToGetCveIDs(tt.in)
for i, s := range act {
if s != tt.out[i] {
t.Errorf("expected %s, actual %s", tt.out[i], s)
}
}
}
}
func TestParseYumUpdateinfoToGetAdvisoryID(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out string
found bool
}{
{
"Update ID : RHSA-2015:2315",
"RHSA-2015:2315",
true,
},
{
"Update ID : ALAS-2015-620",
"ALAS-2015-620",
true,
},
{
"Issued : 2015-11-19 00:00:00",
"",
false,
},
}
for _, tt := range tests {
advisoryID, found := r.parseYumUpdateinfoToGetAdvisoryID(tt.in)
if tt.out != advisoryID {
t.Errorf("expected %s, actual %s", tt.out, advisoryID)
}
if tt.found != found {
t.Errorf("expected %t, actual %t", tt.found, found)
}
}
}
func TestParseYumUpdateinfoLineToGetIssued(t *testing.T) {
date, _ := time.Parse("2006-01-02", "2015-12-15")
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out time.Time
found bool
}{
{
"Issued : 2015-12-15 00:00:00",
date,
true,
},
{
" Issued : 2015-12-15 00:00:00 ",
date,
true,
},
{
"Type : security",
time.Time{},
false,
},
}
for i, tt := range tests {
d, found := r.parseYumUpdateinfoLineToGetIssued(tt.in)
if tt.found != found {
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
}
if tt.out != d {
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
}
}
}
func TestParseYumUpdateinfoLineToGetUpdated(t *testing.T) {
date, _ := time.Parse("2006-01-02", "2015-12-15")
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out time.Time
found bool
}{
{
"Updated : 2015-12-15 00:00:00 Bugs : 1286966 - CVE-2015-8370 grub2: buffer overflow when checking password entered during bootup",
date,
true,
},
{
"Updated : 2015-12-15 14:16 CVEs : CVE-2015-7981",
date,
true,
},
{
"Type : security",
time.Time{},
false,
},
}
for i, tt := range tests {
d, found := r.parseYumUpdateinfoLineToGetUpdated(tt.in)
if tt.found != found {
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
}
if tt.out != d {
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, d)
}
}
}
func TestIsDescriptionLine(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
found bool
}{
{
"Description : Package updates are available for Amazon Linux AMI that fix the",
true,
},
{
" Description : Package updates are available for Amazon Linux AMI that fix the",
true,
},
{
"Status : final",
false,
},
}
for i, tt := range tests {
found := r.isDescriptionLine(tt.in)
if tt.found != found {
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
}
}
}
func TestParseYumUpdateinfoToGetSeverity(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out string
found bool
}{
{
"Severity : Moderate",
"Moderate",
true,
},
{
" Severity : medium",
"medium",
true,
},
{
"Status : final",
"",
false,
},
}
for i, tt := range tests {
out, found := r.parseYumUpdateinfoToGetSeverity(tt.in)
if tt.found != found {
t.Errorf("[%d] line: %s, expected %t, actual %t", i, tt.in, tt.found, found)
}
if tt.out != out {
t.Errorf("[%d] line: %s, expected %v, actual %v", i, tt.in, tt.out, out)
}
}
}
func TestParseYumUpdateinfoRHEL(t *testing.T) {
stdout := `===============================================================================
Important: bind security update
===============================================================================
Update ID : RHSA-2015:1705
Release :
Type : security
Status : final
Issued : 2015-09-03 00:00:00
Bugs : 1259087 - CVE-2015-5722 bind: malformed DNSSEC key failed assertion denial of service
CVEs : CVE-2015-5722
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
: the Domain Name System (DNS) protocols. BIND
: includes a DNS server (named); a resolver library
: (routines for applications to use when interfacing
: with DNS); and tools for verifying that the DNS
: server is operating correctly.
:
Severity : Important
===============================================================================
Important: bind security update
===============================================================================
Update ID : RHSA-2015:2655
Release :
Type : security
Status : final
Issued : 2015-09-03 01:00:00
Updated : 2015-09-04 00:00:00
Bugs : 1291176 - CVE-2015-8000 bind: responses with a malformed class attribute can trigger an assertion failure in db.c
CVEs : CVE-2015-8000
: CVE-2015-8001
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
: the Domain Name System (DNS) protocols. BIND
: includes a DNS server (named); a resolver library
: (routines for applications to use when interfacing
: with DNS); and tools for verifying that the DNS
: server is operating correctly.
:
Severity : Low
===============================================================================
Moderate: bind security update
===============================================================================
Update ID : RHSA-2016:0073
Release :
Type : security
Status : final
Issued : 2015-09-03 02:00:00
Bugs : 1299364 - CVE-2015-8704 bind: specific APL data could trigger an INSIST in apl_42.c CVEs : CVE-2015-8704
: CVE-2015-8705
Description : The Berkeley Internet Name Domain (BIND) is an implementation of
: the Domain Name System (DNS) protocols. BIND
: includes a DNS server (named); a resolver library
: (routines for applications to use when interfacing
: with DNS); and tools for verifying that the DNS
: server is operating correctly.
:
Severity : Moderate
`
issued, _ := time.Parse("2006-01-02", "2015-09-03")
updated, _ := time.Parse("2006-01-02", "2015-09-04")
r := newRedhat(config.ServerInfo{})
r.Family = "redhat"
var tests = []struct {
in string
out []distroAdvisoryCveIDs
}{
{
stdout,
[]distroAdvisoryCveIDs{
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "RHSA-2015:1705",
Severity: "Important",
Issued: issued,
},
CveIDs: []string{"CVE-2015-5722"},
},
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "RHSA-2015:2655",
Severity: "Low",
Issued: issued,
Updated: updated,
},
CveIDs: []string{
"CVE-2015-8000",
"CVE-2015-8001",
},
},
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "RHSA-2016:0073",
Severity: "Moderate",
Issued: issued,
Updated: updated,
},
CveIDs: []string{
"CVE-2015-8704",
"CVE-2015-8705",
},
},
},
},
}
for _, tt := range tests {
actual, _ := r.parseYumUpdateinfo(tt.in)
for i, advisoryCveIDs := range actual {
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
e := pp.Sprintf("%v", tt.out[i])
a := pp.Sprintf("%v", advisoryCveIDs)
t.Errorf("[%d] Alas is not same. \nexpected: %s\nactual: %s",
i, e, a)
}
}
}
}
func TestParseYumUpdateinfoAmazon(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "amazon"
issued, _ := time.Parse("2006-01-02", "2015-12-15")
updated, _ := time.Parse("2006-01-02", "2015-12-16")
var tests = []struct {
in string
out []distroAdvisoryCveIDs
}{
{
`===============================================================================
Amazon Linux AMI 2014.03 - ALAS-2016-644: medium priority package update for python-rsa
===============================================================================
Update ID : ALAS-2016-644
Release :
Type : security
Status : final
Issued : 2015-12-15 13:30
CVEs : CVE-2016-1494
Description : Package updates are available for Amazon Linux AMI that fix the
: following vulnerabilities: CVE-2016-1494:
: 1295869:
: CVE-2016-1494 python-rsa: Signature forgery using
: Bleichenbacher'06 attack
Severity : medium
===============================================================================
Amazon Linux AMI 2014.03 - ALAS-2015-614: medium priority package update for openssl
===============================================================================
Update ID : ALAS-2015-614
Release :
Type : security
Status : final
Issued : 2015-12-15 10:00
Updated : 2015-12-16 14:15 CVEs : CVE-2015-3194
: CVE-2015-3195
: CVE-2015-3196
Description : Package updates are available for Amazon Linux AMI that fix the
: following vulnerabilities: CVE-2015-3196:
: 1288326:
: CVE-2015-3196 OpenSSL: Race condition handling PSK
: identify hint A race condition flaw, leading to a
: double free, was found in the way OpenSSL handled
: pre-shared keys (PSKs). A remote attacker could
: use this flaw to crash a multi-threaded SSL/TLS
: client.
:
Severity : medium`,
[]distroAdvisoryCveIDs{
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "ALAS-2016-644",
Severity: "medium",
Issued: issued,
},
CveIDs: []string{"CVE-2016-1494"},
},
{
DistroAdvisory: models.DistroAdvisory{
AdvisoryID: "ALAS-2015-614",
Severity: "medium",
Issued: issued,
Updated: updated,
},
CveIDs: []string{
"CVE-2015-3194",
"CVE-2015-3195",
"CVE-2015-3196",
},
},
},
},
}
for _, tt := range tests {
actual, _ := r.parseYumUpdateinfo(tt.in)
for i, advisoryCveIDs := range actual {
if !reflect.DeepEqual(tt.out[i], advisoryCveIDs) {
e := pp.Sprintf("%v", tt.out[i])
a := pp.Sprintf("%v", advisoryCveIDs)
t.Errorf("[%d] Alas is not same. expected %s, actual %s",
i, e, a)
}
}
}
}
func TestParseYumCheckUpdateLines(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "centos"
stdout := `Loaded plugins: changelog, fastestmirror, keys, protect-packages, protectbase, security
Loading mirror speeds from cached hostfile
* base: mirror.fairway.ne.jp
* epel: epel.mirror.srv.co.ge
* extras: mirror.fairway.ne.jp
* updates: mirror.fairway.ne.jp
0 packages excluded due to repository protections
audit-libs.x86_64 2.3.7-5.el6 base
bash.x86_64 4.1.2-33.el6_7.1 updates
`
r.Packages = []models.PackageInfo{
{
Name: "audit-libs",
Version: "2.3.6",
Release: "4.el6",
},
{
Name: "bash",
Version: "4.1.1",
Release: "33",
},
}
var tests = []struct {
in string
out models.PackageInfoList
}{
{
stdout,
models.PackageInfoList{
{
Name: "audit-libs",
Version: "2.3.6",
Release: "4.el6",
NewVersion: "2.3.7",
NewRelease: "5.el6",
},
{
Name: "bash",
Version: "4.1.1",
Release: "33",
NewVersion: "4.1.2",
NewRelease: "33.el6_7.1",
},
},
},
}
for _, tt := range tests {
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
for i, ePackInfo := range tt.out {
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
e := pp.Sprintf("%v", ePackInfo)
a := pp.Sprintf("%v", packInfoList[i])
t.Errorf("[%d] expected %s, actual %s", i, e, a)
}
}
}
}
func TestParseYumCheckUpdateLinesAmazon(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Family = "amzon"
stdout := `Loaded plugins: priorities, update-motd, upgrade-helper
34 package(s) needed for security, out of 71 available
bind-libs.x86_64 32:9.8.2-0.37.rc1.45.amzn1 amzn-main
java-1.7.0-openjdk.x86_64 1:1.7.0.95-2.6.4.0.65.amzn1 amzn-main
if-not-architecture 100-200 amzn-main
`
r.Packages = []models.PackageInfo{
{
Name: "bind-libs",
Version: "32:9.8.0",
Release: "0.33.rc1.45.amzn1",
},
{
Name: "java-1.7.0-openjdk",
Version: "1:1.7.0.0",
Release: "2.6.4.0.0.amzn1",
},
{
Name: "if-not-architecture",
Version: "10",
Release: "20",
},
}
var tests = []struct {
in string
out models.PackageInfoList
}{
{
stdout,
models.PackageInfoList{
{
Name: "bind-libs",
Version: "32:9.8.0",
Release: "0.33.rc1.45.amzn1",
NewVersion: "32:9.8.2",
NewRelease: "0.37.rc1.45.amzn1",
},
{
Name: "java-1.7.0-openjdk",
Version: "1:1.7.0.0",
Release: "2.6.4.0.0.amzn1",
NewVersion: "1:1.7.0.95",
NewRelease: "2.6.4.0.65.amzn1",
},
{
Name: "if-not-architecture",
Version: "10",
Release: "20",
NewVersion: "100",
NewRelease: "200",
},
},
},
}
for _, tt := range tests {
packInfoList, err := r.parseYumCheckUpdateLines(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
for i, ePackInfo := range tt.out {
if !reflect.DeepEqual(ePackInfo, packInfoList[i]) {
e := pp.Sprintf("%v", ePackInfo)
a := pp.Sprintf("%v", packInfoList[i])
t.Errorf("[%d] expected %s, actual %s", i, e, a)
}
}
}
}
func TestParseYumUpdateinfoAmazonLinuxHeader(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out models.DistroAdvisory
}{
{
"Amazon Linux AMI 2014.03 - ALAS-2015-598: low priority package update for grep",
models.DistroAdvisory{
AdvisoryID: "ALAS-2015-598",
Severity: "low",
},
},
}
for _, tt := range tests {
a, _, _ := r.parseYumUpdateinfoHeaderAmazon(tt.in)
if !reflect.DeepEqual(a, tt.out) {
e := pp.Sprintf("%v", tt.out)
a := pp.Sprintf("%v", a)
t.Errorf("expected %s, actual %s", e, a)
}
}
}
func TestParseYumUpdateinfoListAvailable(t *testing.T) {
r := newRedhat(config.ServerInfo{})
rhelStdout := `RHSA-2015:2315 Moderate/Sec. NetworkManager-1:1.0.6-27.el7.x86_64
RHSA-2015:2315 Moderate/Sec. NetworkManager-config-server-1:1.0.6-27.el7.x86_64
RHSA-2015:1705 Important/Sec. bind-libs-lite-32:9.9.4-18.el7_1.5.x86_64
RHSA-2016:0176 Critical/Sec. glibc-2.17-106.el7_2.4.x86_64
RHSA-2015:2401 Low/Sec. grub2-1:2.02-0.29.el7.x86_64
RHSA-2015:2401 Low/Sec. grub2-tools-1:2.02-0.29.el7.x86_64
updateinfo list done`
var tests = []struct {
in string
out []advisoryIDPacks
}{
{
rhelStdout,
[]advisoryIDPacks{
{
AdvisoryID: "RHSA-2015:2315",
PackNames: []string{
"NetworkManager",
"NetworkManager-config-server",
},
},
{
AdvisoryID: "RHSA-2015:1705",
PackNames: []string{
"bind-libs-lite",
},
},
{
AdvisoryID: "RHSA-2016:0176",
PackNames: []string{
"glibc",
},
},
{
AdvisoryID: "RHSA-2015:2401",
PackNames: []string{
"grub2",
"grub2-tools",
},
},
},
},
}
for _, tt := range tests {
actual, err := r.parseYumUpdateinfoListAvailable(tt.in)
if err != nil {
t.Errorf("Error has occurred: %s", err)
return
}
for i := range actual {
if !reflect.DeepEqual(actual[i], tt.out[i]) {
e := pp.Sprintf("%v", tt.out)
a := pp.Sprintf("%v", actual)
t.Errorf("[%d] expected: %s\nactual: %s", i, e, a)
}
}
}
}
func TestParseYumUpdateinfoToGetUpdateID(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var packagetests = []struct {
in string
pack models.PackageInfo
}{
{
"openssl 1.0.1e 30.el6.11",
models.PackageInfo{
Name: "openssl",
Version: "1.0.1e",
Release: "30.el6.11",
},
},
}
for _, tt := range packagetests {
p, _ := r.parseScanedPackagesLine(tt.in)
if p.Name != tt.pack.Name {
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
}
if p.Version != tt.pack.Version {
t.Errorf("version: expected %s, actual %s", tt.pack.Version, p.Version)
}
if p.Release != tt.pack.Release {
t.Errorf("release: expected %s, actual %s", tt.pack.Release, p.Release)
}
}
}
func TestExtractPackNameVerRel(t *testing.T) {
r := newRedhat(config.ServerInfo{})
var tests = []struct {
in string
out []string
}{
{
"openssh-server-6.2p2-8.45.amzn1.x86_64",
[]string{"openssh-server", "6.2p2", "8.45.amzn1"},
},
{
"bind-libs-lite-32:9.9.4-29.el7_2.1.x86_64",
[]string{"bind-libs-lite", "32:9.9.4", "29.el7_2.1"},
},
{
"glibc-2.17-106.el7_2.1.x86_64",
[]string{"glibc", "2.17", "106.el7_2.1"},
},
}
for _, tt := range tests {
name, ver, rel := r.extractPackNameVerRel(tt.in)
if tt.out[0] != name {
t.Errorf("name: expected %s, actual %s", tt.out[0], name)
}
if tt.out[1] != ver {
t.Errorf("ver: expected %s, actual %s", tt.out[1], ver)
}
if tt.out[2] != rel {
t.Errorf("ver: expected %s, actual %s", tt.out[2], rel)
}
}
}

209
scan/serverapi.go Normal file
View File

@@ -0,0 +1,209 @@
package scan
import (
"fmt"
"time"
"github.com/Sirupsen/logrus"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/k0kubun/pp"
cve "github.com/kotakanbe/go-cve-dictionary/models"
)
// Log for localhsot
var Log *logrus.Entry
var servers []osTypeInterface
// Base Interface of redhat, debian
type osTypeInterface interface {
setServerInfo(config.ServerInfo)
getServerInfo() config.ServerInfo
setDistributionInfo(string, string)
checkRequiredPackagesInstalled() error
scanPackages() error
scanVulnByCpeName() error
install() error
convertToModel() (models.ScanResult, error)
}
// osPackages included by linux struct
type osPackages struct {
// installed packages
Packages models.PackageInfoList
// unsecure packages
UnsecurePackages CvePacksList
}
func (p *osPackages) setPackages(pi models.PackageInfoList) {
p.Packages = pi
}
func (p *osPackages) setUnsecurePackages(pi []CvePacksInfo) {
p.UnsecurePackages = pi
}
// CvePacksList have CvePacksInfo list, getter/setter, sortable methods.
type CvePacksList []CvePacksInfo
// CvePacksInfo hold the CVE information.
type CvePacksInfo struct {
CveID string
CveDetail cve.CveDetail
Packs []models.PackageInfo
DistroAdvisories []models.DistroAdvisory // for Aamazon, RHEL
CpeNames []string
// CvssScore float64
}
// FindByCveID find by CVEID
func (s CvePacksList) FindByCveID(cveID string) (pi CvePacksInfo, found bool) {
for _, p := range s {
if cveID == p.CveID {
return p, true
}
}
return CvePacksInfo{CveID: cveID}, false
}
// immutable
func (s CvePacksList) set(cveID string, cvePacksInfo CvePacksInfo) CvePacksList {
for i, p := range s {
if cveID == p.CveID {
s[i] = cvePacksInfo
return s
}
}
return append(s, cvePacksInfo)
}
// Len implement Sort Interface
func (s CvePacksList) Len() int {
return len(s)
}
// Swap implement Sort Interface
func (s CvePacksList) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
// Less implement Sort Interface
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) {
var itsMe bool
itsMe, osType = detectDebian(c)
if itsMe {
return
}
itsMe, osType = detectRedhat(c)
return
}
// InitServers detect the kind of OS distribution of target servers
func InitServers(localLogger *logrus.Entry) (err error) {
Log = localLogger
if servers, err = detectServersOS(); err != nil {
err = fmt.Errorf("Failed to detect OS")
} else {
Log.Debugf("%s", pp.Sprintf("%s", servers))
}
return
}
func detectServersOS() (osi []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)
}(s)
}
timeout := time.After(60 * time.Second)
for i := 0; i < len(config.Conf.Servers); i++ {
select {
case res := <-osTypeChan:
osi = append(osi, res)
case <-timeout:
Log.Error("Timeout Occured while detecting OS.")
err = fmt.Errorf("Timeout!")
return
}
}
return
}
// Prepare installs requred packages to scan vulnerabilities.
func Prepare() []error {
return parallelSSHExec(func(o osTypeInterface) error {
if err := o.install(); err != nil {
return err
}
return nil
})
}
// Scan scan
func Scan() []error {
if len(servers) == 0 {
return []error{fmt.Errorf("Not initialize yet.")}
}
Log.Info("Check required packages for scanning...")
if errs := checkRequiredPackagesInstalled(); errs != nil {
Log.Error("Please execute with [prepare] subcommand to install required packages before scanning")
return errs
}
Log.Info("Scanning vuluneable OS packages...")
if errs := scanPackages(); errs != nil {
return errs
}
Log.Info("Scanning vulnerable software specified in CPE...")
if errs := scanVulnByCpeName(); errs != nil {
return errs
}
return nil
}
func checkRequiredPackagesInstalled() []error {
timeoutSec := 30 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.checkRequiredPackagesInstalled()
}, timeoutSec)
}
func scanPackages() []error {
timeoutSec := 30 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.scanPackages()
}, timeoutSec)
}
// scanVulnByCpeName search vulnerabilities that specified in config file.
func scanVulnByCpeName() []error {
timeoutSec := 30 * 60
return parallelSSHExec(func(o osTypeInterface) error {
return o.scanVulnByCpeName()
}, timeoutSec)
}
// GetScanResults returns Scan Resutls
func GetScanResults() (results models.ScanResults, err error) {
for _, s := range servers {
r, err := s.convertToModel()
if err != nil {
return results, fmt.Errorf("Failed converting to model: %s.", err)
}
results = append(results, r)
}
return
}

47
scan/serverapi_test.go Normal file
View File

@@ -0,0 +1,47 @@
package scan
import "testing"
func TestPackageCveInfosSetGet(t *testing.T) {
var test = struct {
in []string
out []string
}{
[]string{
"CVE1",
"CVE2",
"CVE3",
"CVE1",
"CVE1",
"CVE2",
"CVE3",
},
[]string{
"CVE1",
"CVE2",
"CVE3",
},
}
// var ps packageCveInfos
var ps CvePacksList
for _, cid := range test.in {
ps = ps.set(cid, CvePacksInfo{CveID: cid})
}
if len(test.out) != len(ps) {
t.Errorf("length: expected %d, actual %d", len(test.out), len(ps))
}
for i, expectedCid := range test.out {
if expectedCid != ps[i].CveID {
t.Errorf("expected %s, actual %s", expectedCid, ps[i].CveID)
}
}
for _, cid := range test.in {
p, _ := ps.FindByCveID(cid)
if p.CveID != cid {
t.Errorf("expected %s, actual %s", cid, p.CveID)
}
}
}

311
scan/sshutil.go Normal file
View File

@@ -0,0 +1,311 @@
/* 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 scan
import (
"bytes"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"net"
"os"
"strings"
"time"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
"github.com/Sirupsen/logrus"
"github.com/cenkalti/backoff"
conf "github.com/future-architect/vuls/config"
"github.com/k0kubun/pp"
)
type sshResult struct {
Host string
Port string
Stdout string
Stderr string
ExitStatus int
}
func (s sshResult) isSuccess(expectedStatusCodes ...int) bool {
if len(expectedStatusCodes) == 0 {
return s.ExitStatus == 0
}
for _, code := range expectedStatusCodes {
if code == s.ExitStatus {
return true
}
}
return false
}
// Sudo is Const value for sudo mode
const sudo = true
// NoSudo is Const value for normal user mode
const noSudo = false
func parallelSSHExec(fn func(osTypeInterface) error, timeoutSec ...int) (errs []error) {
errChan := make(chan error, len(servers))
defer close(errChan)
for _, s := range servers {
go func(s osTypeInterface) {
if err := fn(s); err != nil {
errChan <- fmt.Errorf("%s@%s:%s: %s",
s.getServerInfo().User,
s.getServerInfo().Host,
s.getServerInfo().Port,
err,
)
} else {
errChan <- nil
}
}(s)
}
var timeout int
if len(timeoutSec) == 0 {
timeout = 10 * 60
} else {
timeout = timeoutSec[0]
}
for i := 0; i < len(servers); i++ {
select {
case err := <-errChan:
if err != nil {
errs = append(errs, err)
} else {
logrus.Debug("Parallel SSH Success.")
}
case <-time.After(time.Duration(timeout) * time.Second):
logrus.Errorf("Parallel SSH Timeout.")
errs = append(errs, fmt.Errorf("Timed out!"))
}
}
return
}
func sshExec(c conf.ServerInfo, cmd string, sudo bool, log ...*logrus.Entry) (result sshResult) {
// Setup Logger
var logger *logrus.Entry
if len(log) == 0 {
level := logrus.InfoLevel
if conf.Conf.Debug == true {
level = logrus.DebugLevel
}
l := &logrus.Logger{
Out: os.Stderr,
Formatter: new(logrus.TextFormatter),
Hooks: make(logrus.LevelHooks),
Level: level,
}
logger = logrus.NewEntry(l)
} else {
logger = log[0]
}
var err error
if sudo && c.User != "root" {
switch {
case c.SudoOpt.ExecBySudo:
cmd = fmt.Sprintf("echo %s | sudo -S %s", c.Password, cmd)
case c.SudoOpt.ExecBySudoSh:
cmd = fmt.Sprintf("echo %s | sudo sh -c '%s'", c.Password, cmd)
default:
logger.Panicf("sudoOpt is invalid. SudoOpt: %v", c.SudoOpt)
}
}
// set pipefail option.
// http://unix.stackexchange.com/questions/14270/get-exit-status-of-process-thats-piped-to-another
cmd = fmt.Sprintf("set -o pipefail; %s", cmd)
logger.Debugf("Command: %s", strings.Replace(cmd, "\n", "", -1))
var client *ssh.Client
client, err = sshConnect(c)
defer client.Close()
var session *ssh.Session
if session, err = client.NewSession(); err != nil {
logger.Errorf("Failed to new session. err: %s, c: %s",
err,
pp.Sprintf("%v", c))
result.ExitStatus = 999
return
}
defer session.Close()
// http://blog.ralch.com/tutorial/golang-ssh-connection/
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err = session.RequestPty("xterm", 400, 120, modes); err != nil {
logger.Errorf("Failed to request for pseudo terminal. err: %s, c: %s",
err,
pp.Sprintf("%v", c))
result.ExitStatus = 999
return
}
var stdoutBuf, stderrBuf bytes.Buffer
session.Stdout = &stdoutBuf
session.Stderr = &stderrBuf
if err := session.Run(cmd); err != nil {
if exitErr, ok := err.(*ssh.ExitError); ok {
result.ExitStatus = exitErr.ExitStatus()
} else {
result.ExitStatus = 999
}
} else {
result.ExitStatus = 0
}
result.Stdout = stdoutBuf.String()
result.Stderr = stderrBuf.String()
result.Host = c.Host
result.Port = c.Port
logger.Debugf(
"SSH executed. cmd: %s, status: %#v\nstdout: \n%s\nstderr: \n%s",
cmd, err, result.Stdout, result.Stderr)
return
}
func getAgentAuth() (auth ssh.AuthMethod, ok bool) {
if sock := os.Getenv("SSH_AUTH_SOCK"); len(sock) > 0 {
if agconn, err := net.Dial("unix", sock); err == nil {
ag := agent.NewClient(agconn)
auth = ssh.PublicKeysCallback(ag.Signers)
ok = true
}
}
return
}
func tryAgentConnect(c conf.ServerInfo) *ssh.Client {
if auth, ok := getAgentAuth(); ok {
config := &ssh.ClientConfig{
User: c.User,
Auth: []ssh.AuthMethod{auth},
}
client, _ := ssh.Dial("tcp", c.Host+":"+c.Port, config)
return client
}
return nil
}
func sshConnect(c conf.ServerInfo) (client *ssh.Client, err error) {
if client = tryAgentConnect(c); client != nil {
return client, nil
}
var auths = []ssh.AuthMethod{}
if auths, err = addKeyAuth(auths, c.KeyPath, c.KeyPassword); err != nil {
logrus.Fatalf("Faild to add keyAuth. err: %s", err)
}
if c.Password != "" {
auths = append(auths, ssh.Password(c.Password))
}
// http://blog.ralch.com/tutorial/golang-ssh-connection/
config := &ssh.ClientConfig{
User: c.User,
Auth: auths,
}
// log.Debugf("config: %s", pp.Sprintf("%v", config))
notifyFunc := func(e error, t time.Duration) {
logrus.Warnf("Faild to ssh %s@%s:%s. err: %s, Retrying in %s...",
c.User, c.Host, c.Port, e, t)
logrus.Debugf("sshConInfo: %s", pp.Sprintf("%v", c))
}
err = backoff.RetryNotify(func() error {
if client, err = ssh.Dial("tcp", c.Host+":"+c.Port, config); err != nil {
return err
}
return nil
}, backoff.NewExponentialBackOff(), notifyFunc)
return
}
// https://github.com/rapidloop/rtop/blob/ba5b35e964135d50e0babedf0bd69b2fcb5dbcb4/src/sshhelper.go#L100
func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]ssh.AuthMethod, error) {
if len(keypath) == 0 {
return auths, nil
}
// read the file
pemBytes, err := ioutil.ReadFile(keypath)
if err != nil {
return auths, err
}
// get first pem block
block, _ := pem.Decode(pemBytes)
if block == nil {
return auths, fmt.Errorf("no key found in %s", keypath)
}
// handle plain and encrypted keyfiles
if x509.IsEncryptedPEMBlock(block) {
block.Bytes, err = x509.DecryptPEMBlock(block, []byte(keypassword))
if err != nil {
return auths, err
}
key, err := parsePemBlock(block)
if err != nil {
return auths, err
}
signer, err := ssh.NewSignerFromKey(key)
if err != nil {
return auths, err
}
return append(auths, ssh.PublicKeys(signer)), nil
}
signer, err := ssh.ParsePrivateKey(pemBytes)
if err != nil {
return auths, err
}
return append(auths, ssh.PublicKeys(signer)), nil
}
// ref golang.org/x/crypto/ssh/keys.go#ParseRawPrivateKey.
func parsePemBlock(block *pem.Block) (interface{}, error) {
switch block.Type {
case "RSA PRIVATE KEY":
return x509.ParsePKCS1PrivateKey(block.Bytes)
case "EC PRIVATE KEY":
return x509.ParseECPrivateKey(block.Bytes)
case "DSA PRIVATE KEY":
return ssh.ParseDSAPrivateKey(block.Bytes)
default:
return nil, fmt.Errorf("rtop: unsupported key type %q", block.Type)
}
}