From 5e5074fbf116aba2fab40111a318f840ca9274dd Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Wed, 26 Jul 2023 13:34:15 +0200 Subject: [PATCH 1/2] Add time range to checker. --- cmd/csaf_checker/config.go | 56 +++++++++++++++++++++++++++++------ cmd/csaf_checker/processor.go | 22 ++++---------- docs/csaf_checker.md | 35 ++++++++++++++++++++-- 3 files changed, 85 insertions(+), 28 deletions(-) diff --git a/cmd/csaf_checker/config.go b/cmd/csaf_checker/config.go index 9da3eae..b8148e5 100644 --- a/cmd/csaf_checker/config.go +++ b/cmd/csaf_checker/config.go @@ -13,7 +13,9 @@ import ( "errors" "fmt" "net/http" + "time" + "github.com/csaf-poc/csaf_distribution/v2/internal/models" "github.com/csaf-poc/csaf_distribution/v2/internal/options" ) @@ -27,15 +29,16 @@ const ( type config struct { 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. - 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"` - 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"` - Version bool `long:"version" description:"Display version of the binary" toml:"-"` - 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"` - 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"` + 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"` + 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"` + Version bool `long:"version" description:"Display version of the binary" toml:"-"` + 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"` + Years *uint `long:"years" short:"y" description:"Number of years to look back from now" value-name:"YEARS" toml:"years"` + 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"` 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:"-"` clientCerts []tls.Certificate + ageAccept func(time.Time) bool } // 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. func (cfg *config) prepare() error { // 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; { case hasCert && !hasKey || !hasCert && hasKey: @@ -116,3 +130,27 @@ func (cfg *config) prepare() error { } 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 +} diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index fb678ac..bc1eeaa 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -44,7 +44,6 @@ type processor struct { validator csaf.RemoteValidator client util.Client unauthClient util.Client - ageAccept func(time.Time) bool redirects map[string][]string noneTLS util.Set[string] @@ -187,7 +186,6 @@ func newProcessor(cfg *config) (*processor, error) { cfg: cfg, alreadyChecked: map[string]whereType{}, expr: util.NewPathEval(), - ageAccept: ageAccept(cfg), validator: validator, labelChecker: labelChecker{ 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. func (p *processor) clean() { p.redirects = nil @@ -557,8 +545,8 @@ func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { rfeed.Entries(func(entry *csaf.Entry) { // Filter if we have date checking. - if p.ageAccept != nil { - if pub := time.Time(entry.Published); !pub.IsZero() && !p.ageAccept(pub) { + if p.cfg.ageAccept != nil { + if pub := time.Time(entry.Published); !pub.IsZero() && !p.cfg.ageAccept(pub) { return } } @@ -669,7 +657,7 @@ func (p *processor) integrity( if m := yearFromURL.FindStringSubmatch(u); m != nil { year, _ := strconv.Atoi(m[1]) // Check if we are in checking time interval. - if p.ageAccept != nil && !p.ageAccept( + if p.cfg.ageAccept != nil && !p.cfg.ageAccept( time.Date( year, 12, 31, // Assume last day of year. 23, 59, 59, 0, // 23:59:59 @@ -975,7 +963,7 @@ func (p *processor) checkChanges(base string, mask whereType) error { return nil, nil, err } // Apply date range filtering. - if p.ageAccept != nil && !p.ageAccept(t) { + if p.cfg.ageAccept != nil && !p.cfg.ageAccept(t) { continue } path := r[pathColumn] @@ -992,7 +980,7 @@ func (p *processor) checkChanges(base string, mask whereType) error { if len(files) == 0 { var filtered string - if p.ageAccept != nil { + if p.cfg.ageAccept != nil { filtered = " (maybe filtered out by time interval)" } p.badChanges.warn("no entries in changes.csv found" + filtered) diff --git a/docs/csaf_checker.md b/docs/csaf_checker.md index 53abfa1..e0faa5a 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -16,10 +16,11 @@ Application Options: -v, --verbose Verbose output -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 + -t, --timerange=RANGE RANGE of time from which advisories to download -H, --header= One or more extra HTTP header fields --validator=URL URL to validate documents remotely --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 Help Options: @@ -48,6 +49,7 @@ insecure = false verbose = false # rate # not set by default # years # not set by default +# timerange # not set by default # header # not set by default # validator # not set by default # validatorcache # not set by default @@ -64,8 +66,37 @@ type 1: warning 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 From ac8d8a91963c18f378f22f77d0c4a186a067092d Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Wed, 26 Jul 2023 13:46:13 +0200 Subject: [PATCH 2/2] Improve wording of docs a bit. --- docs/csaf_checker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/csaf_checker.md b/docs/csaf_checker.md index e0faa5a..c54f505 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -68,7 +68,7 @@ type 2: error 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. +The options `years` and `timerange` allow to only check 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: