From 78d8b89aca3bc226d07ba4aea513471a9c4a350d Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 21 Jun 2022 14:47:06 +0200 Subject: [PATCH] Add support for remote validation services. (#185) * Simple tool to test the remote validation * Added remote validator support to provider. * Added remote validation to aggregator. * Calm golint * Removed csaf_remote_validator tool as it was only for dev. * Re-added csaf_remote_validator tool. Testing is not done. * Embed the document entirely * Include testing the remote validator in the Itests * Change permission of the script * Remove code for Itests * As these will be done in another branch Co-authored-by: Fadi Abbud --- 3rdpartylicenses.md | 1 + cmd/csaf_aggregator/config.go | 3 + cmd/csaf_aggregator/full.go | 20 ++- cmd/csaf_aggregator/indices.go | 4 +- cmd/csaf_aggregator/interim.go | 10 +- cmd/csaf_aggregator/mirror.go | 35 ++-- cmd/csaf_aggregator/processor.go | 19 +- cmd/csaf_provider/actions.go | 15 ++ cmd/csaf_provider/config.go | 31 ++-- cmd/csaf_remote_validator/main.go | 79 +++++++++ csaf/models.go | 2 +- csaf/remotevalidation.go | 281 ++++++++++++++++++++++++++++++ docs/csaf_aggregator.md | 1 + docs/csaf_provider.md | 4 +- go.mod | 1 + go.sum | 3 + 16 files changed, 466 insertions(+), 43 deletions(-) create mode 100644 cmd/csaf_remote_validator/main.go create mode 100644 csaf/remotevalidation.go diff --git a/3rdpartylicenses.md b/3rdpartylicenses.md index 4713aa1..6a86695 100644 --- a/3rdpartylicenses.md +++ b/3rdpartylicenses.md @@ -21,3 +21,4 @@ | github.com/gofrs/flock | BSD-3-Clause | | github.com/PuerkitoBio/goquery | BSD-3-Clause | | github.com/andybalholm/cascadia | BSD-2-Clause | +| go.etcd.io/bbolt | MIT | diff --git a/cmd/csaf_aggregator/config.go b/cmd/csaf_aggregator/config.go index be13674..af5d5a0 100644 --- a/cmd/csaf_aggregator/config.go +++ b/cmd/csaf_aggregator/config.go @@ -67,6 +67,9 @@ type config struct { // for interim advisories. Less/equal zero means forever. InterimYears int `toml:"interim_years"` + // RemoteValidator configures an optional remote validation. + RemoteValidatorOptions *csaf.RemoteValidatorOptions `toml:"remote_validator"` + keyMu sync.Mutex key *crypto.Key keyErr error diff --git a/cmd/csaf_aggregator/full.go b/cmd/csaf_aggregator/full.go index 7cc4e77..d4835ae 100644 --- a/cmd/csaf_aggregator/full.go +++ b/cmd/csaf_aggregator/full.go @@ -36,7 +36,7 @@ func (w *worker) setupProviderFull(provider *provider) error { w.provider = provider // Each job needs a separate client. - w.client = w.cfg.httpClient(provider) + w.client = w.processor.cfg.httpClient(provider) // We need the provider metadata in all cases. if err := w.locateProviderMetadata(provider.Domain); err != nil { @@ -83,6 +83,22 @@ func (p *processor) full() error { var doWork fullWorkFunc if p.cfg.runAsMirror() { + + // check if we need to setup a remote validator + if p.cfg.RemoteValidatorOptions != nil { + validator, err := p.cfg.RemoteValidatorOptions.Open() + if err != nil { + return err + } + + // Not sure if we really need it to be serialized. + p.remoteValidator = csaf.SynchronizedRemoteValidator(validator) + defer func() { + p.remoteValidator.Close() + p.remoteValidator = nil + }() + } + doWork = (*worker).mirror log.Println("Running in aggregator mode") } else { @@ -96,7 +112,7 @@ func (p *processor) full() error { log.Printf("Starting %d workers.\n", p.cfg.Workers) for i := 1; i <= p.cfg.Workers; i++ { wg.Add(1) - w := newWorker(i, p.cfg) + w := newWorker(i, p) go w.fullWork(&wg, doWork, queue) } diff --git a/cmd/csaf_aggregator/indices.go b/cmd/csaf_aggregator/indices.go index b7f4e2b..3f89292 100644 --- a/cmd/csaf_aggregator/indices.go +++ b/cmd/csaf_aggregator/indices.go @@ -143,7 +143,7 @@ func (w *worker) writeROLIE(label string, summaries []summary) error { fname := "csaf-feed-tlp-" + labelFolder + ".json" - feedURL := w.cfg.Domain + "/.well-known/csaf-aggregator/" + + feedURL := w.processor.cfg.Domain + "/.well-known/csaf-aggregator/" + w.provider.Name + "/" + labelFolder + "/" + fname entries := make([]*csaf.Entry, len(summaries)) @@ -156,7 +156,7 @@ func (w *worker) writeROLIE(label string, summaries []summary) error { for i := range summaries { s := &summaries[i] - csafURL := w.cfg.Domain + "/.well-known/csaf-aggregator/" + + csafURL := w.processor.cfg.Domain + "/.well-known/csaf-aggregator/" + w.provider.Name + "/" + label + "/" + strconv.Itoa(s.summary.InitialReleaseDate.Year()) + "/" + s.filename diff --git a/cmd/csaf_aggregator/interim.go b/cmd/csaf_aggregator/interim.go index 01f1a41..d28c9bd 100644 --- a/cmd/csaf_aggregator/interim.go +++ b/cmd/csaf_aggregator/interim.go @@ -149,12 +149,12 @@ func (w *worker) setupProviderInterim(provider *provider) { w.provider = provider // Each job needs a separate client. - w.client = w.cfg.httpClient(provider) + w.client = w.processor.cfg.httpClient(provider) } func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) { defer wg.Done() - path := filepath.Join(w.cfg.Web, ".well-known", "csaf-aggregator") + path := filepath.Join(w.processor.cfg.Web, ".well-known", "csaf-aggregator") for j := range jobs { w.setupProviderInterim(j.provider) @@ -162,7 +162,7 @@ func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) { providerPath := filepath.Join(path, j.provider.Name) j.err = func() error { - tx := newLazyTransaction(providerPath, w.cfg.Folder) + tx := newLazyTransaction(providerPath, w.processor.cfg.Folder) defer tx.rollback() // Try all the labels @@ -178,7 +178,7 @@ func (w *worker) interimWork(wg *sync.WaitGroup, jobs <-chan *interimJob) { interimsCSV := filepath.Join(labelPath, "interims.csv") interims, err := readInterims( - interimsCSV, w.cfg.InterimYears) + interimsCSV, w.processor.cfg.InterimYears) if err != nil { return err } @@ -240,7 +240,7 @@ func (p *processor) interim() error { log.Printf("Starting %d workers.\n", p.cfg.Workers) for i := 1; i <= p.cfg.Workers; i++ { wg.Add(1) - w := newWorker(i, p.cfg) + w := newWorker(i, p) go w.interimWork(&wg, queue) } diff --git a/cmd/csaf_aggregator/mirror.go b/cmd/csaf_aggregator/mirror.go index 37067c2..d904695 100644 --- a/cmd/csaf_aggregator/mirror.go +++ b/cmd/csaf_aggregator/mirror.go @@ -183,7 +183,7 @@ func (w *worker) mirrorInternal() (*csaf.AggregatorCSAFProvider, error) { // Add us as a mirror. mirrorURL := csaf.ProviderURL( fmt.Sprintf("%s/.well-known/csaf-aggregator/%s/provider-metadata.json", - w.cfg.Domain, w.provider.Name)) + w.processor.cfg.Domain, w.provider.Name)) acp.Mirrors = []csaf.ProviderURL{ mirrorURL, @@ -207,7 +207,7 @@ func (w *worker) writeProviderMetadata() error { fname := filepath.Join(w.dir, "provider-metadata.json") pm := csaf.NewProviderMetadataPrefix( - w.cfg.Domain+"/.well-known/csaf-aggregator/"+w.provider.Name, + w.processor.cfg.Domain+"/.well-known/csaf-aggregator/"+w.provider.Name, w.labelsFromSummaries()) // Figure out the role @@ -255,7 +255,7 @@ func (w *worker) mirrorPGPKeys(pm *csaf.ProviderMetadata) error { localKeyURL := func(fingerprint string) string { return fmt.Sprintf("%s/.well-known/csaf-aggregator/%s/openpgp/%s.asc", - w.cfg.Domain, w.provider.Name, fingerprint) + w.processor.cfg.Domain, w.provider.Name, fingerprint) } for i := range pm.PGPKeys { @@ -311,12 +311,12 @@ func (w *worker) mirrorPGPKeys(pm *csaf.ProviderMetadata) error { // If we have public key configured copy it into the new folder - if w.cfg.OpenPGPPublicKey == "" { + if w.processor.cfg.OpenPGPPublicKey == "" { return nil } // Load the key for the fingerprint. - data, err := os.ReadFile(w.cfg.OpenPGPPublicKey) + data, err := os.ReadFile(w.processor.cfg.OpenPGPPublicKey) if err != nil { os.RemoveAll(openPGPFolder) return err @@ -390,7 +390,7 @@ func (w *worker) createAggregatorProvider() (*csaf.AggregatorCSAFProvider, error func (w *worker) doMirrorTransaction() error { webTarget := filepath.Join( - w.cfg.Web, ".well-known", "csaf-aggregator", w.provider.Name) + w.processor.cfg.Web, ".well-known", "csaf-aggregator", w.provider.Name) var oldWeb string @@ -408,7 +408,7 @@ func (w *worker) doMirrorTransaction() error { } // Check if there is a sysmlink already. - target := filepath.Join(w.cfg.Folder, w.provider.Name) + target := filepath.Join(w.processor.cfg.Folder, w.provider.Name) log.Printf("target: '%s'\n", target) exists, err := util.PathExists(target) @@ -472,14 +472,14 @@ func (w *worker) downloadSignature(path string) (string, error) { // sign signs the given data with the configured key. func (w *worker) sign(data []byte) (string, error) { if w.signRing == nil { - key, err := w.cfg.privateOpenPGPKey() + key, err := w.processor.cfg.privateOpenPGPKey() if err != nil { return "", err } if key == nil { return "", nil } - if pp := w.cfg.Passphrase; pp != nil { + if pp := w.processor.cfg.Passphrase; pp != nil { if key, err = key.Unlock([]byte(*pp)); err != nil { return "", err } @@ -544,17 +544,32 @@ func (w *worker) mirrorFiles(tlpLabel *csaf.TLPLabel, files []string) error { continue } + // Check against CSAF schema. errors, err := csaf.ValidateCSAF(advisory) if err != nil { log.Printf("error: %s: %v", file, err) continue } if len(errors) > 0 { - log.Printf("CSAF file %s has %d validation errors.", + log.Printf("CSAF file %s has %d validation errors.\n", file, len(errors)) continue } + // Check against remote validator. + if rmv := w.processor.remoteValidator; rmv != nil { + valid, err := rmv.Validate(advisory) + if err != nil { + log.Printf("Calling remote validator failed: %s\n", err) + continue + } + if !valid { + log.Printf( + "CSAF file %s does not validate remotely.\n", file) + continue + } + } + sum, err := csaf.NewAdvisorySummary(w.expr, advisory) if err != nil { log.Printf("error: %s: %v\n", file, err) diff --git a/cmd/csaf_aggregator/processor.go b/cmd/csaf_aggregator/processor.go index a7dbfc4..cd570d3 100644 --- a/cmd/csaf_aggregator/processor.go +++ b/cmd/csaf_aggregator/processor.go @@ -20,7 +20,11 @@ import ( ) type processor struct { + // cfg is the global configuration. cfg *config + + // remoteValidator is a globally configured remote validator. + remoteValidator csaf.RemoteValidator } type summary struct { @@ -30,9 +34,10 @@ type summary struct { } type worker struct { - num int + num int + processor *processor + expr *util.PathEval - cfg *config signRing *crypto.KeyRing client util.Client // client per provider @@ -43,11 +48,11 @@ type worker struct { summaries map[string][]summary // the summaries of the advisories. } -func newWorker(num int, config *config) *worker { +func newWorker(num int, processor *processor) *worker { return &worker{ - num: num, - cfg: config, - expr: util.NewPathEval(), + num: num, + processor: processor, + expr: util.NewPathEval(), } } @@ -64,7 +69,7 @@ func (w *worker) createDir() (string, error) { return w.dir, nil } dir, err := util.MakeUniqDir( - filepath.Join(w.cfg.Folder, w.provider.Name)) + filepath.Join(w.processor.cfg.Folder, w.provider.Name)) if err == nil { w.dir = dir } diff --git a/cmd/csaf_provider/actions.go b/cmd/csaf_provider/actions.go index d4bac45..1e46c2d 100644 --- a/cmd/csaf_provider/actions.go +++ b/cmd/csaf_provider/actions.go @@ -167,6 +167,21 @@ func (c *controller) upload(r *http.Request) (interface{}, error) { } } + // Validate against remote validator + if c.cfg.RemoteValidator != nil { + validator, err := c.cfg.RemoteValidator.Open() + if err != nil { + return nil, err + } + valid, err := validator.Validate(content) + if err != nil { + return nil, err + } + if !valid { + return nil, errors.New("does not validate against remote validator") + } + } + ex, err := csaf.NewAdvisorySummary(util.NewPathEval(), content) if err != nil { return nil, err diff --git a/cmd/csaf_provider/config.go b/cmd/csaf_provider/config.go index f397b41..a300b8f 100644 --- a/cmd/csaf_provider/config.go +++ b/cmd/csaf_provider/config.go @@ -40,21 +40,22 @@ type providerMetadataConfig struct { // configs contains the config values for the provider. type config struct { - Password *string `toml:"password"` - OpenPGPPublicKey string `toml:"openpgp_public_key"` - OpenPGPPrivateKey string `toml:"openpgp_private_key"` - Folder string `toml:"folder"` - Web string `toml:"web"` - TLPs []tlp `toml:"tlps"` - UploadSignature bool `toml:"upload_signature"` - CanonicalURLPrefix string `toml:"canonical_url_prefix"` - NoPassphrase bool `toml:"no_passphrase"` - NoValidation bool `toml:"no_validation"` - NoWebUI bool `toml:"no_web_ui"` - DynamicProviderMetaData bool `toml:"dynamic_provider_metadata"` - ProviderMetaData *providerMetadataConfig `toml:"provider_metadata"` - UploadLimit *int64 `toml:"upload_limit"` - Issuer *string `toml:"issuer"` + Password *string `toml:"password"` + OpenPGPPublicKey string `toml:"openpgp_public_key"` + OpenPGPPrivateKey string `toml:"openpgp_private_key"` + Folder string `toml:"folder"` + Web string `toml:"web"` + TLPs []tlp `toml:"tlps"` + UploadSignature bool `toml:"upload_signature"` + CanonicalURLPrefix string `toml:"canonical_url_prefix"` + NoPassphrase bool `toml:"no_passphrase"` + NoValidation bool `toml:"no_validation"` + NoWebUI bool `toml:"no_web_ui"` + DynamicProviderMetaData bool `toml:"dynamic_provider_metadata"` + ProviderMetaData *providerMetadataConfig `toml:"provider_metadata"` + UploadLimit *int64 `toml:"upload_limit"` + Issuer *string `toml:"issuer"` + RemoteValidator *csaf.RemoteValidatorOptions `toml:"remote_validator"` } func (pmdc *providerMetadataConfig) apply(pmd *csaf.ProviderMetadata) { diff --git a/cmd/csaf_remote_validator/main.go b/cmd/csaf_remote_validator/main.go new file mode 100644 index 0000000..4228936 --- /dev/null +++ b/cmd/csaf_remote_validator/main.go @@ -0,0 +1,79 @@ +// This file is Free Software under the MIT License +// without warranty, see README.md and LICENSES/MIT.txt for details. +// +// SPDX-License-Identifier: MIT +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/csaf-poc/csaf_distribution/csaf" +) + +func loadJSONFromFile(fname string) (interface{}, error) { + f, err := os.Open(fname) + if err != nil { + return nil, err + } + defer f.Close() + var doc interface{} + err = json.NewDecoder(f).Decode(&doc) + return doc, err +} + +func process(options *csaf.RemoteValidatorOptions, fnames []string) error { + validator, err := options.Open() + if err != nil { + return err + } + defer validator.Close() + + for _, fname := range fnames { + doc, err := loadJSONFromFile(fname) + if err != nil { + return err + } + valid, err := validator.Validate(doc) + if err != nil { + return err + } + fmt.Printf("%s: %t\n", fname, valid) + } + return nil +} + +func main() { + + var ( + url = flag.String("url", "", "URL to the validation service") + presets = flag.String("presets", "", "validation presets") + cache = flag.String("cache", "", "cache") + ) + + flag.Parse() + + var pres []string + + if *presets != "" { + pres = strings.Split(*presets, ",") + } + + options := csaf.RemoteValidatorOptions{ + URL: *url, + Presets: pres, + Cache: *cache, + } + + if err := process(&options, flag.Args()); err != nil { + log.Fatalf("error: %v\n", err) + } +} diff --git a/csaf/models.go b/csaf/models.go index f3af8d6..80b95c8 100644 --- a/csaf/models.go +++ b/csaf/models.go @@ -313,7 +313,7 @@ func (a *Aggregator) Validate() error { } } if a.LastUpdated == nil { - return errors.New("Aggregator.LastUpdate == nil") + return errors.New("aggregator.LastUpdate == nil") } return nil } diff --git a/csaf/remotevalidation.go b/csaf/remotevalidation.go new file mode 100644 index 0000000..329e48f --- /dev/null +++ b/csaf/remotevalidation.go @@ -0,0 +1,281 @@ +// This file is Free Software under the MIT License +// without warranty, see README.md and LICENSES/MIT.txt for details. +// +// SPDX-License-Identifier: MIT +// +// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) +// Software-Engineering: 2022 Intevation GmbH + +package csaf + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "net/http" + "sync" + + bolt "go.etcd.io/bbolt" +) + +// defaultURL is default URL where to look for +// the validation service. +const ( + defaultURL = "http://localhost:3000" + validationPath = "/api/v1/validate" +) + +// defaultPresets are the presets to check. +var defaultPresets = []string{"mandatory"} + +var ( + validationsBucket = []byte("validations") + validFalse = []byte{0} + validTrue = []byte{1} +) + +// RemoteValidatorOptions are the configuation options +// the remote validation service. +type RemoteValidatorOptions struct { + URL string `json:"url" toml:"url"` + Presets []string `json:"presets" toml:"presets"` + Cache string `json:"cache" toml:"cache"` +} + +type test struct { + Type string `json:"type"` + Name string `json:"name"` +} + +// outDocument is the document send to the remote validation service. +type outDocument struct { + Tests []test `json:"tests"` + Document interface{} `json:"document"` +} + +// inDocument is the document recieved from the remote validation service. +type inDocument struct { + Valid bool `json:"isValid"` +} + +var errNotFound = errors.New("not found") + +type cache interface { + get(key []byte) (bool, error) + set(key []byte, valid bool) error + Close() error +} + +// RemoteValidator validates an advisory document remotely. +type RemoteValidator interface { + Validate(doc interface{}) (bool, error) + Close() error +} + +// SynchronizedRemoteValidator returns a serialized variant +// of the given remote validator. +func SynchronizedRemoteValidator(validator RemoteValidator) RemoteValidator { + return &syncedRemoteValidator{RemoteValidator: validator} +} + +// remoteValidator is an implementation of an RemoteValidator. +type remoteValidator struct { + url string + tests []test + cache cache +} + +// syncedRemoteValidator is a serialized variant of a remote validator. +type syncedRemoteValidator struct { + sync.Mutex + RemoteValidator +} + +// Validate implements the validation part of the RemoteValidator interface. +func (srv *syncedRemoteValidator) Validate(doc interface{}) (bool, error) { + srv.Lock() + defer srv.Unlock() + return srv.RemoteValidator.Validate(doc) +} + +// Validate implements the closing part of the RemoteValidator interface. +func (srv *syncedRemoteValidator) Close() error { + srv.Lock() + defer srv.Unlock() + return srv.RemoteValidator.Close() +} + +// prepareTests precompiles the presets for the remote check. +func prepareTests(presets []string) []test { + if len(presets) == 0 { + presets = defaultPresets + } + tests := make([]test, len(presets)) + for i := range tests { + tests[i] = test{Type: "preset", Name: presets[i]} + } + return tests +} + +// prepareURL prepares the URL to be called for validation. +func prepareURL(url string) string { + if url == "" { + return defaultURL + validationPath + } + return url + validationPath +} + +// prepareCache sets up the cache if it is configured. +func prepareCache(config string) (cache, error) { + if config == "" { + return nil, nil + } + + db, err := bolt.Open(config, 0600, nil) + if err != nil { + return nil, err + } + + // Create the bucket. + if err := db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists(validationsBucket) + return err + }); err != nil { + db.Close() + return nil, err + } + + return boltCache{db}, nil +} + +// boltCache is cache implementation based on the bolt datastore. +type boltCache struct{ *bolt.DB } + +// get implements the fetch part of the cache interface. +func (bc boltCache) get(key []byte) (valid bool, err error) { + err2 := bc.View(func(tx *bolt.Tx) error { + b := tx.Bucket(validationsBucket) + v := b.Get(key) + if v == nil { + err = errNotFound + } else { + valid = v[0] != 0 + } + return nil + }) + if err2 != nil { + err = err2 + } + return +} + +// get implements the store part of the cache interface. +func (bc boltCache) set(key []byte, valid bool) error { + return bc.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(validationsBucket) + if valid { + return b.Put(key, validTrue) + } + return b.Put(key, validFalse) + }) +} + +// Open opens a new remoteValidator. +func (rvo *RemoteValidatorOptions) Open() (RemoteValidator, error) { + cache, err := prepareCache(rvo.Cache) + if err != nil { + return nil, err + } + return &remoteValidator{ + url: prepareURL(rvo.URL), + tests: prepareTests(rvo.Presets), + cache: cache, + }, nil +} + +// Close closes the remote validator. +func (v *remoteValidator) Close() error { + if v.cache != nil { + return v.cache.Close() + } + return nil +} + +// key calculates the key for an advisory document and presets. +func (v *remoteValidator) key(doc interface{}) ([]byte, error) { + h := sha256.New() + if err := json.NewEncoder(h).Encode(doc); err != nil { + return nil, err + } + for i := range v.tests { + if _, err := h.Write([]byte(v.tests[i].Name)); err != nil { + return nil, err + } + } + return h.Sum(nil), nil +} + +// Validate executes a remote validation of an advisory. +func (v *remoteValidator) Validate(doc interface{}) (bool, error) { + + var key []byte + + if v.cache != nil { + var err error + if key, err = v.key(doc); err != nil { + return false, err + } + valid, err := v.cache.get(key) + if err != errNotFound { + if err != nil { + return false, err + } + return valid, nil + } + } + + o := outDocument{ + Document: doc, + Tests: v.tests, + } + + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(&o); err != nil { + return false, err + } + + resp, err := http.Post( + v.url, + "application/json", + bytes.NewReader(buf.Bytes())) + + if err != nil { + return false, err + } + + if resp.StatusCode != http.StatusOK { + return false, fmt.Errorf( + "POST failed: %s (%d)", resp.Status, resp.StatusCode) + } + + valid, err := func() (bool, error) { + defer resp.Body.Close() + var in inDocument + return in.Valid, json.NewDecoder(resp.Body).Decode(&in) + }() + + if err != nil { + return false, err + } + + if key != nil { + // store in cache + if err := v.cache.set(key, valid); err != nil { + return valid, err + } + } + + return valid, nil +} diff --git a/docs/csaf_aggregator.md b/docs/csaf_aggregator.md index 2109f63..ad8bf07 100644 --- a/docs/csaf_aggregator.md +++ b/docs/csaf_aggregator.md @@ -87,6 +87,7 @@ lock_file // path to lockfile, to stop other instances if one is not interim_years // limiting the years for which interim documents are searched verbose // print more diagnostic output, e.g. https request allow_single_provider // debugging option +remote_validator // use remote validation checker ``` Rates are specified as floats in HTTPS operations per second. diff --git a/docs/csaf_provider.md b/docs/csaf_provider.md index 17769e6..9519c64 100644 --- a/docs/csaf_provider.md +++ b/docs/csaf_provider.md @@ -28,6 +28,8 @@ Following options are supported in the config file: - provider_metadata: Configure the provider metadata. - provider_metadata.list_on_CSAF_aggregators: List on aggregators - provider_metadata.mirror_on_CSAF_aggregators: Mirror on aggregators + - remote_validator: Use a remote validator service. Not used by default. + `{ "url" = "http://localhost:3000", "presets" = ["mandatory"], "cache" = "/var/lib/csaf/validations.db" }` - provider_metadata.publisher: Set the publisher. Default: ```toml [provider_metadata.publisher] @@ -36,4 +38,4 @@ name = "Example Company" namespace = "https://example.com" issuing_authority = "We at Example Company are responsible for publishing and maintaining Product Y." contact_details = "Example Company can be reached at contact_us@example.com, or via our website at https://www.example.com/contact." - +``` diff --git a/go.mod b/go.mod index 4d4177e..b320612 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/jessevdk/go-flags v1.5.0 github.com/mitchellh/go-homedir v1.1.0 github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 + go.etcd.io/bbolt v1.3.6 golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 golang.org/x/time v0.0.0-20220411224347-583f2d630306 diff --git a/go.sum b/go.sum index 84173ad..6cd4dc8 100644 --- a/go.sum +++ b/go.sum @@ -41,6 +41,8 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -66,6 +68,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=