Skip to content

Commit

Permalink
Support SUSE Enterprise Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
kotakanbe committed Sep 14, 2017
1 parent 820831f commit daa265f
Show file tree
Hide file tree
Showing 8 changed files with 458 additions and 1 deletion.
15 changes: 15 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ const (

// Windows is
Windows = "windows"

// OpenSUSE is
OpenSUSE = "opensuse"

// OpenSUSELeap is
OpenSUSELeap = "opensuse.leap"

// SUSEEnterpriseServer is
SUSEEnterpriseServer = "suse.linux.enterprise.server"

// SUSEEnterpriseDesktop is
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"

// SUSEOpenstackCloud is
SUSEOpenstackCloud = "suse.openstack.cloud"
)

//Config is struct of Configuration
Expand Down
3 changes: 3 additions & 0 deletions models/cvecontents.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ const (
// Oracle is Oracle Linux
Oracle CveContentType = "oracle"

// SUSE is SUSE Linux
SUSE CveContentType = "suse"

// Unknown is Unknown
Unknown CveContentType = "unknown"
)
Expand Down
150 changes: 150 additions & 0 deletions oval/suse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* 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 oval

import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/k0kubun/pp"
ovalmodels "github.com/kotakanbe/goval-dictionary/models"
)

// SUSE is the struct of SUSE Linux
type SUSE struct {
Base
}

// NewSUSE creates OVAL client for SUSE
func NewSUSE() SUSE {
// TODO implement other family
return SUSE{
Base{
family: config.SUSEEnterpriseServer,
},
}
}

// FillWithOval returns scan result after updating CVE info by OVAL
func (o SUSE) FillWithOval(r *models.ScanResult) (err error) {
// TODO
//Debian's uname gives both of kernel release(uname -r), version(kernel-image version)
// linuxImage := "linux-image-" + r.RunningKernel.Release
// // Add linux and set the version of running kernel to search OVAL.
// if r.Container.ContainerID == "" {
// r.Packages["linux"] = models.Package{
// Name: "linux",
// Version: r.RunningKernel.Version,
// }
// }

var relatedDefs ovalResult
if o.isFetchViaHTTP() {
if relatedDefs, err = getDefsByPackNameViaHTTP(r); err != nil {
return err
}
} else {
if relatedDefs, err = getDefsByPackNameFromOvalDB(o.family, r.Release, r.Packages); err != nil {
return err
}
}
pp.Println(relatedDefs)

//TODO
// delete(r.Packages, "linux")

for _, defPacks := range relatedDefs.entries {
//TODO
// Remove linux added above to search for oval
// linux is not a real package name (key of affected packages in OVAL)
// if _, ok := defPacks.actuallyAffectedPackNames["linux"]; ok {
// defPacks.actuallyAffectedPackNames[linuxImage] = true
// delete(defPacks.actuallyAffectedPackNames, "linux")
// for i, p := range defPacks.def.AffectedPacks {
// if p.Name == "linux" {
// p.Name = linuxImage
// defPacks.def.AffectedPacks[i] = p
// }
// }
// }
o.update(r, defPacks)
}

for _, vuln := range r.ScannedCves {
if cont, ok := vuln.CveContents[models.SUSE]; ok {
//TODO
cont.SourceLink = "https://security-tracker.debian.org/tracker/" + cont.CveID
vuln.CveContents[models.SUSE] = cont
}
}
return nil
}

func (o SUSE) update(r *models.ScanResult, defPacks defPacks) {
ovalContent := *o.convertToModel(&defPacks.def)
ovalContent.Type = models.NewCveContentType(o.family)
vinfo, ok := r.ScannedCves[defPacks.def.Title]
if !ok {
util.Log.Debugf("%s is newly detected by OVAL", defPacks.def.Title)
vinfo = models.VulnInfo{
CveID: defPacks.def.Title,
Confidence: models.OvalMatch,
CveContents: models.NewCveContents(ovalContent),
}
} else {
cveContents := vinfo.CveContents
ctype := models.NewCveContentType(o.family)
if _, ok := vinfo.CveContents[ctype]; ok {
util.Log.Debugf("%s OVAL will be overwritten", defPacks.def.Title)
} else {
util.Log.Debugf("%s is also detected by OVAL", defPacks.def.Title)
cveContents = models.CveContents{}
}
if vinfo.Confidence.Score < models.OvalMatch.Score {
vinfo.Confidence = models.OvalMatch
}
cveContents[ctype] = ovalContent
vinfo.CveContents = cveContents
}

// uniq(vinfo.PackNames + defPacks.actuallyAffectedPackNames)
for _, pack := range vinfo.AffectedPackages {
defPacks.actuallyAffectedPackNames[pack.Name] = true
}
vinfo.AffectedPackages = defPacks.toPackStatuses(r.Family, r.Packages)
vinfo.AffectedPackages.Sort()
r.ScannedCves[defPacks.def.Title] = vinfo
}

