1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 11:55:40 +01:00

reworked loading, checking and storing interims.

This commit is contained in:
Sascha L. Teichmann 2022-08-02 17:01:48 +02:00
parent 892a0b941b
commit ef829131e1
2 changed files with 88 additions and 75 deletions

View file

@ -16,6 +16,7 @@ import (
"os" "os"
"runtime" "runtime"
"sync" "sync"
"time"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/gopenpgp/v2/crypto"
@ -85,6 +86,16 @@ type config struct {
keyErr error keyErr error
} }
// TooOldForInterims returns a function that tells if a given
// time is too old for the configured interims interval.
func (c *config) TooOldForInterims() func(time.Time) bool {
if c.InterimYears <= 0 {
return func(time.Time) bool { return false }
}
from := time.Now().AddDate(-c.InterimYears, 0, 0)
return func(t time.Time) bool { return t.Before(from) }
}
// serviceDocument tells if we should generate a service document for a // serviceDocument tells if we should generate a service document for a
// given provider. // given provider.
func (p *provider) serviceDocument(c *config) bool { func (p *provider) serviceDocument(c *config) bool {

View file

@ -34,23 +34,30 @@ type interimJob struct {
err error err error
} }
// statusExpr is used as an expression to check the new status
// of an advisory which was interim before.
const statusExpr = `$.document.tracking.status`
// checkInterims checks the current status of the given
// interim advisories. It returns a slice of advisories
// which are not finished, yet.
func (w *worker) checkInterims( func (w *worker) checkInterims(
tx *lazyTransaction, tx *lazyTransaction,
label string, label string,
interims [][2]string, interims []interimsEntry,
) ([]string, error) { ) ([]interimsEntry, error) {
var data bytes.Buffer var data bytes.Buffer
labelPath := filepath.Join(tx.Src(), label) labelPath := filepath.Join(tx.Src(), label)
// advisories which are not interim any longer. // advisories which are not interim any longer.
var finalized []string var notFinalized []interimsEntry
for _, interim := range interims { for _, interim := range interims {
local := filepath.Join(labelPath, interim[0]) local := filepath.Join(labelPath, interim.path())
url := interim[1] url := interim.url()
// Load local SHA256 of the advisory // Load local SHA256 of the advisory
localHash, err := util.HashFromFile(local + ".sha256") localHash, err := util.HashFromFile(local + ".sha256")
@ -84,6 +91,7 @@ func (w *worker) checkInterims(
// If the hashes are equal then we can ignore this advisory. // If the hashes are equal then we can ignore this advisory.
if bytes.Equal(localHash, remoteHash) { if bytes.Equal(localHash, remoteHash) {
notFinalized = append(notFinalized, interim)
continue continue
} }
@ -106,7 +114,7 @@ func (w *worker) checkInterims(
} }
// Overwrite in the cloned folder. // Overwrite in the cloned folder.
nlocal := filepath.Join(dst, label, interim[0]) nlocal := filepath.Join(dst, label, interim.path())
bytes := data.Bytes() bytes := data.Bytes()
@ -135,9 +143,18 @@ func (w *worker) checkInterims(
if err := w.downloadSignatureOrSign(sigURL, ascFile, bytes); err != nil { if err := w.downloadSignatureOrSign(sigURL, ascFile, bytes); err != nil {
return nil, err return nil, err
} }
// Check if we can remove this advisory as it is not iterim any more.
var status string
if err := w.expr.Extract(statusExpr, util.StringMatcher(&status), true, doc); err != nil {
return nil, err
}
if status == "interim" {
notFinalized = append(notFinalized, interim)
}
} }
return finalized, nil return notFinalized, nil
} }
// setupProviderInterim prepares the worker for a specific provider. // setupProviderInterim prepares the worker for a specific provider.
@ -156,6 +173,8 @@ func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) {
defer wg.Done() defer wg.Done()
path := filepath.Join(w.processor.cfg.Web, ".well-known", "csaf-aggregator") path := filepath.Join(w.processor.cfg.Web, ".well-known", "csaf-aggregator")
tooOld := w.processor.cfg.TooOldForInterims()
for j := range jobs { for j := range jobs {
w.setupProviderInterim(j.provider) w.setupProviderInterim(j.provider)
@ -177,8 +196,7 @@ func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) {
labelPath := filepath.Join(providerPath, label) labelPath := filepath.Join(providerPath, label)
interimsCSV := filepath.Join(labelPath, "interims.csv") interimsCSV := filepath.Join(labelPath, "interims.csv")
interims, err := readInterims( interims, olds, err := readInterims(interimsCSV, tooOld)
interimsCSV, w.processor.cfg.InterimYears)
if err != nil { if err != nil {
return err return err
} }
@ -189,19 +207,23 @@ func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) {
} }
// Compare locals against remotes. // Compare locals against remotes.
finalized, err := w.checkInterims(tx, label, interims) notFinalized, err := w.checkInterims(tx, label, interims)
if err != nil { if err != nil {
return err return err
} }
if len(finalized) > 0 { // Simply append the olds. Maybe we got re-configured with
// a greater interims interval later.
notFinalized = append(notFinalized, olds...)
if len(notFinalized) > 0 {
// We want to write in the transaction folder. // We want to write in the transaction folder.
dst, err := tx.Dst() dst, err := tx.Dst()
if err != nil { if err != nil {
return err return err
} }
interimsCSV := filepath.Join(dst, label, "interims.csv") interimsCSV := filepath.Join(dst, label, "interims.csv")
if err := writeInterims(interimsCSV, finalized); err != nil { if err := writeInterims(interimsCSV, notFinalized); err != nil {
return err return err
} }
} }
@ -265,49 +287,18 @@ func (p *processor) interim() error {
return joinErrors(errs) return joinErrors(errs)
} }
func writeInterims(interimsCSV string, finalized []string) error { type interimsEntry [3]string
// In case this is a longer list (unlikely). func (ie interimsEntry) date() string { return ie[0] }
removed := make(map[string]bool, len(finalized)) func (ie interimsEntry) url() string { return ie[1] }
for _, f := range finalized { func (ie interimsEntry) path() string { return ie[2] }
removed[f] = true
}
lines, err := func() ([][]string, error) { func writeInterims(interimsCSV string, interims []interimsEntry) error {
interimsF, err := os.Open(interimsCSV)
if err != nil {
return nil, err
}
defer interimsF.Close()
c := csv.NewReader(interimsF)
c.FieldsPerRecord = 3
var lines [][]string if len(interims) == 0 {
for {
record, err := c.Read()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
// If not finalized it survives
if !removed[record[1]] {
lines = append(lines, record)
}
}
return lines, nil
}()
if err != nil {
return err
}
// All interims are finalized now -> remove file.
if len(lines) == 0 {
return os.RemoveAll(interimsCSV) return os.RemoveAll(interimsCSV)
} }
return os.RemoveAll(interimsCSV)
// Overwrite old. It's save because we are in a transaction. // Overwrite old. It's save because we are in a transaction.
f, err := os.Create(interimsCSV) f, err := os.Create(interimsCSV)
@ -316,8 +307,10 @@ func writeInterims(interimsCSV string, finalized []string) error {
} }
c := csv.NewWriter(f) c := csv.NewWriter(f)
if err := c.WriteAll(lines); err != nil { for _, ie := range interims {
return f.Close() if err := c.Write(ie[:]); err != nil {
return err
}
} }
c.Flush() c.Flush()
@ -332,49 +325,58 @@ func writeInterims(interimsCSV string, finalized []string) error {
// readInterims scans a interims.csv file for matching // readInterims scans a interims.csv file for matching
// iterim advisories. Its sorted with youngest // iterim advisories. Its sorted with youngest
// first, so we can stop scanning if entries get too old. // first, so we can stop scanning if entries get too old.
func readInterims(interimsCSV string, years int) ([][2]string, error) { // It returns two slices: The advisories that are young enough
// and a slice of the advisories that are too old.
var tooOld func(time.Time) bool func readInterims(
interimsCSV string,
if years <= 0 { tooOld func(time.Time) bool,
tooOld = func(time.Time) bool { return false } ) ([]interimsEntry, []interimsEntry, error) {
} else {
from := time.Now().AddDate(-years, 0, 0)
tooOld = func(t time.Time) bool { return t.Before(from) }
}
interimsF, err := os.Open(interimsCSV) interimsF, err := os.Open(interimsCSV)
if err != nil { if err != nil {
// None existing file -> no interims. // None existing file -> no interims.
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, nil return nil, nil, nil
} }
return nil, err return nil, nil, err
} }
defer interimsF.Close() defer interimsF.Close()
c := csv.NewReader(interimsF) c := csv.NewReader(interimsF)
c.FieldsPerRecord = 3 c.FieldsPerRecord = 3
var files [][2]string var (
files []interimsEntry
olds []interimsEntry
)
youngEnough := true
for { for {
record, err := c.Read() row, err := c.Read()
if err == io.EOF { if err == io.EOF {
break break
} }
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
t, err := time.Parse(time.RFC3339, record[0])
if err != nil { if youngEnough {
return nil, err t, err := time.Parse(time.RFC3339, row[0])
if err != nil {
return nil, nil, err
}
if tooOld(t) {
olds = []interimsEntry{{row[0], row[1], row[2]}}
youngEnough = false
} else {
files = append(files, interimsEntry{row[0], row[1], row[2]})
}
} else {
// These are too old.
olds = append(olds, interimsEntry{row[0], row[1], row[2]})
} }
if tooOld(t) {
break
}
files = append(files, [2]string{record[1], record[2]})
} }
return files, nil return files, olds, nil
} }