From 7501c60bf42db3a1d5c543569b1f576883aaf4cc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 13 Jun 2023 13:28:01 +0200 Subject: [PATCH] Implement rule depending error check. --- cmd/csaf_checker/processor.go | 18 +++++- cmd/csaf_checker/report.go | 8 +-- cmd/csaf_checker/rules.go | 105 +++++++++++++++++++++++++++++----- 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 6d1374d..b97fcdb 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -149,6 +149,19 @@ func (m *topicMessages) reset() { *m = nil } // used returns true if we have used this topic. func (m *topicMessages) used() bool { return *m != nil } +// hasErrors checks if there are any error messages. +func (m *topicMessages) hasErrors() bool { + if !m.used() { + return false + } + for _, msg := range *m { + if msg.Type == ErrorType { + return true + } + } + return false +} + // newProcessor returns a processor structure after assigning the given options to the opts attribute // and initializing the "alreadyChecked" and "expr" fields. func newProcessor(opts *options) (*processor, error) { @@ -263,10 +276,13 @@ func (p *processor) run(domains []string) (*Report, error) { rules = trustedProviderRules } - for _, r := range rules.reporters() { + // 18, 19, 20 should always be checked. + for _, r := range rules.reporters([]int{18, 19, 20}) { r.report(p, domain) } + domain.Passed = rules.eval(p) + report.Domains = append(report.Domains, domain) p.clean() } diff --git a/cmd/csaf_checker/report.go b/cmd/csaf_checker/report.go index 269da00..07c8d9c 100644 --- a/cmd/csaf_checker/report.go +++ b/cmd/csaf_checker/report.go @@ -46,6 +46,7 @@ type Domain struct { Publisher *csaf.Publisher `json:"publisher,omitempty"` Role *csaf.MetadataRole `json:"role,omitempty"` Requirements []*Requirement `json:"requirements,omitempty"` + Passed bool `json:"passed"` } // ReportTime stores the time of the report. @@ -80,12 +81,7 @@ func (r *Requirement) Append(msgs []Message) { // HasErrors tells if this domain has errors. func (d *Domain) HasErrors() bool { - for _, r := range d.Requirements { - if r.HasErrors() { - return true - } - } - return false + return !d.Passed } // String implements fmt.Stringer interface. diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 91a596c..024b1c2 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -9,6 +9,7 @@ package main import ( + "fmt" "sort" "github.com/csaf-poc/csaf_distribution/v2/csaf" @@ -54,6 +55,22 @@ var ( } ) +// roleRequirements returns the rules for the given role. +func roleRequirements(role csaf.MetadataRole) *requirementRules { + switch role { + case csaf.MetadataRoleTrustedProvider: + return trustedProviderRules + case csaf.MetadataRoleProvider: + return providerRules + case csaf.MetadataRolePublisher: + return publisherRules + default: + return nil + } +} + +// ruleAtoms is a helper function to build the leaves of +// a rules tree. func ruleAtoms(nums ...int) []*requirementRules { rules := make([]*requirementRules, len(nums)) for i, num := range nums { @@ -65,16 +82,17 @@ func ruleAtoms(nums ...int) []*requirementRules { return rules } -func (rules *requirementRules) reporters() []reporter { +// reporters assembles a list of reporters needed for a given set +// of rules. The given nums are mandatory. +func (rules *requirementRules) reporters(nums []int) []reporter { if rules == nil { return nil } - var nums []int var recurse func(*requirementRules) recurse = func(rules *requirementRules) { if rules.satisfies != 0 { - // There should not be any dupes + // There should not be any dupes. for _, n := range nums { if n == rules.satisfies { goto doRecurse @@ -99,16 +117,77 @@ func (rules *requirementRules) reporters() []reporter { return reps } -// roleRequirements returns the rules for the given role. -func roleRequirements(role csaf.MetadataRole) *requirementRules { - switch role { - case csaf.MetadataRoleTrustedProvider: - return trustedProviderRules - case csaf.MetadataRoleProvider: - return providerRules - case csaf.MetadataRolePublisher: - return publisherRules +// eval evalutes a set of rules given a given processor state. +func (rules *requirementRules) eval(p *processor) bool { + if rules == nil { + return false + } + + var recurse func(*requirementRules) bool + + recurse = func(rules *requirementRules) bool { + if rules.satisfies != 0 { + return p.eval(rules.satisfies) + } + switch rules.cond { + case condAll: + for _, sub := range rules.subs { + if !recurse(sub) { + return false + } + } + return true + case condOneOf: + for _, sub := range rules.subs { + if recurse(sub) { + return true + } + } + return false + default: + panic(fmt.Sprintf("unexpected cond %v in eval", rules.cond)) + } + } + + return recurse(rules) +} + +func (p *processor) eval(requirement int) bool { + + switch requirement { + case 1: + return !p.invalidAdvisories.hasErrors() + case 2: + return !p.badFilenames.hasErrors() + case 3: + return len(p.noneTLS) == 0 + + case 8: + return !p.badSecurity.hasErrors() + case 9: + return !p.badWellknownMetadata.hasErrors() + case 10: + return !p.badDNSPath.hasErrors() + + case 11: + return !p.badFolders.hasErrors() + case 12: + return !p.badIndices.hasErrors() + case 13: + return !p.badChanges.hasErrors() + case 14: + return !p.badDirListings.hasErrors() + + case 15: + return !p.badROLIEfeed.hasErrors() + case 16: + // TODO: Implement me! + return true + case 17: + // TODO: Implement me! + return true + default: - return nil + panic(fmt.Sprintf("testing unexpected requirement %d", requirement)) } }