func (o SUSE) convertToModel(def *ovalmodels.Definition) *models.CveContent {
var refs []models.Reference
for _, r := range def.References {
refs = append(refs, models.Reference{
Link: r.RefURL,
Source: r.Source,
RefID: r.RefID,
})
}

return &models.CveContent{
CveID: def.Title,
Title: def.Title,
Summary: def.Description,
References: refs,
}
}
4 changes: 3 additions & 1 deletion oval/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,12 @@ func lessThan(family string, packA models.Package, packB ovalmodels.Package) (bo
return false, err
}
return vera.LessThan(verb), nil
case config.RedHat, config.CentOS, config.Oracle:
case config.RedHat, config.CentOS, config.Oracle, config.SUSEEnterpriseServer:
vera := rpmver.NewVersion(fmt.Sprintf("%s-%s", packA.Version, packA.Release))
verb := rpmver.NewVersion(packB.Version)
return vera.LessThan(verb), nil
default:
util.Log.Errorf("Not implemented yet: %s", family)
}
return false, fmt.Errorf("Package version comparison not supported: %s", family)
}
4 changes: 4 additions & 0 deletions report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ func FillWithOval(r *models.ScanResult) (err error) {
case c.Oracle:
ovalClient = oval.NewOracle()
ovalFamily = c.Oracle
case c.SUSEEnterpriseServer:
// TODO other suse family
ovalClient = oval.NewSUSE()
ovalFamily = c.SUSEEnterpriseServer
case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows:
return nil
default:
Expand Down
5 changes: 5 additions & 0 deletions scan/serverapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) {
return
}

if itsMe, osType = detectSUSE(c); itsMe {
util.Log.Debugf("SUSE Linux. Host: %s:%s", c.Host, c.Port)
return
}

if itsMe, osType = detectFreebsd(c); itsMe {
util.Log.Debugf("FreeBSD. Host: %s:%s", c.Host, c.Port)
return
Expand Down
172 changes: 172 additions & 0 deletions scan/suse.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package scan

import (
"bufio"
"fmt"
"regexp"
"strings"

"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)

// inherit OsTypeInterface
type suse struct {
redhat
}

// NewRedhat is constructor
func newSUSE(c config.ServerInfo) *suse {
r := &suse{
redhat: redhat{
base: base{
osPackages: osPackages{
Packages: models.Packages{},
VulnInfos: models.VulnInfos{},
},
},
},
}
r.log = util.NewCustomLogger(c)
r.setServerInfo(c)
return r
}

// https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/suse.rb
func detectSUSE(c config.ServerInfo) (itsMe bool, suse osTypeInterface) {
suse = newSUSE(c)

if r := exec(c, "ls /etc/os-release", noSudo); r.isSuccess() {
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/os-release", noSudo); r.isSuccess() {
name := ""
if strings.Contains(r.Stdout, "ID=opensuse") {
//TODO check opensuse or opensuse.leap
name = config.OpenSUSE
} else if strings.Contains(r.Stdout, `NAME="SLES"`) {
//TODO check suse.enterprise.server or suse.enterprise.desktop
name = config.SUSEEnterpriseServer
} else {
util.Log.Warn("Failed to parse SUSE edition: %s", r)
return true, suse
}

re := regexp.MustCompile(`VERSION_ID=\"(\d+\.\d+|\d+)\"`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) != 2 {
util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
return true, suse
}
suse.setDistro(name, result[1])
return true, suse
}
}
} else if r := exec(c, "ls /etc/SuSE-release", noSudo); r.isSuccess() {
if r := exec(c, "zypper -V", noSudo); r.isSuccess() {
if r := exec(c, "cat /etc/SuSE-release", noSudo); r.isSuccess() {
re := regexp.MustCompile(`SUSE Linux Enterprise Server (\d+)`)
result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
//TODO check suse.enterprise.server or suse.enterprise.desktop
suse.setDistro(config.SUSEEnterpriseServer, result[1])
return true, suse
}

re = regexp.MustCompile(`openSUSE (\d+\.\d+|\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
//TODO check opensuse or opensuse.leap
suse.setDistro(config.OpenSUSE, result[1])
return true, suse
}

util.Log.Warn("Failed to parse SUSE Linux version: %s", r)
return true, suse
}
}
}
util.Log.Debugf("Not SUSE Linux. servername: %s", c.ServerName)
return false, suse
}

func (o *suse) checkDependencies() error {
o.log.Infof("Dependencies... No need")
return nil
}

func (o *suse) checkIfSudoNoPasswd() error {
// SUSE doesn't need root privilege
o.log.Infof("sudo ... No need")
return nil
}

func (o *suse) scanPackages() error {
installed, err := o.scanInstalledPackages()
if err != nil {
o.log.Errorf("Failed to scan installed packages: %s", err)
return err
}

rebootRequired, err := o.rebootRequired()
if err != nil {
o.log.Errorf("Failed to detect the kernel reboot required: %s", err)
return err
}
o.Kernel.RebootRequired = rebootRequired

updatable, err := o.scanUpdatablePackages()
if err != nil {
o.log.Errorf("Failed to scan updatable packages: %s", err)
return err
}
installed.MergeNewVersion(updatable)
o.Packages = installed

return nil
}

//TODO
func (o *suse) rebootRequired() (bool, error) {
return false, nil
}

func (o *suse) scanUpdatablePackages() (models.Packages, error) {
r := o.exec("zypper --no-color -q lu", noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Failed to scan updatable packages: %v", r)
}
return o.parseZypperLULines(r.Stdout)
}

func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) {
updatables := models.Packages{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "S | Repository") ||
strings.HasPrefix(line, "--+----------------") {
continue
}
pack, err := o.parseZypperLUOneLine(line)
if err != nil {
return nil, err
}
updatables[pack.Name] = *pack
}
return updatables, nil
}

func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) {
fs := strings.Fields(line)
if len(fs) != 11 {
return nil, fmt.Errorf("zypper -q lu Unknown format: %s", line)
}
available := strings.Split(fs[8], "-")
return &models.Package{
Name: fs[4],
NewVersion: available[0],
NewRelease: available[1],
Arch: fs[10],
}, nil
}

0 comments on commit daa265f

Please sign in to comment.