2021-04-23 13:51:27 +05:30
package oval
2019-11-03 20:28:28 +02:00
import (
2020-12-31 22:38:53 +09:00
"bufio"
2019-11-03 20:28:28 +02:00
"bytes"
"compress/bzip2"
2021-12-29 14:05:21 +02:00
"encoding/json"
2019-11-03 20:28:28 +02:00
"encoding/xml"
2019-11-06 10:32:34 -08:00
"errors"
2019-11-03 20:28:28 +02:00
"fmt"
"log"
"os"
2020-12-31 22:38:53 +09:00
"path"
2019-11-03 20:28:28 +02:00
"path/filepath"
"strings"
2020-12-31 22:38:53 +09:00
"github.com/cheggaaa/pb"
2019-11-03 20:28:28 +02:00
"github.com/spf13/afero"
"golang.org/x/xerrors"
"github.com/aquasecurity/vuln-list-update/utils"
)
const (
2021-12-29 14:05:21 +02:00
ovalDir = "oval"
redhatDir = "redhat"
cpeDir = "redhat-cpe"
2020-12-31 22:38:53 +09:00
urlFormat = "https://www.redhat.com/security/data/oval/v2/%s"
retry = 5
pulpManifest = "PULP_MANIFEST"
2021-12-29 14:05:21 +02:00
repoToCpeURL = "https://www.redhat.com/security/data/metrics/repository-to-cpe.json"
2020-12-31 22:38:53 +09:00
testsDir = "tests"
objectsDir = "objects"
statesDir = "states"
definitionsDir = "definitions"
2019-11-03 20:28:28 +02:00
)
2019-11-06 10:32:34 -08:00
var (
ErrInvalidRHSAFormat = errors . New ( "invalid RHSA-ID format" )
2020-12-31 22:38:53 +09:00
ErrInvalidCVEFormat = errors . New ( "invalid CVE-ID format" )
2019-11-06 10:32:34 -08:00
)
2019-11-03 20:28:28 +02:00
type Config struct {
2021-12-29 14:05:21 +02:00
VulnListDir string
URLFormat string
RepoToCpeURL string
AppFs afero . Fs
Retry int
2019-11-03 20:28:28 +02:00
}
func NewConfig ( ) Config {
return Config {
2021-12-29 14:05:21 +02:00
VulnListDir : utils . VulnListDir ( ) ,
URLFormat : urlFormat ,
RepoToCpeURL : repoToCpeURL ,
AppFs : afero . NewOsFs ( ) ,
Retry : retry ,
2019-11-03 20:28:28 +02:00
}
}
func ( c Config ) Update ( ) error {
2021-12-29 14:05:21 +02:00
log . Println ( "Updating Red Hat mapping from repositories to CPE names..." )
if err := c . updateRepoToCpe ( ) ; err != nil {
return xerrors . Errorf ( "unable to update repository-to-cpe.json: %w" , err )
}
2020-12-31 22:38:53 +09:00
dirPath := filepath . Join ( c . VulnListDir , ovalDir , redhatDir )
log . Printf ( "Remove Red Hat OVAL v2 directory %s" , dirPath )
if err := os . RemoveAll ( dirPath ) ; err != nil {
return xerrors . Errorf ( "failed to remove Red Hat OVAL v2 directory: %w" , err )
}
log . Println ( "Fetching Red Hat OVAL v2 data..." )
filePaths , err := c . fetchOvalFilePaths ( )
if err != nil {
return xerrors . Errorf ( "failed to get oval file paths: %w" , err )
}
for _ , ovalFilePath := range filePaths {
log . Printf ( "Fetching %s" , ovalFilePath )
2021-12-29 14:05:21 +02:00
if err := c . updateOVAL ( ovalFilePath ) ; err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "failed to update Red Hat OVAL v2 json: %w" , err )
2019-11-03 20:28:28 +02:00
}
}
2020-12-31 22:38:53 +09:00
2019-11-03 20:28:28 +02:00
return nil
}
2021-12-29 14:05:21 +02:00
func ( c Config ) updateRepoToCpe ( ) error {
b , err := utils . FetchURL ( c . RepoToCpeURL , "" , c . Retry )
if err != nil {
return xerrors . Errorf ( "failed to get %s: %w" , c . RepoToCpeURL , err )
}
var repoToCPE repositoryToCPE
if err = json . Unmarshal ( b , & repoToCPE ) ; err != nil {
return xerrors . Errorf ( "JSON parse error: %w" , err )
}
mapping := map [ string ] [ ] string { }
for repo , cpes := range repoToCPE . Data {
mapping [ repo ] = cpes . Cpes
}
dir := filepath . Join ( c . VulnListDir , cpeDir )
if err = utils . WriteJSON ( c . AppFs , dir , "repository-to-cpe.json" , mapping ) ; err != nil {
return xerrors . Errorf ( "JSON write error: %w" , err )
}
return nil
}
func ( c Config ) updateOVAL ( ovalFile string ) error {
2020-12-31 22:38:53 +09:00
// e.g. RHEL8/storage-gluster-3-including-unpatched.oval.xml.bz2
if ! strings . HasPrefix ( ovalFile , "RHEL" ) {
log . Printf ( "Skip %s" , ovalFile )
return nil
}
// e.g. RHEL8/storage-gluster-3-including-unpatched.oval.xml.bz2
// => RHEL8/, storage-gluster-3-including-unpatched.oval.xml.bz2
dir , file := path . Split ( ovalFile )
release := strings . TrimPrefix ( path . Clean ( dir ) , "RHEL" )
url := fmt . Sprintf ( c . URLFormat , ovalFile )
2019-11-03 20:28:28 +02:00
res , err := utils . FetchURL ( url , "" , c . Retry )
if err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "failed to fetch Red Hat OVAL v2: %w" , err )
2019-11-03 20:28:28 +02:00
}
bzr := bzip2 . NewReader ( bytes . NewBuffer ( res ) )
2020-12-31 22:38:53 +09:00
var ovalroot OvalDefinitions
if err := xml . NewDecoder ( bzr ) . Decode ( & ovalroot ) ; err != nil {
return xerrors . Errorf ( "failed to unmarshal Red Hat OVAL v2 XML: %w" , err )
}
// e.g. storage-gluster-3-including-unpatched
platform := strings . TrimSuffix ( file , ".oval.xml.bz2" )
dirPath := filepath . Join ( c . VulnListDir , ovalDir , redhatDir , release , platform )
// write tests/tests.json file
2021-01-04 14:44:37 +02:00
if err := utils . WriteJSON ( c . AppFs , filepath . Join ( dirPath , testsDir ) , "tests.json" , ovalroot . Tests ) ; err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "failed to write tests: %w" , err )
}
// write objects/objects.json file
2021-01-04 14:44:37 +02:00
if err := utils . WriteJSON ( c . AppFs , filepath . Join ( dirPath , objectsDir ) , "objects.json" , ovalroot . Objects ) ; err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "failed to write objects: %w" , err )
}
2019-11-03 20:28:28 +02:00
2020-12-31 22:38:53 +09:00
// write states/states.json file
2021-01-04 14:44:37 +02:00
if err := utils . WriteJSON ( c . AppFs , filepath . Join ( dirPath , statesDir ) , "states.json" , ovalroot . States ) ; err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "failed to write states: %w" , err )
}
// write definitions
bar := pb . StartNew ( len ( ovalroot . Definitions . Definition ) )
for _ , def := range ovalroot . Definitions . Definition {
if len ( def . Metadata . References ) == 0 {
continue
}
// RHSA-ID or CVE-ID
vulnID := def . Metadata . References [ 0 ] . RefID
for _ , ref := range def . Metadata . References {
if strings . HasPrefix ( ref . RefID , "RHSA-" ) {
vulnID = ref . RefID
2019-11-06 10:32:34 -08:00
}
2019-11-03 20:28:28 +02:00
}
2020-12-31 22:38:53 +09:00
if err := c . saveAdvisoryPerYear ( filepath . Join ( dirPath , definitionsDir ) , vulnID , def ) ; err != nil {
return xerrors . Errorf ( "failed to save advisory per year: %w" , err )
}
2019-11-03 20:28:28 +02:00
bar . Increment ( )
}
bar . Finish ( )
2020-12-31 22:38:53 +09:00
2019-11-03 20:28:28 +02:00
return nil
}
2020-12-31 22:38:53 +09:00
func ( c Config ) fetchOvalFilePaths ( ) ( [ ] string , error ) {
res , err := utils . FetchURL ( fmt . Sprintf ( c . URLFormat , pulpManifest ) , "" , c . Retry )
if err != nil {
return nil , xerrors . Errorf ( "failed to fetch PULP_MANIFEST: %w" , err )
2019-11-03 20:28:28 +02:00
}
2020-12-31 22:38:53 +09:00
var ovalFilePaths [ ] string
scanner := bufio . NewScanner ( bytes . NewReader ( res ) )
for scanner . Scan ( ) {
ss := strings . Split ( scanner . Text ( ) , "," )
if len ( ss ) < 3 {
return nil , xerrors . Errorf ( "failed to parse PULP_MANIFEST: %w" , err )
}
2021-05-27 19:40:38 +05:30
// skip if size is 0
if ss [ 2 ] == "0" {
continue
}
2020-12-31 22:38:53 +09:00
ovalFilePaths = append ( ovalFilePaths , ss [ 0 ] )
2019-11-03 20:28:28 +02:00
}
2020-12-31 22:38:53 +09:00
return ovalFilePaths , nil
}
2019-11-03 20:28:28 +02:00
2020-12-31 22:38:53 +09:00
func ( c Config ) saveAdvisoryPerYear ( dirName string , id string , def Definition ) error {
var year string
if strings . HasPrefix ( id , "CVE" ) {
s := strings . Split ( id , "-" )
if len ( s ) != 3 {
log . Printf ( "invalid CVE-ID format: %s\n" , id )
return ErrInvalidCVEFormat
}
year = s [ 1 ]
} else {
// e.g. RHSA-2018:0094
s := strings . Split ( id , ":" )
if len ( s ) != 2 {
log . Printf ( "invalid RHSA-ID format: %s\n" , id )
return ErrInvalidRHSAFormat
}
s = strings . Split ( s [ 0 ] , "-" )
if len ( s ) != 2 {
log . Printf ( "invalid RHSA-ID format: %s\n" , id )
return ErrInvalidRHSAFormat
}
year = s [ 1 ]
}
fileFmt := "%s.json"
if strings . HasPrefix ( def . ID , "oval:com.redhat.unaffected:def" ) {
fileFmt = "%s.unaffected.json"
}
yearDir := filepath . Join ( dirName , year )
2021-01-04 14:44:37 +02:00
if err := utils . WriteJSON ( c . AppFs , yearDir , fmt . Sprintf ( fileFmt , id ) , def ) ; err != nil {
2020-12-31 22:38:53 +09:00
return xerrors . Errorf ( "unable to write a JSON file: %w" , err )
}
return nil
}