mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 11:55:40 +01:00
Remote validator output (#347)
* The validator is now able to print the details of the remote validations. --------- Co-authored-by: JanHoefelmeyer <hoefelmeyer.jan@gmail.com> Co-authored-by: JanHoefelmeyer <Jan Höfelmeyer jhoefelmeyer@intevation.de> Co-authored-by: Sascha L. Teichmann <sascha.teichmann@intevation.de>
This commit is contained in:
parent
92433c1272
commit
8f87273837
7 changed files with 289 additions and 70 deletions
|
|
@ -10,10 +10,11 @@ package csaf
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
|
|
@ -32,12 +33,12 @@ var defaultPresets = []string{"mandatory"}
|
|||
|
||||
var (
|
||||
validationsBucket = []byte("validations")
|
||||
validFalse = []byte{0}
|
||||
validTrue = []byte{1}
|
||||
cacheVersionKey = []byte("version")
|
||||
cacheVersion = []byte("1")
|
||||
)
|
||||
|
||||
// RemoteValidatorOptions are the configuation options
|
||||
// the remote validation service.
|
||||
// of the remote validation service.
|
||||
type RemoteValidatorOptions struct {
|
||||
URL string `json:"url" toml:"url"`
|
||||
Presets []string `json:"presets" toml:"presets"`
|
||||
|
|
@ -55,22 +56,37 @@ type outDocument struct {
|
|||
Document any `json:"document"`
|
||||
}
|
||||
|
||||
// inDocument is the document recieved from the remote validation service.
|
||||
type inDocument struct {
|
||||
Valid bool `json:"isValid"`
|
||||
// RemoteTestResult are any given test-result by a remote validator test.
|
||||
type RemoteTestResult struct {
|
||||
Message string `json:"message"`
|
||||
InstancePath string `json:"instancePath"`
|
||||
}
|
||||
|
||||
var errNotFound = errors.New("not found")
|
||||
// RemoteTest is the result of the remote tests
|
||||
// recieved by the remote validation service.
|
||||
type RemoteTest struct {
|
||||
Name string `json:"name"`
|
||||
Valid bool `json:"isValid"`
|
||||
Error []RemoteTestResult `json:"errors"`
|
||||
Warning []RemoteTestResult `json:"warnings"`
|
||||
Info []RemoteTestResult `json:"infos"`
|
||||
}
|
||||
|
||||
// RemoteValidationResult is the document recieved from the remote validation service.
|
||||
type RemoteValidationResult struct {
|
||||
Valid bool `json:"isValid"`
|
||||
Tests []RemoteTest `json:"tests"`
|
||||
}
|
||||
|
||||
type cache interface {
|
||||
get(key []byte) (bool, error)
|
||||
set(key []byte, valid bool) error
|
||||
get(key []byte) ([]byte, error)
|
||||
set(key []byte, value []byte) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// RemoteValidator validates an advisory document remotely.
|
||||
type RemoteValidator interface {
|
||||
Validate(doc any) (bool, error)
|
||||
Validate(doc any) (*RemoteValidationResult, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +110,7 @@ type syncedRemoteValidator struct {
|
|||
}
|
||||
|
||||
// Validate implements the validation part of the RemoteValidator interface.
|
||||
func (srv *syncedRemoteValidator) Validate(doc any) (bool, error) {
|
||||
func (srv *syncedRemoteValidator) Validate(doc any) (*RemoteValidationResult, error) {
|
||||
srv.Lock()
|
||||
defer srv.Unlock()
|
||||
return srv.RemoteValidator.Validate(doc)
|
||||
|
|
@ -122,7 +138,7 @@ func prepareTests(presets []string) []test {
|
|||
// prepareURL prepares the URL to be called for validation.
|
||||
func prepareURL(url string) string {
|
||||
if url == "" {
|
||||
return defaultURL + validationPath
|
||||
url = defaultURL
|
||||
}
|
||||
return url + validationPath
|
||||
}
|
||||
|
|
@ -140,8 +156,34 @@ func prepareCache(config string) (cache, error) {
|
|||
|
||||
// Create the bucket.
|
||||
if err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists(validationsBucket)
|
||||
return err
|
||||
|
||||
// Create a new bucket with version set.
|
||||
create := func() error {
|
||||
b, err := tx.CreateBucket(validationsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := b.Put(cacheVersionKey, cacheVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
b := tx.Bucket(validationsBucket)
|
||||
|
||||
if b == nil { // Bucket does not exists -> create.
|
||||
return create()
|
||||
}
|
||||
// Bucket exists.
|
||||
if v := b.Get(cacheVersionKey); !bytes.Equal(v, cacheVersion) {
|
||||
// version mismatch -> delete and re-create.
|
||||
if err := tx.DeleteBucket(validationsBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
return create()
|
||||
}
|
||||
return nil
|
||||
|
||||
}); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
|
|
@ -154,31 +196,23 @@ func prepareCache(config string) (cache, error) {
|
|||
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 {
|
||||
func (bc boltCache) get(key []byte) ([]byte, error) {
|
||||
var value []byte
|
||||
if err := 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
|
||||
}
|
||||
value = b.Get(key)
|
||||
return nil
|
||||
})
|
||||
if err2 != nil {
|
||||
err = err2
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// get implements the store part of the cache interface.
|
||||
func (bc boltCache) set(key []byte, valid bool) error {
|
||||
// set implements the store part of the cache interface.
|
||||
func (bc boltCache) set(key, value []byte) 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)
|
||||
return b.Put(key, value)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -217,22 +251,37 @@ func (v *remoteValidator) key(doc any) ([]byte, error) {
|
|||
return h.Sum(nil), nil
|
||||
}
|
||||
|
||||
// deserialize revives a remote validation result from a cache value.
|
||||
func deserialize(value []byte) (*RemoteValidationResult, error) {
|
||||
r, err := zlib.NewReader(bytes.NewReader(value))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
var rvr RemoteValidationResult
|
||||
if err := json.NewDecoder(r).Decode(&rvr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &rvr, nil
|
||||
}
|
||||
|
||||
// Validate executes a remote validation of an advisory.
|
||||
func (v *remoteValidator) Validate(doc any) (bool, error) {
|
||||
func (v *remoteValidator) Validate(doc any) (*RemoteValidationResult, error) {
|
||||
|
||||
var key []byte
|
||||
|
||||
// First look into cache.
|
||||
if v.cache != nil {
|
||||
var err error
|
||||
if key, err = v.key(doc); err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
valid, err := v.cache.get(key)
|
||||
if err != errNotFound {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return valid, nil
|
||||
value, err := v.cache.get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value != nil {
|
||||
return deserialize(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,7 +292,7 @@ func (v *remoteValidator) Validate(doc any) (bool, error) {
|
|||
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(&o); err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := http.Post(
|
||||
|
|
@ -252,30 +301,46 @@ func (v *remoteValidator) Validate(doc any) (bool, error) {
|
|||
bytes.NewReader(buf.Bytes()))
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, fmt.Errorf(
|
||||
return nil, 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)
|
||||
}()
|
||||
var (
|
||||
zout *zlib.Writer
|
||||
rvr RemoteValidationResult
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
if err := func() error {
|
||||
defer resp.Body.Close()
|
||||
var in io.Reader
|
||||
// If we are caching record the incoming data and compress it.
|
||||
if key != nil {
|
||||
buf.Reset() // reuse the out buffer.
|
||||
zout = zlib.NewWriter(&buf)
|
||||
in = io.TeeReader(resp.Body, zout)
|
||||
} else {
|
||||
// no cache -> process directly.
|
||||
in = resp.Body
|
||||
}
|
||||
return json.NewDecoder(in).Decode(&rvr)
|
||||
}(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store in cache
|
||||
if key != nil {
|
||||
// store in cache
|
||||
if err := v.cache.set(key, valid); err != nil {
|
||||
return valid, err
|
||||
if err := zout.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The document is now compressed in the buffer.
|
||||
if err := v.cache.set(key, buf.Bytes()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return valid, nil
|
||||
return &rvr, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue