Support SUSE Enterprise Linux (#487)

* Support SUSE Enterprise Linux

* Implement Reboot Required detection on SLES

* Fix query OVAL because SUSE provides OVAL data each major.minor version

* Update README

* Support SUSE Enterprise 11
This commit is contained in:
Kota Kanbe
2017-09-28 12:23:19 +09:00
committed by GitHub
parent e5eb8e42f5
commit 132432dce6
15 changed files with 689 additions and 67 deletions

62
Gopkg.lock generated
View File

@@ -4,14 +4,14 @@
[[projects]]
name = "github.com/Azure/azure-sdk-for-go"
packages = ["storage"]
revision = "57db66900881e9fd21fd041a9d013514700ecab3"
version = "v10.3.0-beta"
revision = "df4dd90d076ebbf6e87d08d3f00bfac8ff4bde1a"
version = "v10.3.1-beta"
[[projects]]
name = "github.com/Azure/go-autorest"
packages = ["autorest","autorest/adal","autorest/azure","autorest/date"]
revision = "77a52603f06947221c672f10275abc9bf2c7d557"
version = "v8.3.0"
revision = "f6be1abbb5abd0517522f850dd785990d373da7e"
version = "v8.4.0"
[[projects]]
name = "github.com/BurntSushi/toml"
@@ -28,8 +28,8 @@
[[projects]]
name = "github.com/aws/aws-sdk-go"
packages = ["aws","aws/awserr","aws/awsutil","aws/client","aws/client/metadata","aws/corehandlers","aws/credentials","aws/credentials/ec2rolecreds","aws/credentials/endpointcreds","aws/credentials/stscreds","aws/defaults","aws/ec2metadata","aws/endpoints","aws/request","aws/session","aws/signer/v4","internal/shareddefaults","private/protocol","private/protocol/query","private/protocol/query/queryutil","private/protocol/rest","private/protocol/restxml","private/protocol/xml/xmlutil","service/s3","service/sts"]
revision = "264af29009637e0a9e5d4a276d0969c3ed918ffd"
version = "v1.10.29"
revision = "b69f447375c7fa0047ebcdd8ae5d585d5aac2f71"
version = "v1.10.51"
[[projects]]
name = "github.com/boltdb/bolt"
@@ -46,8 +46,8 @@
[[projects]]
name = "github.com/cheggaaa/pb"
packages = ["."]
revision = "0d6285554e726cc0620cbecc7e6969c945dcc63b"
version = "v1.0.17"
revision = "657164d0228d6bebe316fdf725c69f131a50fb10"
version = "v1.0.18"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
@@ -64,8 +64,8 @@
[[projects]]
name = "github.com/go-redis/redis"
packages = [".","internal","internal/consistenthash","internal/hashtag","internal/pool","internal/proto"]
revision = "19c1c2272e00c1aaa903cf574c746cd449f9cd3c"
version = "v6.5.7"
revision = "975882d73d21759d45a4eb49652064083bc23e61"
version = "v6.7.0"
[[projects]]
name = "github.com/go-sql-driver/mysql"
@@ -137,7 +137,7 @@
branch = "master"
name = "github.com/kotakanbe/go-cve-dictionary"
packages = ["config","db","jvn","log","models","nvd","util"]
revision = "c20fa7e1d07f7c700baf12c855f7fcf61525f1b6"
revision = "f5406ffe8226f01f64544723339c6a17b2bd74af"
[[projects]]
name = "github.com/kotakanbe/go-pingscanner"
@@ -149,7 +149,7 @@
branch = "master"
name = "github.com/kotakanbe/goval-dictionary"
packages = ["config","db","db/rdb","log","models"]
revision = "3523cc174e68f285d0572d07c68ffa3a9290799c"
revision = "a9de1f6e9126c5e75b46b39e4049624cde8c8bb4"
[[projects]]
branch = "master"
@@ -157,17 +157,11 @@
packages = ["."]
revision = "75edb2e85a38873f0318be05a458446681d1022f"
[[projects]]
name = "github.com/labstack/gommon"
packages = ["color","log"]
revision = "779b8a8b9850a97acba6a3fe20feb628c39e17c1"
version = "0.2.2"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [".","hstore","oid"]
revision = "e42267488fe361b9dc034be7a6bffef5b195bceb"
revision = "b77235e3890a962fe8a6f8c4c7198679ca7814e7"
[[projects]]
name = "github.com/mattn/go-colorable"
@@ -178,8 +172,8 @@
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
version = "v0.0.2"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
name = "github.com/mattn/go-runewidth"
@@ -203,7 +197,7 @@
branch = "master"
name = "github.com/moul/http2curl"
packages = ["."]
revision = "4e24498b31dba4683efb9d35c1c8a91e2eda28c8"
revision = "9ac6cf4d929b2fa8fd2d2e6dec5bb0feb4f4911d"
[[projects]]
branch = "master"
@@ -239,19 +233,7 @@
branch = "master"
name = "github.com/sirupsen/logrus"
packages = ["."]
revision = "84573d5f03ab3740f524c7842c3a9bf617961d32"
[[projects]]
branch = "master"
name = "github.com/valyala/bytebufferpool"
packages = ["."]
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
[[projects]]
branch = "master"
name = "github.com/valyala/fasttemplate"
packages = ["."]
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
revision = "89742aefa4b206dcf400792f3bd35b542998eb3b"
[[projects]]
branch = "master"
@@ -263,25 +245,25 @@
branch = "master"
name = "golang.org/x/crypto"
packages = ["curve25519","ed25519","ed25519/internal/edwards25519","ssh","ssh/agent","ssh/terminal"]
revision = "eb71ad9bd329b5ac0fd0148dd99bd62e8be8e035"
revision = "847319b7fc94cab682988f93da778204da164588"
[[projects]]
branch = "master"
name = "golang.org/x/net"
packages = ["context","idna","publicsuffix"]
revision = "1c05540f6879653db88113bc4a2b70aec4bd491f"
revision = "0744d001aa8470aaa53df28d32e5ceeb8af9bd70"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "07c182904dbd53199946ba614a412c61d3c548f5"
revision = "429f518978ab01db8bb6f44b66785088e7fba58b"
[[projects]]
branch = "master"
name = "golang.org/x/text"
packages = ["internal/gen","internal/triegen","internal/ucd","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "e56139fd9c5bc7244c76116c68e500765bb6db6b"
packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"]
revision = "1cbadb444a806fd9430d14ad08967ed91da4fa0a"
[solve-meta]
analyzer-name = "dep"

View File

@@ -139,7 +139,7 @@ Vulsは上に挙げた手動運用での課題を解決するツールであり
# Main Features
- サーバに存在する脆弱性をスキャン
- FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Raspbianに対応
- FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise, Raspbianに対応
- クラウド、オンプレミス、Docker
- 高精度なスキャン
- Vulsは複数の脆弱性データベース、複数の検知方法を組み合わせることで高精度なスキャンを実現している
@@ -328,6 +328,7 @@ $ goval-dictionary fetch-redhat 7
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Oracle Linux](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
## Step5. Deploy Vuls
@@ -586,9 +587,10 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ
| Oracle | Fast |  No | Supported | No |
| Ubuntu | Fast |  No | Supported | No |
| Debian | Fast |  No | Supported | No |
| Raspbian |1st time: Slow <br> From 2nd time: Fast | Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast | Need | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No|
----
@@ -604,22 +606,26 @@ Vulsをスキャン対象サーバにデプロイする。Vulsはローカルホ
| Oracle | Slow |  Need | Supported | Need |
| Ubuntu |1st time: Slow <br> From 2nd time: Fast| Need | Supported | Need |
| Debian |1st time: Slow <br> From 2nd time: Fast| Need | Supported | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Slow |  No | No | Need |
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No|
- Ubuntu, Debian, Raspbian
- On Ubuntu, Debian and Raspbian
`apt-get changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。
アップデート対象のパッケージが沢山ある場合、チェンジログの取得に時間がかかるので、初回のスキャンは遅い。
ただ、回目以降はキャッシュしたchangelogを使うので速くなる。
- CentOS
- On CentOS
`yum changelog`でアップデート対象のパッケージのチェンジログを取得し、含まれるCVE IDをパースする。
- Amazon, RHEL and FreeBSD
- On RHEL, Oracle, Amazon and FreeBSD
`yum changelog`でアップデート対象のパッケージのチェンジログを取得する(パースはしない)。
- On SUSE Enterprise Linux
Same as fast scan mode for now.
----
# Use Cases
@@ -646,6 +652,7 @@ web/app server in the same configuration under the load balancer
| CentOS | 6, 7|
| Amazon Linux| All|
| FreeBSD | 10, 11|
| SUSE Enterprise | 11, 12|
| Raspbian | Jessie, Stretch |
----
@@ -882,6 +889,7 @@ configtestサブコマンドは、config.tomlで定義されたサーバ/コン
| Amazon | All | - |
| RHEL | 5, 6, 7 | - |
| Oracle Linux | 5, 6, 7 | - |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10, 11 | - |
| Raspbian | Jessie, Stretch | - |
@@ -899,13 +907,14 @@ Deep Scan Modeでスキャンするためには、下記のパッケージが必
| Distribution | Release | Requirements |
|:-------------|-------------------:|:-------------|
| Ubuntu | 12, 14, 16| - |
| Debian | 7, 8, 9| aptitude, reboot-notifier |
| Debian | 7, 8, 9| aptitude, reboot-notifier |
| CentOS | 6, 7| yum-plugin-changelog, yum-utils |
| Amazon | All | yum-plugin-changelog, yum-utils |
| RHEL | 5 | yum-utils, yum-security, yum-changelog |
| RHEL | 6, 7 | yum-utils, yum-plugin-changelog |
| Oracle Linux | 5 | yum-utils, yum-security, yum-changelog |
| Oracle Linux | 6, 7 | yum-utils, yum-plugin-changelog |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10 | - |
| Raspbian | Wheezy, Jessie | - |
@@ -935,7 +944,7 @@ vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- CentOS, Amazon Linux, FreeBSDは今のところRoot権限なしでスキャン可能
- CentOS, Amazon Linux, SUSE Enterprise, FreeBSDは今のところRoot権限なしでスキャン可能
----
@@ -1338,7 +1347,7 @@ Confidence 100 / OvalMatch
| Detection Method | Confidence | OS |Description|
|:-----------------------|-------------------:|:---------------------------------|:--|
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian |Detection using OVAL |
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian, SUSE |Detection using OVAL |
| YumUpdateSecurityMatch | 100 | RHEL, Amazon, Oracle |Detection using yum-plugin-security|
| ChangelogExactMatch | 95 | CentOS, Ubuntu, Debian, Raspbian |Exact version match between changelog and package version|
| ChangelogLenientMatch | 50 | Ubuntu, Debian, Raspbian |Lenient version match between changelog and package version|
@@ -1713,6 +1722,7 @@ $ vuls report -ovaldb-url=http://192.168.0.1:1323
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Oracle](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
----

View File

@@ -144,7 +144,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
# Main Features
- Scan for any vulnerabilities in Linux/FreeBSD Server
- Supports FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux and Raspbian
- Supports FreeBSD, Ubuntu, Debian, CentOS, Amazon Linux, RHEL, Oracle Linux, SUSE Enterprise Linux and Raspbian
- Cloud, on-premise, Docker
- High quality scan
- Vuls uses Multiple vulnerability databases
@@ -335,6 +335,7 @@ If you want to scan other than CentOS 7, fetch OVAL data according to the OS typ
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Oracle Linux](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
## Step5. Deploy Vuls
@@ -597,6 +598,7 @@ On the aggregation server, you can refer to the scanning result of each scan tar
| Raspbian |1st time: Slow <br> From 2nd time: Fast | Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Fast |  No | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No|
---------
@@ -614,6 +616,7 @@ On the aggregation server, you can refer to the scanning result of each scan tar
| Raspbian |1st time: Slow <br> From 2nd time: Fast| Need | No | Need |
| FreeBSD | Fast |  No | No | Need |
| Amazon | Slow |  No | No | Need |
| SUSE Enterprise | Fast |  No | Supported | No|
- On Ubuntu, Debian and Raspbian
@@ -624,9 +627,13 @@ From the second time on, the scan speed is fast by using the local cache.
- On CentOS
Vuls issues `yum changelog` to get changelogs of upgradable packages at once and parse the changelog.
- On RHEL, Oracle, Amazon and FreeBSD
Detect CVE IDs by using package manager.
- On SUSE Enterprise Linux
Same as fast scan mode for now.
----
# Use Cases
@@ -658,6 +665,7 @@ If there is a staging environment with the same configuration as the production
| CentOS | 6, 7|
| Amazon Linux | All|
| FreeBSD | 10, 11|
| SUSE Enterprise | 11, 12|
| Raspbian | Jessie, Stretch |
----
@@ -893,6 +901,7 @@ The configtest subcommand checks whether vuls is able to connect via SSH to serv
| Amazon | All | - |
| RHEL | 5, 6, 7 | - |
| Oracle Linux | 5, 6, 7 | - |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10, 11 | - |
| Raspbian | Jessie, Stretch | - |
@@ -915,6 +924,7 @@ In order to scan with deep scan mode, the following dependencies are required, s
| RHEL | 6, 7 | yum-utils, yum-plugin-changelog |
| Oracle Linux | 5 | yum-utils, yum-security, yum-changelog |
| Oracle Linux | 6, 7 | yum-utils, yum-plugin-changelog |
| SUSE Enterprise| 11, 12 | - |
| FreeBSD | 10 | - |
| Raspbian | Wheezy, Jessie | - |
@@ -944,7 +954,7 @@ vuls ALL=(ALL) NOPASSWD: /usr/bin/apt-get update
Defaults:vuls env_keep="http_proxy https_proxy HTTP_PROXY HTTPS_PROXY"
```
- On CentOS, Amazon Linux, FreeBSD, it is possible to scan without root privilege for now.
- On CentOS, Amazon Linux, SUSE Enterprise, FreeBSD, it is possible to scan without root privilege for now.
----
@@ -1349,7 +1359,7 @@ Confidence 100 / OvalMatch
| Detection Method | Confidence | OS |Description|
|:-----------------------|-------------------:|:---------------------------------|:--|
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian |Detection using OVAL |
| OvalMatch | 100 | CentOS, RHEL, Oracle, Ubuntu, Debian, SUSE |Detection using OVAL |
| YumUpdateSecurityMatch | 100 | RHEL, Amazon, Oracle |Detection using yum-plugin-security|
| ChangelogExactMatch | 95 | CentOS, Ubuntu, Debian, Raspbian |Exact version match between changelog and package version|
| ChangelogLenientMatch | 50 | Ubuntu, Debian, Raspbian |Lenient version match between changelog and package version|
@@ -1712,6 +1722,7 @@ $ vuls report -ovaldb-url=http://192.168.0.1:1323
- [Ubuntu](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-ubuntu)
- [Debian](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-debian)
- [Oracle](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-oracle)
- [SUSE](https://github.com/kotakanbe/goval-dictionary#usage-fetch-oval-data-from-suse)
----

View File

@@ -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

View File

@@ -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"
)

View File

@@ -546,6 +546,8 @@ func (v VulnInfo) VendorLinks(family string) map[string]string {
return links
case config.Debian:
links["Debian-CVE"] = "https://security-tracker.debian.org/tracker/" + v.CveID
case config.SUSEEnterpriseServer:
links["SUSE-CVE"] = "https://www.suse.com/security/cve/" + v.CveID
case config.FreeBSD:
for _, advisory := range v.DistroAdvisories {
links["FreeBSD-VuXML"] = fmt.Sprintf("https://vuxml.freebsd.org/freebsd/%s.html", advisory.AdvisoryID)

119
oval/suse.go Normal file
View File

@@ -0,0 +1,119 @@
/* 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"
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) {
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
}
}
for _, defPacks := range relatedDefs.entries {
o.update(r, defPacks)
}
for _, vuln := range r.ScannedCves {
if cont, ok := vuln.CveContents[models.SUSE]; ok {
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,
}
}

View File

@@ -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)
}

View File

@@ -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:

View File

@@ -280,14 +280,7 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
}
installed := models.Packages{}
var cmd string
majorVersion, _ := o.Distro.MajorVersion()
if majorVersion < 6 {
cmd = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'"
} else {
cmd = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
}
r := o.exec(cmd, noSudo)
r := o.exec(rpmQa(o.Distro), noSudo)
if !r.isSuccess() {
return nil, fmt.Errorf("Scan packages failed: %s", r)
}
@@ -304,14 +297,13 @@ func (o *redhat) scanInstalledPackages() (models.Packages, error) {
// Kernel package may be isntalled multiple versions.
// From the viewpoint of vulnerability detection,
// pay attention only to the running kernel
if pack.Name == "kernel" {
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
if o.Kernel.Release != ver {
o.log.Debugf("Not a running kernel: %s, uname: %s", ver, release)
isKernel, running := isRunningKernel(pack, o.Distro.Family, o.Kernel)
if isKernel {
if !running {
o.log.Debugf("Not a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
continue
} else {
o.log.Debugf("Running kernel: %s, uname: %s", ver, release)
}
o.log.Debugf("Found a running kernel. pack: %#v, kernel: %#v", pack, o.Kernel)
}
installed[pack.Name] = pack
}

View File

@@ -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

185
scan/suse.go Normal file
View File

@@ -0,0 +1,185 @@
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"`) {
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(`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
}
re = regexp.MustCompile(`VERSION = (\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
version := result[1]
re = regexp.MustCompile(`PATCHLEVEL = (\d+)`)
result = re.FindStringSubmatch(strings.TrimSpace(r.Stdout))
if len(result) == 2 {
suse.setDistro(config.SUSEEnterpriseServer,
fmt.Sprintf("%s.%s", version, 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
}
func (o *suse) rebootRequired() (bool, error) {
r := o.exec("rpm -q --last kernel-default | head -n1", noSudo)
if !r.isSuccess() {
return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r)
}
stdout := strings.Fields(r.Stdout)[0]
return !strings.Contains(stdout, strings.TrimSuffix(o.Kernel.Release, "-default")), nil
}
func (o *suse) scanUpdatablePackages() (models.Packages, error) {
cmd := ""
if v, _ := o.Distro.MajorVersion(); v < 12 {
cmd = "zypper -q lu"
} else {
cmd = "zypper --no-color -q lu"
}
r := o.exec(cmd, 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
}

106
scan/suse_test.go Normal file
View File

@@ -0,0 +1,106 @@
/* 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/future-architect/vuls/models"
"github.com/k0kubun/pp"
)
func TestScanUpdatablePackages(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: "sles"}
stdout := `S | Repository | Name | Current Version | Available Version | Arch
--+---------------------------------------------+-------------------------------+-----------------------------+-----------------------------+-------
v | SLES12-SP2-Updates | SUSEConnect | 0.3.0-19.8.1 | 0.3.1-19.11.2 | x86_64
v | SLES12-SP2-Updates | SuSEfirewall2 | 3.6.312-2.3.1 | 3.6.312-2.10.1 | noarch`
var tests = []struct {
in string
out models.Packages
}{
{
stdout,
models.NewPackages(
models.Package{
Name: "SUSEConnect",
NewVersion: "0.3.1",
NewRelease: "19.11.2",
Arch: "x86_64",
},
models.Package{
Name: "SuSEfirewall2",
NewVersion: "3.6.312",
NewRelease: "2.10.1",
Arch: "noarch",
},
),
},
}
for _, tt := range tests {
packages, err := r.parseZypperLULines(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
for name, ePack := range tt.out {
if !reflect.DeepEqual(ePack, packages[name]) {
e := pp.Sprintf("%v", ePack)
a := pp.Sprintf("%v", packages[name])
t.Errorf("expected %s, actual %s", e, a)
}
}
}
}
func TestScanUpdatablePackage(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: "sles"}
stdout := `v | SLES12-SP2-Updates | SUSEConnect | 0.3.0-19.8.1 | 0.3.1-19.11.2 | x86_64`
var tests = []struct {
in string
out models.Package
}{
{
stdout,
models.Package{
Name: "SUSEConnect",
NewVersion: "0.3.1",
NewRelease: "19.11.2",
Arch: "x86_64",
},
},
}
for _, tt := range tests {
pack, err := r.parseZypperLUOneLine(tt.in)
if err != nil {
t.Errorf("Error has occurred, err: %s\ntt.in: %v", err, tt.in)
return
}
if !reflect.DeepEqual(*pack, tt.out) {
e := pp.Sprintf("%v", tt.out)
a := pp.Sprintf("%v", pack)
t.Errorf("expected %s, actual %s", e, a)
}
}
}

69
scan/utils.go Normal file
View File

@@ -0,0 +1,69 @@
/* 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"
"strings"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
)
func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (isKernel, running bool) {
switch family {
case config.SUSEEnterpriseServer:
if pack.Name == "kernel-default" {
// Remove the last period and later because uname don't show that.
ss := strings.Split(pack.Release, ".")
rel := strings.Join(ss[0:len(ss)-1], ".")
ver := fmt.Sprintf("%s-%s-default", pack.Version, rel)
return true, kernel.Release == ver
}
return false, false
case config.RedHat, config.Oracle, config.CentOS, config.Amazon:
if pack.Name == "kernel" {
ver := fmt.Sprintf("%s-%s.%s", pack.Version, pack.Release, pack.Arch)
return true, kernel.Release == ver
}
return false, false
default:
util.Log.Warnf("Reboot required is not implemented yet: %s, %s", family, kernel)
}
return false, false
}
func rpmQa(distro config.Distro) string {
const old = "rpm -qa --queryformat '%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n'"
const new = "rpm -qa --queryformat '%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n'"
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}

117
scan/utils_test.go Normal file
View File

@@ -0,0 +1,117 @@
/* 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 (
"testing"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)
func TestIsRunningKernelSUSE(t *testing.T) {
r := newSUSE(config.ServerInfo{})
r.Distro = config.Distro{Family: config.SUSEEnterpriseServer}
kernel := models.Kernel{
Release: "4.4.74-92.35-default",
Version: "",
}
var tests = []struct {
pack models.Package
family string
kernel models.Kernel
expected bool
}{
{
pack: models.Package{
Name: "kernel-default",
Version: "4.4.74",
Release: "92.35.1",
Arch: "x86_64",
},
family: config.SUSEEnterpriseServer,
kernel: kernel,
expected: true,
},
{
pack: models.Package{
Name: "kernel-default",
Version: "4.4.59",
Release: "92.20.2",
Arch: "x86_64",
},
family: config.SUSEEnterpriseServer,
kernel: kernel,
expected: false,
},
}
for i, tt := range tests {
_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
if tt.expected != actual {
t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
}
}
}
func TestIsRunningKernelRedHatLikeLinux(t *testing.T) {
r := newRedhat(config.ServerInfo{})
r.Distro = config.Distro{Family: config.Amazon}
kernel := models.Kernel{
Release: "4.9.43-17.38.amzn1.x86_64",
Version: "",
}
var tests = []struct {
pack models.Package
family string
kernel models.Kernel
expected bool
}{
{
pack: models.Package{
Name: "kernel",
Version: "4.9.43",
Release: "17.38.amzn1",
Arch: "x86_64",
},
family: config.Amazon,
kernel: kernel,
expected: true,
},
{
pack: models.Package{
Name: "kernel",
Version: "4.9.38",
Release: "16.35.amzn1",
Arch: "x86_64",
},
family: config.Amazon,
kernel: kernel,
expected: false,
},
}
for i, tt := range tests {
_, actual := isRunningKernel(tt.pack, tt.family, tt.kernel)
if tt.expected != actual {
t.Errorf("[%d] expected %t, actual %t", i, tt.expected, actual)
}
}
}