2019-10-13 06:02:24 +03:00
package amazon
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/xml"
"fmt"
2023-03-19 23:49:22 +06:00
"github.com/aquasecurity/vuln-list-update/utils"
"golang.org/x/xerrors"
"gopkg.in/cheggaaa/pb.v1"
2019-10-13 06:02:24 +03:00
"log"
"net/url"
"os"
"path"
"path/filepath"
)
const (
retry = 3
2023-03-19 23:49:22 +06:00
amazonDir = "amazon"
2019-10-13 06:02:24 +03:00
)
var (
2022-07-04 13:35:10 +06:00
mirrorListURI = map [ string ] string {
2019-10-13 06:02:24 +03:00
"1" : "http://repo.us-west-2.amazonaws.com/2018.03/updates/x86_64/mirror.list" ,
"2" : "https://cdn.amazonlinux.com/2/core/latest/x86_64/mirror.list" ,
2023-03-19 23:49:22 +06:00
// run `dnf repolist all --verbose` inside container to get `Repo-mirrors`
"2022" : "https://cdn.amazonlinux.com/al2022/core/mirrors/latest/x86_64/mirror.list" ,
"2023" : "https://cdn.amazonlinux.com/al2023/core/mirrors/latest/x86_64/mirror.list" ,
2019-10-13 06:02:24 +03:00
}
)
2022-07-04 13:35:10 +06:00
type Config struct {
2023-03-19 23:49:22 +06:00
mirrorListURI map [ string ] string
vulnListDir string
2019-10-13 06:02:24 +03:00
}
2022-07-04 13:35:10 +06:00
type option func ( * Config )
2019-10-13 06:02:24 +03:00
2022-07-04 13:35:10 +06:00
// With takes some internal values for testing
2023-03-19 23:49:22 +06:00
func With ( mirrorListURI map [ string ] string , vulnListDir string ) option {
2022-07-04 13:35:10 +06:00
return func ( opts * Config ) {
opts . mirrorListURI = mirrorListURI
opts . vulnListDir = vulnListDir
}
2019-10-13 06:02:24 +03:00
}
2022-07-04 13:35:10 +06:00
func NewConfig ( opts ... option ) * Config {
config := & Config {
2023-03-19 23:49:22 +06:00
mirrorListURI : mirrorListURI ,
vulnListDir : utils . VulnListDir ( ) ,
2022-07-04 13:35:10 +06:00
}
2019-10-13 06:02:24 +03:00
2022-07-04 13:35:10 +06:00
for _ , opt := range opts {
opt ( config )
}
2019-10-13 06:02:24 +03:00
2022-07-04 13:35:10 +06:00
return config
2019-10-13 06:02:24 +03:00
}
func ( ac Config ) Update ( ) error {
2022-07-04 13:35:10 +06:00
for version , amznURL := range ac . mirrorListURI {
2019-10-13 06:02:24 +03:00
log . Printf ( "Fetching security advisories of Amazon Linux %s...\n" , version )
if err := ac . update ( version , amznURL ) ; err != nil {
return xerrors . Errorf ( "failed to update security advisories of Amazon Linux %s: %w" , version , err )
}
}
return nil
}
func ( ac Config ) update ( version , url string ) error {
2022-07-04 13:35:10 +06:00
dir := filepath . Join ( ac . vulnListDir , amazonDir , version )
2020-01-29 22:05:10 +02:00
if err := os . RemoveAll ( dir ) ; err != nil {
return xerrors . Errorf ( "unable to remove amazon directory: %w" , err )
}
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return xerrors . Errorf ( "failed to mkdir: %w" , err )
}
2019-10-13 06:02:24 +03:00
vulns , err := fetchUpdateInfoAmazonLinux ( url )
if err != nil {
return xerrors . Errorf ( "failed to fetch security advisories from Amazon Linux Security Center: %w" , err )
}
bar := pb . StartNew ( len ( vulns . ALASList ) )
for _ , alas := range vulns . ALASList {
filePath := filepath . Join ( dir , fmt . Sprintf ( "%s.json" , alas . ID ) )
if err = utils . Write ( filePath , alas ) ; err != nil {
return xerrors . Errorf ( "failed to write Amazon CVE details: %w" , err )
}
bar . Increment ( )
}
bar . Finish ( )
return nil
}
func fetchUpdateInfoAmazonLinux ( mirrorListURL string ) ( uinfo * UpdateInfo , err error ) {
body , err := utils . FetchURL ( mirrorListURL , "" , retry )
if err != nil {
return nil , xerrors . Errorf ( "failed to fetch mirror list files: %w" , err )
}
var mirrors [ ] string
scanner := bufio . NewScanner ( bytes . NewReader ( body ) )
for scanner . Scan ( ) {
mirrors = append ( mirrors , scanner . Text ( ) )
}
for _ , mirror := range mirrors {
u , err := url . Parse ( mirror )
if err != nil {
2020-10-14 01:10:10 -07:00
return nil , xerrors . Errorf ( "failed to parse mirror URL: %w" , err )
2019-10-13 06:02:24 +03:00
}
originalPath := u . Path
u . Path = path . Join ( u . Path , "/repodata/repomd.xml" )
updateInfoPath , err := fetchUpdateInfoURL ( u . String ( ) )
if err != nil {
log . Printf ( "Failed to fetch updateInfo URL: %s\n" , err )
continue
}
u . Path = path . Join ( originalPath , updateInfoPath )
uinfo , err := fetchUpdateInfo ( u . String ( ) )
if err != nil {
log . Printf ( "Failed to fetch updateInfo: %s\n" , err )
continue
}
return uinfo , nil
}
return nil , xerrors . New ( "Failed to fetch updateinfo" )
}
func fetchUpdateInfoURL ( mirror string ) ( updateInfoPath string , err error ) {
res , err := utils . FetchURL ( mirror , "" , retry )
if err != nil {
return "" , xerrors . Errorf ( "failed to fetch %s: %w" , mirror , err )
}
var repoMd RepoMd
if err := xml . NewDecoder ( bytes . NewBuffer ( res ) ) . Decode ( & repoMd ) ; err != nil {
return "" , xerrors . Errorf ( "failed to decode repomd.xml: %w" , err )
}
for _ , repo := range repoMd . RepoList {
if repo . Type == "updateinfo" {
updateInfoPath = repo . Location . Href
break
}
}
if updateInfoPath == "" {
return "" , xerrors . New ( "No updateinfo field in the repomd" )
}
return updateInfoPath , nil
}
func fetchUpdateInfo ( url string ) ( * UpdateInfo , error ) {
res , err := utils . FetchURL ( url , "" , retry )
if err != nil {
return nil , xerrors . Errorf ( "failed to fetch updateInfo: %w" , err )
}
r , err := gzip . NewReader ( bytes . NewBuffer ( res ) )
if err != nil {
return nil , xerrors . Errorf ( "failed to decompress updateInfo: %w" , err )
}
defer r . Close ( )
var updateInfo UpdateInfo
if err := xml . NewDecoder ( r ) . Decode ( & updateInfo ) ; err != nil {
return nil , err
}
for i , alas := range updateInfo . ALASList {
var cveIDs [ ] string
for _ , ref := range alas . References {
if ref . Type == "cve" {
cveIDs = append ( cveIDs , ref . ID )
}
}
updateInfo . ALASList [ i ] . CveIDs = cveIDs
}
return & updateInfo , nil
}