2023-09-27 16:18:16 +03:00
package k8s
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"golang.org/x/xerrors"
"github.com/aquasecurity/vuln-list-update/osv"
"github.com/aquasecurity/vuln-list-update/utils"
uu "github.com/aquasecurity/vuln-list-update/utils"
)
const (
2023-09-28 10:28:05 +03:00
k8svulnDBURL = "https://kubernetes.io/docs/reference/issues-security/official-cve-feed/index.json"
mitreURL = "https://cveawg.mitre.org/api/cve"
cveList = "https://www.cve.org/"
upstreamFolder = "upstream"
2023-09-27 16:18:16 +03:00
)
2023-11-14 13:25:34 +06:00
type options struct {
mitreURL string
}
type option func ( * options )
func WithMitreURL ( mitreURL string ) option {
return func ( opts * options ) {
opts . mitreURL = mitreURL
}
}
type Updater struct {
* options
}
func NewUpdater ( opts ... option ) Updater {
o := & options {
mitreURL : mitreURL ,
}
for _ , opt := range opts {
opt ( o )
}
return Updater {
options : o ,
}
}
2023-09-27 16:18:16 +03:00
type VulnDB struct {
Cves [ ] * osv . OSV
}
type CVE struct {
Items [ ] Item ` json:"items,omitempty" `
}
type Item struct {
ID string ` json:"id,omitempty" `
Summary string ` json:"summary,omitempty" `
ContentText string ` json:"content_text,omitempty" `
DatePublished string ` json:"date_published,omitempty" `
ExternalURL string ` json:"external_url,omitempty" `
URL string ` json:"url,omitempty" `
}
2023-11-14 13:25:34 +06:00
func ( u Updater ) Collect ( ) ( * VulnDB , error ) {
2023-09-27 16:18:16 +03:00
response , err := http . Get ( k8svulnDBURL )
if err != nil {
return nil , err
}
defer response . Body . Close ( )
var db CVE
if err = json . NewDecoder ( response . Body ) . Decode ( & db ) ; err != nil {
return nil , err
}
2023-09-28 10:28:05 +03:00
cvesMap , err := cveIDToModifiedMap ( filepath . Join ( utils . VulnListDir ( ) , upstreamFolder ) )
2023-09-27 16:18:16 +03:00
if err != nil {
return nil , err
}
2023-11-14 13:25:34 +06:00
return u . ParseVulnDBData ( db , cvesMap )
2023-09-27 16:18:16 +03:00
}
const (
// excludeNonCoreComponentsCves exclude cves with missing data or non k8s core components
excludeNonCoreComponentsCves = "CVE-2019-11255,CVE-2020-10749,CVE-2020-8554"
)
2023-11-14 13:25:34 +06:00
func ( u Updater ) Update ( ) error {
if err := u . update ( ) ; err != nil {
2023-09-27 16:18:16 +03:00
return xerrors . Errorf ( "error in k8s update: %w" , err )
}
return nil
}
2023-11-14 13:25:34 +06:00
func ( u Updater ) update ( ) error {
2023-09-27 16:18:16 +03:00
log . Printf ( "Fetching k8s cves" )
2023-11-14 13:25:34 +06:00
k8sdb , err := u . Collect ( )
2023-09-27 16:18:16 +03:00
if err != nil {
return err
}
for _ , cve := range k8sdb . Cves {
2023-09-28 10:28:05 +03:00
if err = uu . Write ( filepath . Join ( uu . VulnListDir ( ) , upstreamFolder , fmt . Sprintf ( "%s.json" , cve . ID ) ) , cve ) ; err != nil {
2023-09-27 16:18:16 +03:00
return xerrors . Errorf ( "failed to save k8s CVE detail: %w" , err )
}
}
return nil
}
2023-11-14 13:25:34 +06:00
func ( u Updater ) ParseVulnDBData ( db CVE , cvesMap map [ string ] string ) ( * VulnDB , error ) {
2023-09-27 16:18:16 +03:00
var fullVulnerabilities [ ] * osv . OSV
for _ , item := range db . Items {
for _ , cveID := range getMultiIDs ( item . ID ) {
// check if the current cve is older than the existing one on the vuln-list-k8s repo
if strings . Contains ( excludeNonCoreComponentsCves , item . ID ) || olderCve ( cveID , item . DatePublished , cvesMap ) {
continue
}
2023-11-14 13:25:34 +06:00
vulnerability , err := parseMitreCve ( item . ExternalURL , u . mitreURL , cveID )
2023-09-27 16:18:16 +03:00
if err != nil {
return nil , err
}
if cveMissingImportantData ( vulnerability ) {
continue
}
descComponent := getComponentFromDescription ( item . ContentText , vulnerability . Package )
fullVulnerabilities = append ( fullVulnerabilities , & osv . OSV {
2023-10-02 18:44:10 +03:00
ID : cveID ,
Modified : item . DatePublished ,
Published : item . DatePublished ,
Summary : item . Summary ,
Details : vulnerability . Description ,
Affected : getAffectedEvents ( vulnerability . versions , getComponentName ( descComponent , vulnerability . Package ) , vulnerability . CvssV3 ) ,
References : [ ] osv . Reference {
{
Url : item . URL , Type : "ADVISORY" ,
} , {
Url : item . ExternalURL , Type : "ADVISORY" ,
} ,
} ,
2023-09-27 16:18:16 +03:00
} )
}
}
return & VulnDB { fullVulnerabilities } , nil
}
func getAffectedEvents ( v [ ] * Version , p string , cvss Cvssv3 ) [ ] osv . Affected {
2023-10-02 18:44:10 +03:00
events := make ( [ ] osv . Event , 0 )
2023-09-27 16:18:16 +03:00
for _ , av := range v {
if len ( av . Introduced ) == 0 {
continue
}
if len ( av . Introduced ) > 0 {
events = append ( events , osv . Event { Introduced : av . Introduced } )
}
if len ( av . Fixed ) > 0 {
events = append ( events , osv . Event { Fixed : av . Fixed } )
} else if len ( av . LastAffected ) > 0 {
events = append ( events , osv . Event { LastAffected : av . LastAffected } )
} else if len ( av . Introduced ) > 0 && len ( av . LastAffected ) == 0 && len ( av . Fixed ) == 0 {
events = append ( events , osv . Event { LastAffected : av . Introduced } )
}
}
2023-10-02 18:44:10 +03:00
return [ ] osv . Affected {
{
Ranges : [ ] osv . Range {
{
Events : events ,
Type : "SEMVER" ,
} ,
} ,
Package : osv . Package {
Name : p ,
Ecosystem : "kubernetes" ,
} ,
Severities : [ ] osv . Severity {
{
Type : cvss . Type ,
Score : cvss . Vector ,
} ,
} ,
} ,
}
2023-09-27 16:18:16 +03:00
}
func getComponentName ( k8sComponent string , mitreComponent string ) string {
if len ( k8sComponent ) == 0 {
k8sComponent = mitreComponent
}
if strings . ToLower ( mitreComponent ) != "kubernetes" {
k8sComponent = mitreComponent
}
return strings . ToLower ( fmt . Sprintf ( "%s/%s" , upstreamOrgByName ( k8sComponent ) , upstreamRepoByName ( k8sComponent ) ) )
}
func cveMissingImportantData ( vulnerability * Cve ) bool {
return len ( vulnerability . versions ) == 0 ||
len ( vulnerability . Package ) == 0 ||
len ( vulnerability . CvssV3 . Vector ) == 0
}
// cveIDToModifiedMap read existing cves from vulnList folder and map it to cve id and last updated
func cveIDToModifiedMap ( cveFolderPath string ) ( map [ string ] string , error ) {
mapCveTime := make ( map [ string ] string )
if _ , err := os . Stat ( cveFolderPath ) ; os . IsNotExist ( err ) {
return mapCveTime , nil
}
fileInfo , err := os . ReadDir ( cveFolderPath )
if err != nil {
return mapCveTime , err
}
for _ , file := range fileInfo {
if file . IsDir ( ) {
continue
}
2023-09-28 10:28:05 +03:00
if ! ( strings . Contains ( file . Name ( ) , "CVE-" ) && strings . HasSuffix ( file . Name ( ) , ".json" ) ) {
continue
}
2023-09-27 16:18:16 +03:00
b , err := os . ReadFile ( filepath . Join ( cveFolderPath , file . Name ( ) ) )
if err != nil {
return nil , err
}
var cve osv . OSV
err = json . Unmarshal ( [ ] byte ( strings . ReplaceAll ( string ( b ) , "\n" , "" ) ) , & cve )
if err != nil {
return nil , err
}
mapCveTime [ cve . ID ] = cve . Modified
}
return mapCveTime , nil
}
// olderCve check if the current cve is older than the existing one
func olderCve ( cveID string , currentCVEUpdated string , existCveLastUpdated map [ string ] string ) bool {
if len ( existCveLastUpdated ) == 0 {
return false
}
var lastUpdated string
var ok bool
if lastUpdated , ok = existCveLastUpdated [ cveID ] ; ! ok {
return false
}
existLastUpdated , err := time . Parse ( time . RFC3339 , lastUpdated )
if err != nil {
return false
}
currentLastUpdated , err := time . Parse ( time . RFC3339 , currentCVEUpdated )
if err != nil {
return false
}
// check if the current collcted cve is older or same as the existing one
if currentLastUpdated . Before ( existLastUpdated ) || currentLastUpdated == existLastUpdated {
return true
}
return false
}