2019-04-30 13:02:09 +09:00
package alpine
import (
2021-01-11 17:08:29 +02:00
"bytes"
2019-04-30 13:02:09 +09:00
"encoding/json"
"fmt"
"log"
2021-01-11 17:08:29 +02:00
"net/url"
"path"
2019-04-30 13:02:09 +09:00
"path/filepath"
"strings"
2021-01-11 17:08:29 +02:00
"github.com/PuerkitoBio/goquery"
"github.com/spf13/afero"
2020-06-29 20:55:16 +03:00
"golang.org/x/xerrors"
2019-04-30 13:02:09 +09:00
2019-08-18 22:47:18 -10:00
"github.com/aquasecurity/vuln-list-update/utils"
2019-04-30 13:02:09 +09:00
)
const (
2021-01-11 17:08:29 +02:00
alpineDir = "alpine"
repoURL = "https://secdb.alpinelinux.org/"
retry = 3
2019-04-30 13:02:09 +09:00
)
2021-01-11 17:08:29 +02:00
type Updater struct {
vulnListDir string
2022-12-04 04:07:37 -05:00
advisoryDir string
2021-01-11 17:08:29 +02:00
appFs afero . Fs
baseURL * url . URL
retry int
}
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
type option func ( c * Updater )
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
func WithVulnListDir ( v string ) option {
return func ( c * Updater ) { c . vulnListDir = v }
2019-10-10 18:45:17 +03:00
}
2022-12-04 04:07:37 -05:00
func WithAdvisoryDir ( s string ) option {
return func ( c * Updater ) { c . advisoryDir = s }
}
2021-01-11 17:08:29 +02:00
func WithAppFs ( v afero . Fs ) option {
return func ( c * Updater ) { c . appFs = v }
}
func WithBaseURL ( v * url . URL ) option {
return func ( c * Updater ) { c . baseURL = v }
}
func WithRetry ( v int ) option {
return func ( c * Updater ) { c . retry = v }
}
func NewUpdater ( options ... option ) * Updater {
u , _ := url . Parse ( repoURL )
updater := & Updater {
vulnListDir : utils . VulnListDir ( ) ,
2022-12-04 04:07:37 -05:00
advisoryDir : alpineDir ,
2021-01-11 17:08:29 +02:00
appFs : afero . NewOsFs ( ) ,
baseURL : u ,
retry : retry ,
}
for _ , option := range options {
option ( updater )
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
return updater
}
func ( u Updater ) Update ( ) ( err error ) {
2022-12-04 04:07:37 -05:00
dir := filepath . Join ( u . vulnListDir , u . advisoryDir )
2021-01-11 17:08:29 +02:00
log . Printf ( "Remove Alpine directory %s" , dir )
if err := u . appFs . RemoveAll ( dir ) ; err != nil {
return xerrors . Errorf ( "failed to remove Alpine directory: %w" , err )
}
if err := u . appFs . MkdirAll ( dir , 0755 ) ; err != nil {
return err
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
log . Println ( "Fetching Alpine data..." )
b , err := utils . FetchURL ( u . baseURL . String ( ) , "" , u . retry )
if err != nil {
return err
}
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
d , err := goquery . NewDocumentFromReader ( bytes . NewReader ( b ) )
if err != nil {
return err
}
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
var releases [ ] string
d . Find ( "a" ) . Each ( func ( i int , selection * goquery . Selection ) {
2022-04-12 15:11:41 +06:00
release := selection . Text ( )
if ! strings . HasPrefix ( release , "v" ) && ! strings . HasPrefix ( release , "edge" ) {
2021-01-11 17:08:29 +02:00
return
2019-04-30 13:02:09 +09:00
}
2022-04-12 15:11:41 +06:00
releases = append ( releases , release )
2021-01-11 17:08:29 +02:00
} )
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
for _ , release := range releases {
releaseURL := * u . baseURL
releaseURL . Path = path . Join ( releaseURL . Path , release )
files , err := u . traverse ( releaseURL )
2019-04-30 13:02:09 +09:00
if err != nil {
2021-01-11 17:08:29 +02:00
return err
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
for _ , file := range files {
2022-12-04 04:07:37 -05:00
if err = u . Save ( release , file ) ; err != nil {
2021-01-11 17:08:29 +02:00
return err
2019-04-30 13:02:09 +09:00
}
}
}
return nil
}
2021-01-11 17:08:29 +02:00
func ( u Updater ) traverse ( url url . URL ) ( [ ] string , error ) {
b , err := utils . FetchURL ( url . String ( ) , "" , u . retry )
2019-04-30 13:02:09 +09:00
if err != nil {
2021-01-11 17:08:29 +02:00
return nil , err
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
d , err := goquery . NewDocumentFromReader ( bytes . NewReader ( b ) )
2019-04-30 13:02:09 +09:00
if err != nil {
2021-01-11 17:08:29 +02:00
return nil , err
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
var files [ ] string
d . Find ( "a" ) . Each ( func ( i int , selection * goquery . Selection ) {
if ! strings . HasSuffix ( selection . Text ( ) , ".json" ) {
return
2019-10-10 18:45:17 +03:00
}
2021-01-11 17:08:29 +02:00
files = append ( files , selection . Text ( ) )
2019-04-30 13:02:09 +09:00
} )
2021-01-11 17:08:29 +02:00
return files , nil
}
2019-04-30 13:02:09 +09:00
2022-12-04 04:07:37 -05:00
func ( u Updater ) Save ( release , fileName string ) error {
2021-01-11 17:08:29 +02:00
log . Printf ( " release: %s, file: %s" , release , fileName )
advisoryURL := * u . baseURL
advisoryURL . Path = path . Join ( advisoryURL . Path , release , fileName )
b , err := utils . FetchURL ( advisoryURL . String ( ) , "" , u . retry )
2019-04-30 13:02:09 +09:00
if err != nil {
2021-01-11 17:08:29 +02:00
return err
2019-04-30 13:02:09 +09:00
}
2019-10-07 14:57:48 -07:00
2021-01-11 17:08:29 +02:00
var secdb secdb
if err = json . Unmarshal ( b , & secdb ) ; err != nil {
return err
}
2020-06-29 20:54:35 +03:00
2021-01-11 17:08:29 +02:00
// "packages" might not be an array and it causes an unmarshal error.
// See https://gitlab.alpinelinux.org/alpine/infra/docker/secdb/-/issues/2
var v interface { }
if err = json . Unmarshal ( secdb . Packages , & v ) ; err != nil {
return err
}
if _ , ok := v . ( [ ] interface { } ) ; ! ok {
log . Printf ( " skip release: %s, file: %s" , release , fileName )
return nil
2019-10-07 14:57:48 -07:00
}
2021-01-11 17:08:29 +02:00
// It should succeed now.
var pkgs [ ] packages
if err = json . Unmarshal ( secdb . Packages , & pkgs ) ; err != nil {
return err
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
for _ , pkg := range pkgs {
if err = u . savePkg ( secdb , pkg . Pkg , release ) ; err != nil {
return err
}
}
2019-04-30 13:02:09 +09:00
2021-01-11 17:08:29 +02:00
return nil
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
func ( u Updater ) savePkg ( secdb secdb , pkg pkg , release string ) error {
secfixes := map [ string ] [ ] string { }
for fixedVersion , v := range pkg . Secfixes {
// CVE-IDs might not be an array and it causes an unmarshal error.
vv , ok := v . ( [ ] interface { } )
if ! ok {
log . Printf ( " skip pkg: %s, version: %s" , pkg . Name , fixedVersion )
continue
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
var cveIDs [ ] string
for _ , v := range vv {
cveIDs = append ( cveIDs , v . ( string ) )
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
secfixes [ fixedVersion ] = cveIDs
}
advisory := advisory {
Name : pkg . Name ,
Secfixes : secfixes ,
Apkurl : secdb . Apkurl ,
Archs : secdb . Archs ,
Urlprefix : secdb . Urlprefix ,
Reponame : secdb . Reponame ,
Distroversion : secdb . Distroversion ,
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
release = strings . TrimPrefix ( release , "v" )
2022-12-04 04:07:37 -05:00
dir := filepath . Join ( u . vulnListDir , u . advisoryDir , release , secdb . Reponame )
2021-01-11 17:08:29 +02:00
file := fmt . Sprintf ( "%s.json" , pkg . Name )
if err := utils . WriteJSON ( u . appFs , dir , file , advisory ) ; err != nil {
return xerrors . Errorf ( "failed to write %s under %s: %w" , file , dir , err )
2019-04-30 13:02:09 +09:00
}
2021-01-11 17:08:29 +02:00
return nil
2019-04-30 13:02:09 +09:00
}