e081c6e763
* feat(alma): support AlmaLinux Errata * style(alma): change var name * fix(alma): fix test case * chore: fix typo * chore: use pb/v3 * chore: change by review * style: rename var * fix(alma): change location of the module field * feat(alma): more detailed by year * refactor(alma): do not loop twice * refactor(alma): use IssuedDate * refactor(alma): remove magic number * refactor(alma): use time.UnixMilli
198 lines
4.9 KiB
Go
198 lines
4.9 KiB
Go
package alma
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/aquasecurity/vuln-list-update/utils"
|
|
"github.com/cheggaaa/pb/v3"
|
|
"golang.org/x/xerrors"
|
|
)
|
|
|
|
const (
|
|
almaLinuxDir = "alma"
|
|
urlFormat = "https://errata.almalinux.org/%s/errata.json"
|
|
retry = 3
|
|
)
|
|
|
|
var (
|
|
AlmaReleaseVersion = []string{"8"}
|
|
)
|
|
|
|
type erratum struct {
|
|
ID OID `json:"_id"`
|
|
BsRepoID OID `json:"bs_repo_id"`
|
|
UpdateinfoID string `json:"updateinfo_id"`
|
|
Description string `json:"description"`
|
|
Fromstr string `json:"fromstr"`
|
|
IssuedDate Date `json:"issued_date"`
|
|
Pkglist Pkglist `json:"pkglist"`
|
|
Pushcount string `json:"pushcount"`
|
|
References []Reference `json:"references"`
|
|
Release string `json:"release"`
|
|
Rights string `json:"rights"`
|
|
Severity string `json:"severity"`
|
|
Solution string `json:"solution"`
|
|
Status string `json:"status"`
|
|
Summary string `json:"summary"`
|
|
Title string `json:"title"`
|
|
Type string `json:"type"`
|
|
UpdatedDate Date `json:"updated_date"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
type OID struct {
|
|
OID string `json:"$oid,omitempty"`
|
|
}
|
|
|
|
type Date struct {
|
|
Date int64 `json:"$date"`
|
|
}
|
|
|
|
type Pkglist struct {
|
|
Name string `json:"name"`
|
|
Shortname string `json:"shortname"`
|
|
Packages []Package `json:"packages"`
|
|
Module Module `json:"module"`
|
|
}
|
|
|
|
type Package struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
Release string `json:"release"`
|
|
Epoch string `json:"epoch"`
|
|
Arch string `json:"arch"`
|
|
Src string `json:"src"`
|
|
Filename string `json:"filename"`
|
|
Sum string `json:"sum"`
|
|
SumType interface{} `json:"sum_type"`
|
|
RebootSuggested int `json:"reboot_suggested"`
|
|
}
|
|
|
|
type Module struct {
|
|
Stream string `json:"stream,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Version int64 `json:"version,omitempty"`
|
|
Arch string `json:"arch,omitempty"`
|
|
Context string `json:"context,omitempty"`
|
|
}
|
|
|
|
type Reference struct {
|
|
Href string `json:"href"`
|
|
Type string `json:"type"`
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
}
|
|
|
|
type options struct {
|
|
urls map[string]string
|
|
dir string
|
|
retry int
|
|
}
|
|
|
|
type option func(*options)
|
|
|
|
func WithURLs(urls map[string]string) option {
|
|
return func(opts *options) { opts.urls = urls }
|
|
}
|
|
|
|
func WithDir(dir string) option {
|
|
return func(opts *options) { opts.dir = dir }
|
|
}
|
|
|
|
func WithRetry(retry int) option {
|
|
return func(opts *options) { opts.retry = retry }
|
|
}
|
|
|
|
type Config struct {
|
|
*options
|
|
}
|
|
|
|
func NewConfig(opts ...option) Config {
|
|
urls := map[string]string{}
|
|
for _, version := range AlmaReleaseVersion {
|
|
urls[version] = fmt.Sprintf(urlFormat, version)
|
|
}
|
|
|
|
o := &options{
|
|
urls: urls,
|
|
dir: utils.VulnListDir(),
|
|
retry: retry,
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(o)
|
|
}
|
|
|
|
return Config{
|
|
options: o,
|
|
}
|
|
}
|
|
|
|
func (c Config) Update() error {
|
|
for version, url := range c.urls {
|
|
log.Printf("Fetching security advisories of AlmaLinux %s ...\n", version)
|
|
if err := c.update(version, url); err != nil {
|
|
return xerrors.Errorf("failed to update security advisories of AlmaLinux %s: %w", version, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c Config) update(version, url string) error {
|
|
dirPath := filepath.Join(c.dir, almaLinuxDir, version)
|
|
log.Printf("Remove AlmaLinux %s directory %s\n", version, dirPath)
|
|
if err := os.RemoveAll(dirPath); err != nil {
|
|
return xerrors.Errorf("failed to remove AlmaLinux %s directory: %w", version, err)
|
|
}
|
|
if err := os.MkdirAll(dirPath, os.ModePerm); err != nil {
|
|
return xerrors.Errorf("failed to mkdir: %w", err)
|
|
}
|
|
|
|
body, err := utils.FetchURL(url, "", c.retry)
|
|
if err != nil {
|
|
return xerrors.Errorf("failed to fetch security advisories from AlmaLinux: %w", err)
|
|
}
|
|
|
|
var errata []erratum
|
|
if err := json.Unmarshal(body, &errata); err != nil {
|
|
return xerrors.Errorf("failed to unmarshal json: %w", err)
|
|
}
|
|
|
|
secErrata := map[string][]erratum{}
|
|
for _, erratum := range errata {
|
|
if !strings.HasPrefix(erratum.UpdateinfoID, "ALSA-") {
|
|
continue
|
|
}
|
|
|
|
y := strconv.Itoa(time.UnixMilli(erratum.IssuedDate.Date).Year())
|
|
secErrata[y] = append(secErrata[y], erratum)
|
|
}
|
|
|
|
for year, errata := range secErrata {
|
|
log.Printf("Write Errata for AlmaLinux %s %s\n", version, year)
|
|
|
|
if err := os.MkdirAll(filepath.Join(dirPath, year), os.ModePerm); err != nil {
|
|
return xerrors.Errorf("failed to mkdir: %w", err)
|
|
}
|
|
|
|
bar := pb.StartNew(len(errata))
|
|
for _, erratum := range errata {
|
|
filepath := filepath.Join(dirPath, year, fmt.Sprintf("%s.json", erratum.UpdateinfoID))
|
|
if err := utils.Write(filepath, erratum); err != nil {
|
|
return xerrors.Errorf("failed to write AlmaLinux CVE details: %w", err)
|
|
}
|
|
bar.Increment()
|
|
}
|
|
bar.Finish()
|
|
}
|
|
|
|
return nil
|
|
}
|