mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 05:40:11 +01:00
Add time range to checker.
This commit is contained in:
parent
125028773f
commit
5e5074fbf1
3 changed files with 85 additions and 28 deletions
|
|
@ -13,7 +13,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/csaf-poc/csaf_distribution/v2/internal/models"
|
||||||
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
|
"github.com/csaf-poc/csaf_distribution/v2/internal/options"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -27,15 +29,16 @@ const (
|
||||||
type config struct {
|
type config struct {
|
||||||
Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE" toml:"output"`
|
Output string `short:"o" long:"output" description:"File name of the generated report" value-name:"REPORT-FILE" toml:"output"`
|
||||||
//lint:ignore SA5008 We are using choice twice: json, html.
|
//lint:ignore SA5008 We are using choice twice: json, html.
|
||||||
Format outputFormat `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" toml:"format"`
|
Format outputFormat `short:"f" long:"format" choice:"json" choice:"html" description:"Format of report" toml:"format"`
|
||||||
Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"`
|
Insecure bool `long:"insecure" description:"Do not check TLS certificates from provider" toml:"insecure"`
|
||||||
ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE" toml:"client_cert"`
|
ClientCert *string `long:"client-cert" description:"TLS client certificate file (PEM encoded data)" value-name:"CERT-FILE" toml:"client_cert"`
|
||||||
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE" toml:"client_key"`
|
ClientKey *string `long:"client-key" description:"TLS client private key file (PEM encoded data)" value-name:"KEY-FILE" toml:"client_key"`
|
||||||
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
|
Version bool `long:"version" description:"Display version of the binary" toml:"-"`
|
||||||
Verbose bool `long:"verbose" short:"v" description:"Verbose output" toml:"verbose"`
|
Verbose bool `long:"verbose" short:"v" description:"Verbose output" toml:"verbose"`
|
||||||
Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second (defaults to unlimited)" toml:"rate"`
|
Rate *float64 `long:"rate" short:"r" description:"The average upper limit of https operations per second (defaults to unlimited)" toml:"rate"`
|
||||||
Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS" toml:"years"`
|
Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS" toml:"years"`
|
||||||
ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields" toml:"header"`
|
Range *models.TimeRange `long:"timerange" short:"t" description:"RANGE of time from which advisories to download" value-name:"RANGE" toml:"timerange"`
|
||||||
|
ExtraHeader http.Header `long:"header" short:"H" description:"One or more extra HTTP header fields" toml:"header"`
|
||||||
|
|
||||||
RemoteValidator string `long:"validator" description:"URL to validate documents remotely" value-name:"URL" toml:"validator"`
|
RemoteValidator string `long:"validator" description:"URL to validate documents remotely" value-name:"URL" toml:"validator"`
|
||||||
RemoteValidatorCache string `long:"validatorcache" description:"FILE to cache remote validations" value-name:"FILE" toml:"validator_cache"`
|
RemoteValidatorCache string `long:"validatorcache" description:"FILE to cache remote validations" value-name:"FILE" toml:"validator_cache"`
|
||||||
|
|
@ -44,6 +47,7 @@ type config struct {
|
||||||
Config string `short:"c" long:"config" description:"Path to config TOML file" value-name:"TOML-FILE" toml:"-"`
|
Config string `short:"c" long:"config" description:"Path to config TOML file" value-name:"TOML-FILE" toml:"-"`
|
||||||
|
|
||||||
clientCerts []tls.Certificate
|
clientCerts []tls.Certificate
|
||||||
|
ageAccept func(time.Time) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// configPaths are the potential file locations of the config file.
|
// configPaths are the potential file locations of the config file.
|
||||||
|
|
@ -102,6 +106,16 @@ func (cfg *config) protectedAccess() bool {
|
||||||
// prepare prepares internal state of a loaded configuration.
|
// prepare prepares internal state of a loaded configuration.
|
||||||
func (cfg *config) prepare() error {
|
func (cfg *config) prepare() error {
|
||||||
// Load client certs.
|
// Load client certs.
|
||||||
|
if err := cfg.prepareCertificates(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg.prepareTimeRangeFilter()
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareCertificates loads the client side certificates used by the HTTP client.
|
||||||
|
func (cfg *config) prepareCertificates() error {
|
||||||
|
|
||||||
switch hasCert, hasKey := cfg.ClientCert != nil, cfg.ClientKey != nil; {
|
switch hasCert, hasKey := cfg.ClientCert != nil, cfg.ClientKey != nil; {
|
||||||
|
|
||||||
case hasCert && !hasKey || !hasCert && hasKey:
|
case hasCert && !hasKey || !hasCert && hasKey:
|
||||||
|
|
@ -116,3 +130,27 @@ func (cfg *config) prepare() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// acceptYears returns a filter that accepts advisories from the last years.
|
||||||
|
func acceptYears(years uint) func(time.Time) bool {
|
||||||
|
good := time.Now().AddDate(-int(years), 0, 0)
|
||||||
|
return func(t time.Time) bool {
|
||||||
|
return !t.Before(good)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareTimeRangeFilter sets up the filter in which time range
|
||||||
|
// advisory should be considered for checking.
|
||||||
|
func (cfg *config) prepareTimeRangeFilter() error {
|
||||||
|
switch {
|
||||||
|
case cfg.Years != nil && cfg.Range != nil:
|
||||||
|
return errors.New(`"timerange" and "years" are both configured: only one allowed`)
|
||||||
|
|
||||||
|
case cfg.Years != nil:
|
||||||
|
cfg.ageAccept = acceptYears(*cfg.Years)
|
||||||
|
|
||||||
|
case cfg.Range != nil:
|
||||||
|
cfg.ageAccept = cfg.Range.Contains
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ type processor struct {
|
||||||
validator csaf.RemoteValidator
|
validator csaf.RemoteValidator
|
||||||
client util.Client
|
client util.Client
|
||||||
unauthClient util.Client
|
unauthClient util.Client
|
||||||
ageAccept func(time.Time) bool
|
|
||||||
|
|
||||||
redirects map[string][]string
|
redirects map[string][]string
|
||||||
noneTLS util.Set[string]
|
noneTLS util.Set[string]
|
||||||
|
|
@ -187,7 +186,6 @@ func newProcessor(cfg *config) (*processor, error) {
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
alreadyChecked: map[string]whereType{},
|
alreadyChecked: map[string]whereType{},
|
||||||
expr: util.NewPathEval(),
|
expr: util.NewPathEval(),
|
||||||
ageAccept: ageAccept(cfg),
|
|
||||||
validator: validator,
|
validator: validator,
|
||||||
labelChecker: labelChecker{
|
labelChecker: labelChecker{
|
||||||
advisories: map[csaf.TLPLabel]util.Set[string]{},
|
advisories: map[csaf.TLPLabel]util.Set[string]{},
|
||||||
|
|
@ -204,16 +202,6 @@ func (p *processor) close() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ageAccept(cfg *config) func(time.Time) bool {
|
|
||||||
if cfg.Years == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
good := time.Now().AddDate(-int(*cfg.Years), 0, 0)
|
|
||||||
return func(t time.Time) bool {
|
|
||||||
return !t.Before(good)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clean clears the fields values of the given processor.
|
// clean clears the fields values of the given processor.
|
||||||
func (p *processor) clean() {
|
func (p *processor) clean() {
|
||||||
p.redirects = nil
|
p.redirects = nil
|
||||||
|
|
@ -557,8 +545,8 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) {
|
||||||
rfeed.Entries(func(entry *csaf.Entry) {
|
rfeed.Entries(func(entry *csaf.Entry) {
|
||||||
|
|
||||||
// Filter if we have date checking.
|
// Filter if we have date checking.
|
||||||
if p.ageAccept != nil {
|
if p.cfg.ageAccept != nil {
|
||||||
if pub := time.Time(entry.Published); !pub.IsZero() && !p.ageAccept(pub) {
|
if pub := time.Time(entry.Published); !pub.IsZero() && !p.cfg.ageAccept(pub) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -669,7 +657,7 @@ func (p *processor) integrity(
|
||||||
if m := yearFromURL.FindStringSubmatch(u); m != nil {
|
if m := yearFromURL.FindStringSubmatch(u); m != nil {
|
||||||
year, _ := strconv.Atoi(m[1])
|
year, _ := strconv.Atoi(m[1])
|
||||||
// Check if we are in checking time interval.
|
// Check if we are in checking time interval.
|
||||||
if p.ageAccept != nil && !p.ageAccept(
|
if p.cfg.ageAccept != nil && !p.cfg.ageAccept(
|
||||||
time.Date(
|
time.Date(
|
||||||
year, 12, 31, // Assume last day of year.
|
year, 12, 31, // Assume last day of year.
|
||||||
23, 59, 59, 0, // 23:59:59
|
23, 59, 59, 0, // 23:59:59
|
||||||
|
|
@ -975,7 +963,7 @@ func (p *processor) checkChanges(base string, mask whereType) error {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
// Apply date range filtering.
|
// Apply date range filtering.
|
||||||
if p.ageAccept != nil && !p.ageAccept(t) {
|
if p.cfg.ageAccept != nil && !p.cfg.ageAccept(t) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
path := r[pathColumn]
|
path := r[pathColumn]
|
||||||
|
|
@ -992,7 +980,7 @@ func (p *processor) checkChanges(base string, mask whereType) error {
|
||||||
|
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
var filtered string
|
var filtered string
|
||||||
if p.ageAccept != nil {
|
if p.cfg.ageAccept != nil {
|
||||||
filtered = " (maybe filtered out by time interval)"
|
filtered = " (maybe filtered out by time interval)"
|
||||||
}
|
}
|
||||||
p.badChanges.warn("no entries in changes.csv found" + filtered)
|
p.badChanges.warn("no entries in changes.csv found" + filtered)
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,11 @@ Application Options:
|
||||||
-v, --verbose Verbose output
|
-v, --verbose Verbose output
|
||||||
-r, --rate= The average upper limit of https operations per second (defaults to unlimited)
|
-r, --rate= The average upper limit of https operations per second (defaults to unlimited)
|
||||||
-y, --years=YEARS Number of years to look back from now
|
-y, --years=YEARS Number of years to look back from now
|
||||||
|
-t, --timerange=RANGE RANGE of time from which advisories to download
|
||||||
-H, --header= One or more extra HTTP header fields
|
-H, --header= One or more extra HTTP header fields
|
||||||
--validator=URL URL to validate documents remotely
|
--validator=URL URL to validate documents remotely
|
||||||
--validatorcache=FILE FILE to cache remote validations
|
--validatorcache=FILE FILE to cache remote validations
|
||||||
--validatorpreset= One or more presets to validate remotely (default: mandatory)
|
--validatorpreset= One or more presets to validate remotely (default: [mandatory])
|
||||||
-c, --config=TOML-FILE Path to config TOML file
|
-c, --config=TOML-FILE Path to config TOML file
|
||||||
|
|
||||||
Help Options:
|
Help Options:
|
||||||
|
|
@ -48,6 +49,7 @@ insecure = false
|
||||||
verbose = false
|
verbose = false
|
||||||
# rate # not set by default
|
# rate # not set by default
|
||||||
# years # not set by default
|
# years # not set by default
|
||||||
|
# timerange # not set by default
|
||||||
# header # not set by default
|
# header # not set by default
|
||||||
# validator # not set by default
|
# validator # not set by default
|
||||||
# validatorcache # not set by default
|
# validatorcache # not set by default
|
||||||
|
|
@ -64,8 +66,37 @@ type 1: warning
|
||||||
type 2: error
|
type 2: error
|
||||||
```
|
```
|
||||||
|
|
||||||
The checker result is a success if no checks resulted in type 2, and a failure otherwise.
|
The checker result is a success if no checks resulted in type 2, and a failure otherwise.
|
||||||
|
|
||||||
|
`years` and `timerange` allows only checking advisories from a given time interval.
|
||||||
|
It is only allowed to specify one off them.
|
||||||
|
`years` looks number of years back from now. `timerange` values allow finer controls:
|
||||||
|
|
||||||
|
1. Relative. If the given string follows the rules of being a [Go duration](https://pkg.go.dev/time@go1.20.6#ParseDuration)
|
||||||
|
the time interval from now minus that duration till now is used.
|
||||||
|
E.g. `"3h"` means checking the advisories that have changed in the last three hours.
|
||||||
|
|
||||||
|
2. Absolute. If the given string is an RFC 3339 date timestamp the time interval between
|
||||||
|
this date and now is used.
|
||||||
|
E.g. `"2006-01-02"` means that all files between 2006 January 2nd and now going to be
|
||||||
|
checked.
|
||||||
|
Accepted patterns are:
|
||||||
|
- `"2006-01-02T15:04:05Z"`
|
||||||
|
- `"2006-01-02T15:04:05+07:00"`
|
||||||
|
- `"2006-01-02T15:04:05-07:00"`
|
||||||
|
- `"2006-01-02T15:04:05"`
|
||||||
|
- `"2006-01-02T15:04"`
|
||||||
|
- `"2006-01-02T15"`
|
||||||
|
- `"2006-01-02"`
|
||||||
|
- `"2006-01"`
|
||||||
|
- `"2006"`
|
||||||
|
|
||||||
|
Missing parts are set to the smallest value possible in that field.
|
||||||
|
|
||||||
|
3. Range. Same as 2 but separated by a `,` to span an interval. e.g `2019,2024`
|
||||||
|
spans an interval from 1st January 2019 to the 1st January of 2024.
|
||||||
|
|
||||||
|
All interval boundaries are inclusive.
|
||||||
|
|
||||||
### Remarks
|
### Remarks
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue