From c7453a644825ddbb1a5c81a8934a82f2af521630 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 13 Jun 2023 12:25:19 +0200 Subject: [PATCH 01/40] Be more precise with conditional rules. --- cmd/csaf_checker/processor.go | 11 +++- cmd/csaf_checker/reporters.go | 42 ------------- cmd/csaf_checker/rules.go | 114 ++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 43 deletions(-) create mode 100644 cmd/csaf_checker/rules.go diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 38644c9..6d1374d 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -254,7 +254,16 @@ func (p *processor) run(domains []string) (*Report, error) { continue } - for _, r := range buildReporters(*domain.Role) { + rules := roleRequirements(*domain.Role) + // TODO: store error base on rules eval in report. + if rules == nil { + log.Printf( + "WARN: Cannot find requirement rules for role %q. Assuming trusted provider.\n", + *domain.Role) + rules = trustedProviderRules + } + + for _, r := range rules.reporters() { r.report(p, domain) } diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index 493732e..41b8b96 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -12,8 +12,6 @@ import ( "fmt" "sort" "strings" - - "github.com/csaf-poc/csaf_distribution/v2/csaf" ) type ( @@ -72,46 +70,6 @@ var reporters = [23]reporter{ &mirrorReporter{baseReporter{num: 23, description: "Mirror"}}, } -var roleImplies = map[csaf.MetadataRole][]csaf.MetadataRole{ - csaf.MetadataRoleProvider: {csaf.MetadataRolePublisher}, - csaf.MetadataRoleTrustedProvider: {csaf.MetadataRoleProvider}, -} - -func requirements(role csaf.MetadataRole) [][2]int { - var own [][2]int - switch role { - case csaf.MetadataRoleTrustedProvider: - own = [][2]int{{18, 20}} - case csaf.MetadataRoleProvider: - // TODO: use commented numbers when TLPs should be checked. - own = [][2]int{{6 /* 5 */, 7}, {8, 10}, {11, 14}, {15, 17}} - case csaf.MetadataRolePublisher: - own = [][2]int{{1, 3 /* 4 */}} - } - for _, base := range roleImplies[role] { - own = append(own, requirements(base)...) - } - return own -} - -// buildReporters initializes each report by assigning a number and description to it. -// It returns an array of the reporter interface type. -func buildReporters(role csaf.MetadataRole) []reporter { - var reps []reporter - reqs := requirements(role) - // sort to have them ordered by there number. - sort.Slice(reqs, func(i, j int) bool { return reqs[i][0] < reqs[j][0] }) - for _, req := range reqs { - from, to := req[0]-1, req[1]-1 - for i := from; i <= to; i++ { - if rep := reporters[i]; rep != nil { - reps = append(reps, rep) - } - } - } - return reps -} - func (bc *baseReporter) requirement(domain *Domain) *Requirement { req := &Requirement{ Num: bc.num, diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go new file mode 100644 index 0000000..91a596c --- /dev/null +++ b/cmd/csaf_checker/rules.go @@ -0,0 +1,114 @@ +// 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: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +package main + +import ( + "sort" + + "github.com/csaf-poc/csaf_distribution/v2/csaf" +) + +type ruleCondition int + +const ( + condAll ruleCondition = iota + condOneOf +) + +type requirementRules struct { + cond ruleCondition + satisfies int + subs []*requirementRules +} + +var ( + publisherRules = &requirementRules{ + cond: condAll, + subs: ruleAtoms(1, 2, 3 /* 4 */), + } + + providerRules = &requirementRules{ + cond: condAll, + subs: []*requirementRules{ + publisherRules, + {cond: condOneOf, subs: ruleAtoms(8, 9, 10)}, + {cond: condOneOf, subs: []*requirementRules{ + {cond: condAll, subs: ruleAtoms(11, 12, 13, 14)}, + {cond: condAll, subs: ruleAtoms(15, 16, 17)}, + }}, + }, + } + + trustedProviderRules = &requirementRules{ + cond: condAll, + subs: []*requirementRules{ + providerRules, + {cond: condAll, subs: ruleAtoms(18, 19, 20)}, + }, + } +) + +func ruleAtoms(nums ...int) []*requirementRules { + rules := make([]*requirementRules, len(nums)) + for i, num := range nums { + rules[i] = &requirementRules{ + cond: condAll, + satisfies: num, + } + } + return rules +} + +func (rules *requirementRules) reporters() []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 + for _, n := range nums { + if n == rules.satisfies { + goto doRecurse + } + } + nums = append(nums, rules.satisfies) + } + doRecurse: + for _, sub := range rules.subs { + recurse(sub) + } + } + recurse(rules) + + sort.Ints(nums) + + reps := make([]reporter, len(nums)) + + for i, n := range nums { + reps[i] = reporters[n] + } + 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 + default: + return nil + } +} From 7501c60bf42db3a1d5c543569b1f576883aaf4cc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 13 Jun 2023 13:28:01 +0200 Subject: [PATCH 02/40] 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)) } } From 51035c0dc9b586bebd28f6353100ce2897f3f042 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 13 Jun 2023 13:34:35 +0200 Subject: [PATCH 03/40] Add comment --- cmd/csaf_checker/rules.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 024b1c2..206d634 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -152,6 +152,7 @@ func (rules *requirementRules) eval(p *processor) bool { return recurse(rules) } +// eval evalutes the processing state for a given requirement. func (p *processor) eval(requirement int) bool { switch requirement { @@ -188,6 +189,6 @@ func (p *processor) eval(requirement int) bool { return true default: - panic(fmt.Sprintf("testing unexpected requirement %d", requirement)) + panic(fmt.Sprintf("evaluating unexpected requirement %d", requirement)) } } From 8d45525e7f13ef3cd6fd95f2fa0ca8020bc145a4 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Tue, 13 Jun 2023 15:51:16 +0200 Subject: [PATCH 04/40] Made reporters 1-based to easy lookup. --- cmd/csaf_checker/reporters.go | 48 +++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index 41b8b96..de0f992 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -44,30 +44,30 @@ type ( mirrorReporter struct{ baseReporter } ) -var reporters = [23]reporter{ - &validReporter{baseReporter{num: 1, description: "Valid CSAF documents"}}, - &filenameReporter{baseReporter{num: 2, description: "Filename"}}, - &tlsReporter{baseReporter{num: 3, description: "TLS"}}, - &tlpWhiteReporter{baseReporter{num: 4, description: "TLP:WHITE"}}, - &tlpAmberRedReporter{baseReporter{num: 5, description: "TLP:AMBER and TLP:RED"}}, - &redirectsReporter{baseReporter{num: 6, description: "Redirects"}}, - &providerMetadataReport{baseReporter{num: 7, description: "provider-metadata.json"}}, - &securityReporter{baseReporter{num: 8, description: "security.txt"}}, - &wellknownMetadataReporter{baseReporter{num: 9, description: "/.well-known/csaf/provider-metadata.json"}}, - &dnsPathReporter{baseReporter{num: 10, description: "DNS path"}}, - &oneFolderPerYearReport{baseReporter{num: 11, description: "One folder per year"}}, - &indexReporter{baseReporter{num: 12, description: "index.txt"}}, - &changesReporter{baseReporter{num: 13, description: "changes.csv"}}, - &directoryListingsReporter{baseReporter{num: 14, description: "Directory listings"}}, - &rolieFeedReporter{baseReporter{num: 15, description: "ROLIE feed"}}, - &rolieServiceReporter{baseReporter{num: 16, description: "ROLIE service document"}}, - &rolieCategoryReporter{baseReporter{num: 17, description: "ROLIE category document"}}, - &integrityReporter{baseReporter{num: 18, description: "Integrity"}}, - &signaturesReporter{baseReporter{num: 19, description: "Signatures"}}, - &publicPGPKeyReporter{baseReporter{num: 20, description: "Public OpenPGP Key"}}, - &listReporter{baseReporter{num: 21, description: "List of CSAF providers"}}, - &hasTwoReporter{baseReporter{num: 22, description: "Two disjoint issuing parties"}}, - &mirrorReporter{baseReporter{num: 23, description: "Mirror"}}, +var reporters = [...]reporter{ + 1: &validReporter{baseReporter{num: 1, description: "Valid CSAF documents"}}, + 2: &filenameReporter{baseReporter{num: 2, description: "Filename"}}, + 3: &tlsReporter{baseReporter{num: 3, description: "TLS"}}, + 4: &tlpWhiteReporter{baseReporter{num: 4, description: "TLP:WHITE"}}, + 5: &tlpAmberRedReporter{baseReporter{num: 5, description: "TLP:AMBER and TLP:RED"}}, + 6: &redirectsReporter{baseReporter{num: 6, description: "Redirects"}}, + 7: &providerMetadataReport{baseReporter{num: 7, description: "provider-metadata.json"}}, + 8: &securityReporter{baseReporter{num: 8, description: "security.txt"}}, + 9: &wellknownMetadataReporter{baseReporter{num: 9, description: "/.well-known/csaf/provider-metadata.json"}}, + 10: &dnsPathReporter{baseReporter{num: 10, description: "DNS path"}}, + 11: &oneFolderPerYearReport{baseReporter{num: 11, description: "One folder per year"}}, + 12: &indexReporter{baseReporter{num: 12, description: "index.txt"}}, + 13: &changesReporter{baseReporter{num: 13, description: "changes.csv"}}, + 14: &directoryListingsReporter{baseReporter{num: 14, description: "Directory listings"}}, + 15: &rolieFeedReporter{baseReporter{num: 15, description: "ROLIE feed"}}, + 16: &rolieServiceReporter{baseReporter{num: 16, description: "ROLIE service document"}}, + 17: &rolieCategoryReporter{baseReporter{num: 17, description: "ROLIE category document"}}, + 18: &integrityReporter{baseReporter{num: 18, description: "Integrity"}}, + 19: &signaturesReporter{baseReporter{num: 19, description: "Signatures"}}, + 20: &publicPGPKeyReporter{baseReporter{num: 20, description: "Public OpenPGP Key"}}, + 21: &listReporter{baseReporter{num: 21, description: "List of CSAF providers"}}, + 22: &hasTwoReporter{baseReporter{num: 22, description: "Two disjoint issuing parties"}}, + 23: &mirrorReporter{baseReporter{num: 23, description: "Mirror"}}, } func (bc *baseReporter) requirement(domain *Domain) *Requirement { From 7139f4dfa9e993552b846187a85816c87dc84e3b Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 09:04:29 +0200 Subject: [PATCH 05/40] correct typos --- cmd/csaf_checker/roliecheck.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 9dcdf11..d6807b9 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -93,8 +93,8 @@ func (ca *rolieLabelChecker) check( } } -// processROLIEFeeds goes through all ROLIE feeds and checks there -// integriry and completeness. +// processROLIEFeeds goes through all ROLIE feeds and checks their +// integrity and completeness. func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { base, err := url.Parse(p.pmdURL) From f4f3efb197f93f1fd1de9a4803dfd28ff8462e71 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 09:46:42 +0200 Subject: [PATCH 06/40] Add function to load ROLIE service document --- cmd/csaf_checker/roliecheck.go | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index d6807b9..7fe9de9 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -9,8 +9,13 @@ package main import ( + "bytes" + "io" + "net/http" "net/url" + "encoding/json" + "github.com/csaf-poc/csaf_distribution/v2/csaf" "github.com/csaf-poc/csaf_distribution/v2/util" ) @@ -263,3 +268,44 @@ func containsAllKeys[K comparable, V any](m1, m2 map[K]V) bool { } return true } + +func (p *processor) serviceCheck(url string, feeds [][]csaf.Feed) error { + // load service document + p.badROLIEservice.use() + if url == "" { + p.badROLIEservice.warn("No ROLIE service document found.") + return nil + } + + client := p.httpClient() + res, err := client.Get(url) + if err != nil { + p.badROLIEservice.error("Cannot fetch rolie service document %s: %v", url, err) + return errContinue + } + if res.StatusCode != http.StatusOK { + p.badROLIEservice.warn("Fetching %s failed. Status code %d (%s)", + url, res.StatusCode, res.Status) + return errContinue + } + + _, err = func() (any, error) { + defer res.Body.Close() + all, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + var rolieService any + err = json.NewDecoder(bytes.NewReader(all)).Decode(&rolieService) + return rolieService, err + + }() + if err != nil { + p.badROLIEservice.error("Loading ROLIE service document failed: %v.", err) + return errContinue + } + + return nil + //Todo: Check conformity with RFC8322 + +} From 87dbb5674b7075e99ce822b7cd510c2af96e1a76 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 09:50:12 +0200 Subject: [PATCH 07/40] Add badROLIEservice to processor --- cmd/csaf_checker/processor.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index b97fcdb..852688c 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -68,6 +68,7 @@ type processor struct { badDNSPath topicMessages badDirListings topicMessages badROLIEfeed topicMessages + badROLIEservice topicMessages expr *util.PathEval } @@ -234,6 +235,7 @@ func (p *processor) clean() { p.badDNSPath.reset() p.badDirListings.reset() p.badROLIEfeed.reset() + p.badROLIEservice.reset() p.labelChecker = nil } From 719ecaea762eab161af57593b53d63bf6d586aa9 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 10:16:31 +0200 Subject: [PATCH 08/40] Add Requirements 18-20 to rules.go --- cmd/csaf_checker/rules.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 206d634..e2fcbe9 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -187,7 +187,12 @@ func (p *processor) eval(requirement int) bool { case 17: // TODO: Implement me! return true - + case 18: + return !p.badIntegrities.hasErrors() + case 19: + return !p.badSignatures.hasErrors() + case 20: + return !p.badPGPs.hasErrors() default: panic(fmt.Sprintf("evaluating unexpected requirement %d", requirement)) } From fd374b30b6bd0c660c2b837316d11c7e92bd869e Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Wed, 14 Jun 2023 12:56:55 +0200 Subject: [PATCH 09/40] Load ROLIE service by library function. --- cmd/csaf_checker/roliecheck.go | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 7fe9de9..c3b2be6 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -9,13 +9,9 @@ package main import ( - "bytes" - "io" "net/http" "net/url" - "encoding/json" - "github.com/csaf-poc/csaf_distribution/v2/csaf" "github.com/csaf-poc/csaf_distribution/v2/util" ) @@ -289,23 +285,21 @@ func (p *processor) serviceCheck(url string, feeds [][]csaf.Feed) error { return errContinue } - _, err = func() (any, error) { + rolieService, err := func() (any, error) { defer res.Body.Close() - all, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - var rolieService any - err = json.NewDecoder(bytes.NewReader(all)).Decode(&rolieService) - return rolieService, err - + return csaf.LoadROLIEServiceDocument(res.Body) }() + if err != nil { p.badROLIEservice.error("Loading ROLIE service document failed: %v.", err) return errContinue } + // TODO: Use me! + _ = rolieService + _ = feeds + return nil - //Todo: Check conformity with RFC8322 + // TODO: Check conformity with RFC8322 } From d7fb52b735cbcacf1690bda0041bab27646e17a1 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 14:37:09 +0200 Subject: [PATCH 10/40] check whether rolie service document contains all feeds --- cmd/csaf_checker/roliecheck.go | 54 +++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index c3b2be6..b36942d 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -285,7 +285,7 @@ func (p *processor) serviceCheck(url string, feeds [][]csaf.Feed) error { return errContinue } - rolieService, err := func() (any, error) { + rolieService, err := func() (*csaf.ROLIEServiceDocument, error) { defer res.Body.Close() return csaf.LoadROLIEServiceDocument(res.Body) }() @@ -295,11 +295,57 @@ func (p *processor) serviceCheck(url string, feeds [][]csaf.Feed) error { return errContinue } - // TODO: Use me! - _ = rolieService - _ = feeds + // Build lists of all feeds in feeds and in the Service Document + sfeeds := []string{} + ffeeds := []string{} + for _, col := range rolieService.Service.Workspace { + for _, fd := range col.Collection { + sfeeds = append(sfeeds, fd.HRef) + } + } + for _, r := range feeds { + for _, s := range r { + ffeeds = append(ffeeds, string(*s.URL)) + } + } + // Check if ROLIE Service Document contains exactly all ROLIE feeds + m1, m2 := sameContentsSS(sfeeds, ffeeds) + if len(m1) != 0 { + p.badROLIEservice.error("The ROLIE service document contains nonexistent feed entries: %v", m1) + } + if len(m2) != 0 { + p.badROLIEservice.error("The ROLIE service document is missing feed entries: %v", m2) + } return nil // TODO: Check conformity with RFC8322 } + +// sameContents checks if two slices slice1 and slice2 of strings contains the same strings +// returns two slices of all strings missing in the respective other slice +func sameContentsSS(slice1 []string, slice2 []string) ([]string, []string) { + m1 := []string{} + m2 := []string{} + for _, e1 := range slice1 { + if !containsAllSS(slice2, e1) { + m1 = append(m1, e1) + } + } + for _, e2 := range slice2 { + if !containsAllSS(slice1, e2) { + m2 = append(m2, e2) + } + } + return m1, m2 +} + +// containsAllSS checks if a slice of strings contains a string +func containsAllSS(slice []string, str string) bool { + for _, e := range slice { + if e == str { + return true + } + } + return false +} From 380ccfdf5aa162da8e5da253a5126e75e9219591 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 15:18:48 +0200 Subject: [PATCH 11/40] Add fetch of service category document from pmd url --- cmd/csaf_checker/processor.go | 2 ++ cmd/csaf_checker/roliecheck.go | 23 +++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 852688c..1728e9c 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1006,6 +1006,8 @@ func (p *processor) checkCSAFs(_ string) error { return err } } + // check for service category document + p.serviceCheck(feeds) } // No rolie feeds -> try directory_urls. diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index b36942d..41e257b 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -265,23 +265,30 @@ func containsAllKeys[K comparable, V any](m1, m2 map[K]V) bool { return true } -func (p *processor) serviceCheck(url string, feeds [][]csaf.Feed) error { +func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { + // service category document should be next to the pmd + pmdURL, err := url.Parse(p.pmdURL) + if err != nil { + return err + } + baseURL, err := util.BaseURL(pmdURL) + if err != nil { + return err + } + urls := baseURL + "service.json" + // load service document p.badROLIEservice.use() - if url == "" { - p.badROLIEservice.warn("No ROLIE service document found.") - return nil - } client := p.httpClient() - res, err := client.Get(url) + res, err := client.Get(urls) if err != nil { - p.badROLIEservice.error("Cannot fetch rolie service document %s: %v", url, err) + p.badROLIEservice.error("Cannot fetch rolie service document %s: %v", urls, err) return errContinue } if res.StatusCode != http.StatusOK { p.badROLIEservice.warn("Fetching %s failed. Status code %d (%s)", - url, res.StatusCode, res.Status) + urls, res.StatusCode, res.Status) return errContinue } From 051de5194d79955e6ce409ddd3818f7b680bfc0c Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 14 Jun 2023 15:27:59 +0200 Subject: [PATCH 12/40] implement rolieServiceReporter --- cmd/csaf_checker/reporters.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index de0f992..85b1acd 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -339,8 +339,18 @@ func (r *rolieFeedReporter) report(p *processor, domain *Domain) { // whether it is a [RFC8322] conform JSON file that lists the // ROLIE feed documents and sets the "message" field value // of the "Requirement" struct as a result of that. -func (r *rolieServiceReporter) report(_ *processor, _ *Domain) { - // TODO +func (r *rolieServiceReporter) report(p *processor, domain *Domain) { + req := r.requirement(domain) + if !p.badROLIEservice.used() { + req.message(InfoType, "ROLIE service document was not checked.") + return + } + if len(p.badROLIEservice) == 0 { + req.message(InfoType, "ROLIE service document validated fine.") + return + } + req.Messages = p.badROLIEservice + } // report tests whether a ROLIE category document is used and if so, From d91af558cebef275a2e3ca2bb5c240faf987e111 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 15 Jun 2023 11:05:37 +0200 Subject: [PATCH 13/40] Add ROLIE category document check for existence --- cmd/csaf_checker/processor.go | 2 ++ cmd/csaf_checker/reporters.go | 14 ++++++++++++-- cmd/csaf_checker/roliecheck.go | 22 ++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 1728e9c..edf92b7 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -69,6 +69,7 @@ type processor struct { badDirListings topicMessages badROLIEfeed topicMessages badROLIEservice topicMessages + badROLIEcategory topicMessages expr *util.PathEval } @@ -236,6 +237,7 @@ func (p *processor) clean() { p.badDirListings.reset() p.badROLIEfeed.reset() p.badROLIEservice.reset() + p.badROLIEcategory.reset() p.labelChecker = nil } diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index 85b1acd..c9d9ed9 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -358,8 +358,18 @@ func (r *rolieServiceReporter) report(p *processor, domain *Domain) { // documents by certain criteria // and sets the "message" field value // of the "Requirement" struct as a result of that. -func (r *rolieCategoryReporter) report(_ *processor, _ *Domain) { - // TODO +func (r *rolieCategoryReporter) report(p *processor, domain *Domain) { + req := r.requirement(domain) + if !p.badROLIEcategory.used() { + req.message(InfoType, "No checks on the existence of ROLIE category documents performed.") + return + } + if len(p.badROLIEcategory) == 0 { + req.message(InfoType, "All checked ROLIE category documents exist.") + return + } + req.Messages = p.badROLIEcategory + } func (r *integrityReporter) report(p *processor, domain *Domain) { diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 41e257b..30ac49d 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -11,6 +11,7 @@ package main import ( "net/http" "net/url" + "strings" "github.com/csaf-poc/csaf_distribution/v2/csaf" "github.com/csaf-poc/csaf_distribution/v2/util" @@ -159,6 +160,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } label := tlpLabel(feed.TLPLabel) + p.categoryCheck(feedBase, label) p.labelChecker = &rolieLabelChecker{ feedURL: feedURL.String(), @@ -265,6 +267,26 @@ func containsAllKeys[K comparable, V any](m1, m2 map[K]V) bool { return true } +func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { + labelname := strings.ToLower(string(label)) + urlrc := folderURL + "category-" + labelname + ".json" + + p.badROLIEcategory.use() + client := p.httpClient() + res, err := client.Get(urlrc) + if err != nil { + p.badROLIEcategory.error("Cannot fetch rolie category document %s: %v", urlrc, err) + return errContinue + } + if res.StatusCode != http.StatusOK { + p.badROLIEcategory.warn("Fetching %s failed. Status code %d (%s)", + urlrc, res.StatusCode, res.Status) + return errContinue + } + + return nil +} + func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { // service category document should be next to the pmd pmdURL, err := url.Parse(p.pmdURL) From f74c5123c2cb6a795b7a7399a3384cf51e94ac00 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 15 Jun 2023 11:47:26 +0200 Subject: [PATCH 14/40] Add comments to categoryCheck and serviceCheck, add evaluation of category document contents --- cmd/csaf_checker/roliecheck.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 30ac49d..224efe8 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -267,6 +267,8 @@ func containsAllKeys[K comparable, V any](m1, m2 map[K]V) bool { return true } +// categoryCheck checks for the existence of a feeds ROLIE category document and if it does, +// whether the category document contains distinguishing categories func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { labelname := strings.ToLower(string(label)) urlrc := folderURL + "category-" + labelname + ".json" @@ -283,10 +285,23 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { urlrc, res.StatusCode, res.Status) return errContinue } + rolieCategory, err := func() (*csaf.ROLIECategoryDocument, error) { + defer res.Body.Close() + return csaf.LoadROLIECategoryDocument(res.Body) + }() + if err != nil { + p.badROLIEcategory.error("Loading ROLIE category document failed: %v.", err) + return errContinue + } + if len(rolieCategory.Categories.Category) == 0 { + p.badROLIEcategory.warn("No distinguishing categories in ROLIE category document: %s", urlrc) + } return nil } +// serviceCheck checks if a ROLIE service document exists and if it does, +// whether it contains all ROLIE feeds. func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { // service category document should be next to the pmd pmdURL, err := url.Parse(p.pmdURL) From 172c1cd85c6ddb97a4b8265651ef016abb805adc Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 13:50:11 +0200 Subject: [PATCH 15/40] Factored out set checks --- cmd/csaf_checker/roliecheck.go | 48 ++++++++-------------------------- util/set.go | 43 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 37 deletions(-) create mode 100644 util/set.go diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 224efe8..2ee8f2e 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -11,6 +11,7 @@ package main import ( "net/http" "net/url" + "sort" "strings" "github.com/csaf-poc/csaf_distribution/v2/csaf" @@ -340,56 +341,29 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { } // Build lists of all feeds in feeds and in the Service Document - sfeeds := []string{} - ffeeds := []string{} + sfeeds := util.Set[string]{} + ffeeds := util.Set[string]{} for _, col := range rolieService.Service.Workspace { for _, fd := range col.Collection { - sfeeds = append(sfeeds, fd.HRef) + sfeeds.Add(fd.HRef) } } for _, r := range feeds { for _, s := range r { - ffeeds = append(ffeeds, string(*s.URL)) + ffeeds.Add(string(*s.URL)) } } + // Check if ROLIE Service Document contains exactly all ROLIE feeds - m1, m2 := sameContentsSS(sfeeds, ffeeds) - if len(m1) != 0 { + if m1 := sfeeds.Difference(ffeeds).Keys(); len(m1) != 0 { + sort.Strings(m1) p.badROLIEservice.error("The ROLIE service document contains nonexistent feed entries: %v", m1) } - if len(m2) != 0 { + if m2 := ffeeds.Difference(sfeeds).Keys(); len(m2) != 0 { + sort.Strings(m2) p.badROLIEservice.error("The ROLIE service document is missing feed entries: %v", m2) } - return nil // TODO: Check conformity with RFC8322 - -} - -// sameContents checks if two slices slice1 and slice2 of strings contains the same strings -// returns two slices of all strings missing in the respective other slice -func sameContentsSS(slice1 []string, slice2 []string) ([]string, []string) { - m1 := []string{} - m2 := []string{} - for _, e1 := range slice1 { - if !containsAllSS(slice2, e1) { - m1 = append(m1, e1) - } - } - for _, e2 := range slice2 { - if !containsAllSS(slice1, e2) { - m2 = append(m2, e2) - } - } - return m1, m2 -} - -// containsAllSS checks if a slice of strings contains a string -func containsAllSS(slice []string, str string) bool { - for _, e := range slice { - if e == str { - return true - } - } - return false + return nil } diff --git a/util/set.go b/util/set.go new file mode 100644 index 0000000..0160bdc --- /dev/null +++ b/util/set.go @@ -0,0 +1,43 @@ +// 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: 2023 German Federal Office for Information Security (BSI) +// Software-Engineering: 2023 Intevation GmbH + +package util + +// Set is a simple set type. +type Set[K comparable] map[K]struct{} + +// Contains returns if the set contains a given key or not. +func (s Set[K]) Contains(k K) bool { + _, found := s[k] + return found +} + +// Add adds a key to the set. +func (s Set[K]) Add(k K) { + s[k] = struct{}{} +} + +// Keys returns the keys of the set. +func (s Set[K]) Keys() []K { + keys := make([]K, 0, len(s)) + for k := range s { + keys = append(keys, k) + } + return keys +} + +// Difference returns the differnce of two sets. +func (s Set[K]) Difference(t Set[K]) Set[K] { + d := Set[K]{} + for k := range s { + if !t.Contains(k) { + d.Add(k) + } + } + return d +} From 3e5137dd2f04f59254f4d0a1b8c5724d3f6430fd Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 13:56:10 +0200 Subject: [PATCH 16/40] Add missing error check. --- cmd/csaf_checker/roliecheck.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 2ee8f2e..e69af48 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -161,7 +161,11 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } label := tlpLabel(feed.TLPLabel) - p.categoryCheck(feedBase, label) + if err := p.categoryCheck(feedBase, label); err != nil { + if err != errContinue { + return err + } + } p.labelChecker = &rolieLabelChecker{ feedURL: feedURL.String(), From b5d1924d3f30fed3ea047c653abe5854771d229b Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 14:16:07 +0200 Subject: [PATCH 17/40] Resolve TODOs concerning rule checking. --- cmd/csaf_checker/rules.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index e2fcbe9..4e210ff 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -182,11 +182,10 @@ func (p *processor) eval(requirement int) bool { case 15: return !p.badROLIEfeed.hasErrors() case 16: - // TODO: Implement me! - return true + return !p.badROLIEservice.hasErrors() case 17: - // TODO: Implement me! - return true + return !p.badROLIEcategory.hasErrors() + case 18: return !p.badIntegrities.hasErrors() case 19: From 71a3c3a13bf000ad608f95ac8259c60574f7fca9 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 14:18:46 +0200 Subject: [PATCH 18/40] Unify camel case spelling of message tracking. --- cmd/csaf_checker/processor.go | 14 +++++++------- cmd/csaf_checker/reporters.go | 18 +++++++++--------- cmd/csaf_checker/roliecheck.go | 34 +++++++++++++++++----------------- cmd/csaf_checker/rules.go | 6 +++--- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index edf92b7..07d2246 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -67,9 +67,9 @@ type processor struct { badWellknownMetadata topicMessages badDNSPath topicMessages badDirListings topicMessages - badROLIEfeed topicMessages - badROLIEservice topicMessages - badROLIEcategory topicMessages + badROLIEFeed topicMessages + badROLIEService topicMessages + badROLIECategory topicMessages expr *util.PathEval } @@ -235,9 +235,9 @@ func (p *processor) clean() { p.badWellknownMetadata.reset() p.badDNSPath.reset() p.badDirListings.reset() - p.badROLIEfeed.reset() - p.badROLIEservice.reset() - p.badROLIEcategory.reset() + p.badROLIEFeed.reset() + p.badROLIEService.reset() + p.badROLIECategory.reset() p.labelChecker = nil } @@ -685,7 +685,7 @@ func (p *processor) integrity( // Extract the tlp level of the entry if tlpa, err := p.expr.Eval( `$.document.distribution`, doc); err != nil { - p.badROLIEfeed.error( + p.badROLIEFeed.error( "Extracting 'tlp level' from %s failed: %v", u, err) } else { tlpe := extractTLP(tlpa) diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index c9d9ed9..bce05c4 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -324,15 +324,15 @@ func (r *directoryListingsReporter) report(p *processor, domain *Domain) { // of the "Requirement" struct as a result of that. func (r *rolieFeedReporter) report(p *processor, domain *Domain) { req := r.requirement(domain) - if !p.badROLIEfeed.used() { + if !p.badROLIEFeed.used() { req.message(InfoType, "No checks on the validity of ROLIE feeds performed.") return } - if len(p.badROLIEfeed) == 0 { + if len(p.badROLIEFeed) == 0 { req.message(InfoType, "All checked ROLIE feeds validated fine.") return } - req.Messages = p.badROLIEfeed + req.Messages = p.badROLIEFeed } // report tests whether a ROLIE service document is used and if so, @@ -341,15 +341,15 @@ func (r *rolieFeedReporter) report(p *processor, domain *Domain) { // of the "Requirement" struct as a result of that. func (r *rolieServiceReporter) report(p *processor, domain *Domain) { req := r.requirement(domain) - if !p.badROLIEservice.used() { + if !p.badROLIEService.used() { req.message(InfoType, "ROLIE service document was not checked.") return } - if len(p.badROLIEservice) == 0 { + if len(p.badROLIEService) == 0 { req.message(InfoType, "ROLIE service document validated fine.") return } - req.Messages = p.badROLIEservice + req.Messages = p.badROLIEService } @@ -360,15 +360,15 @@ func (r *rolieServiceReporter) report(p *processor, domain *Domain) { // of the "Requirement" struct as a result of that. func (r *rolieCategoryReporter) report(p *processor, domain *Domain) { req := r.requirement(domain) - if !p.badROLIEcategory.used() { + if !p.badROLIECategory.used() { req.message(InfoType, "No checks on the existence of ROLIE category documents performed.") return } - if len(p.badROLIEcategory) == 0 { + if len(p.badROLIECategory) == 0 { req.message(InfoType, "All checked ROLIE category documents exist.") return } - req.Messages = p.badROLIEcategory + req.Messages = p.badROLIECategory } diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index e69af48..60a4a27 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -78,11 +78,11 @@ func (ca *rolieLabelChecker) check( switch { case advisoryRank < feedRank: if advisoryRank == 0 { // All kinds of 'UNLABELED' - p.badROLIEfeed.info( + p.badROLIEFeed.info( "Found unlabeled advisory %q in feed %q.", advisory, ca.feedURL) } else { - p.badROLIEfeed.warn( + p.badROLIEFeed.warn( "Found advisory %q labled TLP:%s in feed %q (TLP:%s).", advisory, advisoryLabel, ca.feedURL, ca.feedLabel) @@ -90,7 +90,7 @@ func (ca *rolieLabelChecker) check( case advisoryRank > feedRank: // Must not happen, give error - p.badROLIEfeed.error( + p.badROLIEFeed.error( "%s of TLP level %s must not be listed in feed %s of TLP level %s", advisory, advisoryLabel, ca.feedURL, ca.feedLabel) } @@ -104,7 +104,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { if err != nil { return err } - p.badROLIEfeed.use() + p.badROLIEFeed.use() advisories := map[*csaf.Feed][]csaf.AdvisoryFile{} @@ -239,7 +239,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } if !hasWhite && !hasGreen && !hasUnlabeled { - p.badROLIEfeed.error( + p.badROLIEFeed.error( "One ROLIE feed with a TLP:WHITE, TLP:GREEN or unlabeled tlp must exist, " + "but none were found.") } @@ -253,7 +253,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { csaf.TLPLabelRed, } { if _, ok := hasSummary[label]; !ok && len(p.labelChecker.advisories[label]) > 0 { - p.badROLIEfeed.warn( + p.badROLIEFeed.warn( "ROLIE feed for TLP:%s has no accessible listed feed covering all advisories.", label) } @@ -278,15 +278,15 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { labelname := strings.ToLower(string(label)) urlrc := folderURL + "category-" + labelname + ".json" - p.badROLIEcategory.use() + p.badROLIECategory.use() client := p.httpClient() res, err := client.Get(urlrc) if err != nil { - p.badROLIEcategory.error("Cannot fetch rolie category document %s: %v", urlrc, err) + p.badROLIECategory.error("Cannot fetch rolie category document %s: %v", urlrc, err) return errContinue } if res.StatusCode != http.StatusOK { - p.badROLIEcategory.warn("Fetching %s failed. Status code %d (%s)", + p.badROLIECategory.warn("Fetching %s failed. Status code %d (%s)", urlrc, res.StatusCode, res.Status) return errContinue } @@ -296,11 +296,11 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { }() if err != nil { - p.badROLIEcategory.error("Loading ROLIE category document failed: %v.", err) + p.badROLIECategory.error("Loading ROLIE category document failed: %v.", err) return errContinue } if len(rolieCategory.Categories.Category) == 0 { - p.badROLIEcategory.warn("No distinguishing categories in ROLIE category document: %s", urlrc) + p.badROLIECategory.warn("No distinguishing categories in ROLIE category document: %s", urlrc) } return nil } @@ -320,16 +320,16 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { urls := baseURL + "service.json" // load service document - p.badROLIEservice.use() + p.badROLIEService.use() client := p.httpClient() res, err := client.Get(urls) if err != nil { - p.badROLIEservice.error("Cannot fetch rolie service document %s: %v", urls, err) + p.badROLIEService.error("Cannot fetch rolie service document %s: %v", urls, err) return errContinue } if res.StatusCode != http.StatusOK { - p.badROLIEservice.warn("Fetching %s failed. Status code %d (%s)", + p.badROLIEService.warn("Fetching %s failed. Status code %d (%s)", urls, res.StatusCode, res.Status) return errContinue } @@ -340,7 +340,7 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { }() if err != nil { - p.badROLIEservice.error("Loading ROLIE service document failed: %v.", err) + p.badROLIEService.error("Loading ROLIE service document failed: %v.", err) return errContinue } @@ -361,11 +361,11 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { // Check if ROLIE Service Document contains exactly all ROLIE feeds if m1 := sfeeds.Difference(ffeeds).Keys(); len(m1) != 0 { sort.Strings(m1) - p.badROLIEservice.error("The ROLIE service document contains nonexistent feed entries: %v", m1) + p.badROLIEService.error("The ROLIE service document contains nonexistent feed entries: %v", m1) } if m2 := ffeeds.Difference(sfeeds).Keys(); len(m2) != 0 { sort.Strings(m2) - p.badROLIEservice.error("The ROLIE service document is missing feed entries: %v", m2) + p.badROLIEService.error("The ROLIE service document is missing feed entries: %v", m2) } // TODO: Check conformity with RFC8322 diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 4e210ff..57cef91 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -180,11 +180,11 @@ func (p *processor) eval(requirement int) bool { return !p.badDirListings.hasErrors() case 15: - return !p.badROLIEfeed.hasErrors() + return !p.badROLIEFeed.hasErrors() case 16: - return !p.badROLIEservice.hasErrors() + return !p.badROLIEService.hasErrors() case 17: - return !p.badROLIEcategory.hasErrors() + return !p.badROLIECategory.hasErrors() case 18: return !p.badIntegrities.hasErrors() From c6d0e9a9e28753d34f772b3c5a43505faf6ace75 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 14:35:51 +0200 Subject: [PATCH 19/40] Utilize new set type more. --- cmd/csaf_checker/roliecheck.go | 31 +++++++++++-------------------- util/set.go | 10 ++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 60a4a27..71c49b4 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -24,7 +24,7 @@ type rolieLabelChecker struct { feedURL string feedLabel csaf.TLPLabel - advisories map[csaf.TLPLabel]map[string]struct{} + advisories map[csaf.TLPLabel]util.Set[string] } // tlpLevel returns an inclusion order of TLP colors. @@ -68,10 +68,10 @@ func (ca *rolieLabelChecker) check( // Associate advisory label to urls. advs := ca.advisories[advisoryLabel] if advs == nil { - advs = make(map[string]struct{}) + advs = util.Set[string]{} ca.advisories[advisoryLabel] = advs } - advs[advisory] = struct{}{} + advs.Add(advisory) // If entry shows up in feed of higher tlp level, // give out info or warning @@ -170,7 +170,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { p.labelChecker = &rolieLabelChecker{ feedURL: feedURL.String(), feedLabel: label, - advisories: map[csaf.TLPLabel]map[string]struct{}{}, + advisories: map[csaf.TLPLabel]util.Set[string]{}, } if err := p.integrity(files, feedBase, rolieMask, p.badProviderMetadata.add); err != nil { @@ -183,7 +183,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { // Phase 3: Check for completeness. - hasSummary := map[csaf.TLPLabel]struct{}{} + hasSummary := util.Set[csaf.TLPLabel]{} var ( hasUnlabeled = false @@ -222,18 +222,19 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } reference := p.labelChecker.advisories[label] - advisories := make(map[string]struct{}, len(reference)) + advisories := make(util.Set[string], len(reference)) for _, adv := range files { u, err := url.Parse(adv.URL()) if err != nil { - p.badProviderMetadata.error("Invalid URL %s in feed: %v.", *feed.URL, err) + p.badProviderMetadata.error( + "Invalid URL %s in feed: %v.", *feed.URL, err) continue } advisories[makeAbs(u).String()] = struct{}{} } - if containsAllKeys(reference, advisories) { - hasSummary[label] = struct{}{} + if advisories.ContainsAll(reference) { + hasSummary.Add(label) } } } @@ -252,7 +253,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { csaf.TLPLabelAmber, csaf.TLPLabelRed, } { - if _, ok := hasSummary[label]; !ok && len(p.labelChecker.advisories[label]) > 0 { + if hasSummary.Contains(label) && len(p.labelChecker.advisories[label]) > 0 { p.badROLIEFeed.warn( "ROLIE feed for TLP:%s has no accessible listed feed covering all advisories.", label) @@ -262,16 +263,6 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { return nil } -// containsAllKeys returns if m2 contains all keys of m1. -func containsAllKeys[K comparable, V any](m1, m2 map[K]V) bool { - for k := range m1 { - if _, ok := m2[k]; !ok { - return false - } - } - return true -} - // categoryCheck checks for the existence of a feeds ROLIE category document and if it does, // whether the category document contains distinguishing categories func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { diff --git a/util/set.go b/util/set.go index 0160bdc..0df693d 100644 --- a/util/set.go +++ b/util/set.go @@ -41,3 +41,13 @@ func (s Set[K]) Difference(t Set[K]) Set[K] { } return d } + +// ContainsAll returns true if all keys of a given set are in this set. +func (s Set[K]) ContainsAll(t Set[K]) bool { + for k := range t { + if !s.Contains(k) { + return false + } + } + return true +} From a9dcfc26f344864e6c8b42159e160c05bd732d41 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 15 Jun 2023 14:45:30 +0200 Subject: [PATCH 20/40] Break some overly long lines. --- cmd/csaf_checker/roliecheck.go | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 71c49b4..bf088f1 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -273,7 +273,8 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { client := p.httpClient() res, err := client.Get(urlrc) if err != nil { - p.badROLIECategory.error("Cannot fetch rolie category document %s: %v", urlrc, err) + p.badROLIECategory.error( + "Cannot fetch rolie category document %s: %v", urlrc, err) return errContinue } if res.StatusCode != http.StatusOK { @@ -287,11 +288,13 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { }() if err != nil { - p.badROLIECategory.error("Loading ROLIE category document failed: %v.", err) + p.badROLIECategory.error( + "Loading ROLIE category document failed: %v.", err) return errContinue } if len(rolieCategory.Categories.Category) == 0 { - p.badROLIECategory.warn("No distinguishing categories in ROLIE category document: %s", urlrc) + p.badROLIECategory.warn( + "No distinguishing categories in ROLIE category document: %s", urlrc) } return nil } @@ -316,7 +319,8 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { client := p.httpClient() res, err := client.Get(urls) if err != nil { - p.badROLIEService.error("Cannot fetch rolie service document %s: %v", urls, err) + p.badROLIEService.error( + "Cannot fetch rolie service document %s: %v", urls, err) return errContinue } if res.StatusCode != http.StatusOK { @@ -331,13 +335,16 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { }() if err != nil { - p.badROLIEService.error("Loading ROLIE service document failed: %v.", err) + p.badROLIEService.error( + "Loading ROLIE service document failed: %v.", err) return errContinue } // Build lists of all feeds in feeds and in the Service Document - sfeeds := util.Set[string]{} - ffeeds := util.Set[string]{} + var ( + sfeeds = util.Set[string]{} + ffeeds = util.Set[string]{} + ) for _, col := range rolieService.Service.Workspace { for _, fd := range col.Collection { sfeeds.Add(fd.HRef) @@ -352,11 +359,13 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { // Check if ROLIE Service Document contains exactly all ROLIE feeds if m1 := sfeeds.Difference(ffeeds).Keys(); len(m1) != 0 { sort.Strings(m1) - p.badROLIEService.error("The ROLIE service document contains nonexistent feed entries: %v", m1) + p.badROLIEService.error( + "The ROLIE service document contains nonexistent feed entries: %v", m1) } if m2 := ffeeds.Difference(sfeeds).Keys(); len(m2) != 0 { sort.Strings(m2) - p.badROLIEService.error("The ROLIE service document is missing feed entries: %v", m2) + p.badROLIEService.error( + "The ROLIE service document is missing feed entries: %v", m2) } // TODO: Check conformity with RFC8322 From 5614939562ef5fc696c4c8b9ae81cca39da77c61 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 15 Jun 2023 14:55:20 +0200 Subject: [PATCH 21/40] Add offending file to error message for Requirements 16/17 --- cmd/csaf_checker/roliecheck.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index bf088f1..ff9d018 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -289,7 +289,7 @@ func (p *processor) categoryCheck(folderURL string, label csaf.TLPLabel) error { if err != nil { p.badROLIECategory.error( - "Loading ROLIE category document failed: %v.", err) + "Loading ROLIE category document %s failed: %v.", urlrc, err) return errContinue } if len(rolieCategory.Categories.Category) == 0 { @@ -336,7 +336,7 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { if err != nil { p.badROLIEService.error( - "Loading ROLIE service document failed: %v.", err) + "Loading ROLIE service document %s failed: %v.", urls, err) return errContinue } @@ -360,12 +360,12 @@ func (p *processor) serviceCheck(feeds [][]csaf.Feed) error { if m1 := sfeeds.Difference(ffeeds).Keys(); len(m1) != 0 { sort.Strings(m1) p.badROLIEService.error( - "The ROLIE service document contains nonexistent feed entries: %v", m1) + "The ROLIE service document %s contains nonexistent feed entries: %v", urls, m1) } if m2 := ffeeds.Difference(sfeeds).Keys(); len(m2) != 0 { sort.Strings(m2) p.badROLIEService.error( - "The ROLIE service document is missing feed entries: %v", m2) + "The ROLIE service document %s is missing feed entries: %v", urls, m2) } // TODO: Check conformity with RFC8322 From 8d269ce106e963d4f4d790c2d9ab692b7ad99e8f Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Fri, 16 Jun 2023 14:10:54 +0200 Subject: [PATCH 22/40] No longer require optional distribution section in advisory to extract TLP label --- cmd/csaf_checker/processor.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 07d2246..645eefb 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -684,7 +684,7 @@ func (p *processor) integrity( // Extract the tlp level of the entry if tlpa, err := p.expr.Eval( - `$.document.distribution`, doc); err != nil { + `$.document`, doc); err != nil { p.badROLIEFeed.error( "Extracting 'tlp level' from %s failed: %v", u, err) } else { @@ -810,11 +810,15 @@ func (p *processor) integrity( // extractTLP tries to extract a valid TLP label from an advisory // Returns "UNLABELED" if it does not exist, the label otherwise func extractTLP(tlpa any) csaf.TLPLabel { - if distribution, ok := tlpa.(map[string]any); ok { - if tlp, ok := distribution["tlp"]; ok { - if label, ok := tlp.(map[string]any); ok { - if labelstring, ok := label["label"].(string); ok { - return csaf.TLPLabel(labelstring) + if document, ok := tlpa.(map[string]any); ok { + if distri, ok := document["distribution"]; ok { + if distribution, ok := distri.(map[string]any); ok { + if tlp, ok := distribution["tlp"]; ok { + if label, ok := tlp.(map[string]any); ok { + if labelstring, ok := label["label"].(string); ok { + return csaf.TLPLabel(labelstring) + } + } } } } From 2ec8be4e8ceff77a4656137b4f688db39d643df2 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Fri, 16 Jun 2023 15:11:07 +0200 Subject: [PATCH 23/40] Instantiate label checker only once. --- cmd/csaf_checker/roliecheck.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index ff9d018..59616f1 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -134,6 +134,9 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { advisories[feed] = advs } } + p.labelChecker = &rolieLabelChecker{ + advisories: map[csaf.TLPLabel]util.Set[string]{}, + } // Phase 2: check for integrity. for _, fs := range feeds { @@ -167,11 +170,8 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } } - p.labelChecker = &rolieLabelChecker{ - feedURL: feedURL.String(), - feedLabel: label, - advisories: map[csaf.TLPLabel]util.Set[string]{}, - } + p.labelChecker.feedURL = feedURL.String() + p.labelChecker.feedLabel = label if err := p.integrity(files, feedBase, rolieMask, p.badProviderMetadata.add); err != nil { if err != errContinue { From d5589a018db81fd27ce53f3e021ee438d1be70a8 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Fri, 16 Jun 2023 17:15:39 +0200 Subject: [PATCH 24/40] Change roliecheck.go: Now check whether no summary label exist, instead of incorrectly checking whether one exists --- cmd/csaf_checker/roliecheck.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 59616f1..19b0c29 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -253,7 +253,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { csaf.TLPLabelAmber, csaf.TLPLabelRed, } { - if hasSummary.Contains(label) && len(p.labelChecker.advisories[label]) > 0 { + if !hasSummary.Contains(label) && len(p.labelChecker.advisories[label]) > 0 { p.badROLIEFeed.warn( "ROLIE feed for TLP:%s has no accessible listed feed covering all advisories.", label) From 421a05d42128dcb1f0bf6e935a844a2bc9cfb243 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 19 Jun 2023 13:49:31 +0200 Subject: [PATCH 25/40] Ignore domain not having roles. --- cmd/csaf_checker/processor.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 645eefb..7b94660 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -271,6 +271,11 @@ func (p *processor) run(domains []string) (*Report, error) { continue } + if domain.Role == nil { + log.Printf("No role found in meta data. Ignoring domain %q\n", d) + continue + } + rules := roleRequirements(*domain.Role) // TODO: store error base on rules eval in report. if rules == nil { From 20bf16bd4fbdf4e605f4e45ece0de621a86f4df4 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 19 Jun 2023 17:49:35 +0200 Subject: [PATCH 26/40] Add stubs for missing rule checks of providers. --- cmd/csaf_checker/rules.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 57cef91..0fef547 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -38,6 +38,7 @@ var ( cond: condAll, subs: []*requirementRules{ publisherRules, + {cond: condAll, subs: ruleAtoms(5, 6, 7)}, {cond: condOneOf, subs: ruleAtoms(8, 9, 10)}, {cond: condOneOf, subs: []*requirementRules{ {cond: condAll, subs: ruleAtoms(11, 12, 13, 14)}, @@ -163,6 +164,15 @@ func (p *processor) eval(requirement int) bool { case 3: return len(p.noneTLS) == 0 + case 5: + // TODO: implement me! + return true + case 6: + return len(p.redirects) == 0 + case 7: + // TODO: implement me! + return true + case 8: return !p.badSecurity.hasErrors() case 9: From ed26e8e41dce4d0c9803522049f60a46e02b0fa8 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 21 Jun 2023 15:35:00 +0200 Subject: [PATCH 27/40] Add Tests for Requirements 4 and 5 --- cmd/csaf_checker/processor.go | 36 ++++++++++++++------------ cmd/csaf_checker/reporters.go | 26 ++++++++++++++++--- cmd/csaf_checker/roliecheck.go | 47 ++++++++++++++++++++++++++++++++-- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 7b94660..c883fd2 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -54,22 +54,24 @@ type processor struct { keys *crypto.KeyRing labelChecker *rolieLabelChecker - invalidAdvisories topicMessages - badFilenames topicMessages - badIntegrities topicMessages - badPGPs topicMessages - badSignatures topicMessages - badProviderMetadata topicMessages - badSecurity topicMessages - badIndices topicMessages - badChanges topicMessages - badFolders topicMessages - badWellknownMetadata topicMessages - badDNSPath topicMessages - badDirListings topicMessages - badROLIEFeed topicMessages - badROLIEService topicMessages - badROLIECategory topicMessages + invalidAdvisories topicMessages + badFilenames topicMessages + badIntegrities topicMessages + badPGPs topicMessages + badSignatures topicMessages + badProviderMetadata topicMessages + badSecurity topicMessages + badIndices topicMessages + badChanges topicMessages + badFolders topicMessages + badWellknownMetadata topicMessages + badDNSPath topicMessages + badDirListings topicMessages + badROLIEFeed topicMessages + badROLIEService topicMessages + badROLIECategory topicMessages + badWhitePermissions topicMessages + badAmberRedPermissions topicMessages expr *util.PathEval } @@ -238,6 +240,8 @@ func (p *processor) clean() { p.badROLIEFeed.reset() p.badROLIEService.reset() p.badROLIECategory.reset() + p.badWhitePermissions.reset() + p.badAmberRedPermissions.reset() p.labelChecker = nil } diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index bce05c4..dfafc6c 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -152,16 +152,34 @@ func (r *tlsReporter) report(p *processor, domain *Domain) { // report tests if a document labeled TLP:WHITE // is freely accessible and sets the "message" field value // of the "Requirement" struct as a result of that. -func (r *tlpWhiteReporter) report(_ *processor, _ *Domain) { - // TODO +func (r *tlpWhiteReporter) report(p *processor, domain *Domain) { + req := r.requirement(domain) + if !p.badWhitePermissions.used() { + req.message(InfoType, "No advisories labeled TLP:WHITE tested for accessibility.") + return + } + if len(p.badWhitePermissions) == 0 { + req.message(InfoType, "All advisories labeled TLP:WHITE were freely accessible.") + return + } + req.Messages = p.badWhitePermissions } // report tests if a document labeled TLP:AMBER // or TLP:RED is access protected // and sets the "message" field value // of the "Requirement" struct as a result of that. -func (r *tlpAmberRedReporter) report(_ *processor, _ *Domain) { - // TODO +func (r *tlpAmberRedReporter) report(p *processor, domain *Domain) { + req := r.requirement(domain) + if !p.badAmberRedPermissions.used() { + req.message(InfoType, "No advisories labeled TLP:AMBER or TLP:RED tested for accessibility.") + return + } + if len(p.badAmberRedPermissions) == 0 { + req.message(InfoType, "All tested advisories labeled TLP:WHITE or TLP:RED were access-protected.") + return + } + req.Messages = p.badAmberRedPermissions } // report tests if redirects are used and sets the "message" field value diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 19b0c29..96f6618 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -9,6 +9,7 @@ package main import ( + "crypto/tls" "net/http" "net/url" "sort" @@ -24,10 +25,12 @@ type rolieLabelChecker struct { feedURL string feedLabel csaf.TLPLabel - advisories map[csaf.TLPLabel]util.Set[string] + advisories map[csaf.TLPLabel]util.Set[string] + basicClient *http.Client } // tlpLevel returns an inclusion order of TLP colors. +// TODO: Is this the right location to put the p.[...].use()? func tlpLevel(label csaf.TLPLabel) int { switch label { case csaf.TLPLabelWhite: @@ -52,6 +55,17 @@ func tlpLabel(label *csaf.TLPLabel) csaf.TLPLabel { return csaf.TLPLabelUnlabeled } +// createBasicClient creates and returns a http Client +func (p *processor) createBasicClient() *http.Client { + if p.opts.Insecure { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + return &http.Client{Transport: tr} + } + return &http.Client{} +} + // check tests if in advisory is in the right TLP color of the // currently tested feed. func (ca *rolieLabelChecker) check( @@ -94,6 +108,34 @@ func (ca *rolieLabelChecker) check( "%s of TLP level %s must not be listed in feed %s of TLP level %s", advisory, advisoryLabel, ca.feedURL, ca.feedLabel) } + + switch { + case advisoryRank == 1: + p.badWhitePermissions.use() + case advisoryRank > 2: + p.badAmberRedPermissions.use() + } + + res, err := ca.basicClient.Get(advisory) + if err != nil { + switch { + case advisoryRank == 1: + p.badWhitePermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) + case advisoryRank > 2: + p.badAmberRedPermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) + } + } + switch res.StatusCode { + case http.StatusOK: + if advisoryRank > 2 { + p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) + } + case http.StatusForbidden: + if advisoryRank == 1 { + // TODO: Differentiate between error and warning based on whether the advisory appears in a not access protected location as well. + p.badWhitePermissions.warn("Advisory %s of TLP level WHITE is access protected.", advisory) + } + } } // processROLIEFeeds goes through all ROLIE feeds and checks their @@ -135,7 +177,8 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { } } p.labelChecker = &rolieLabelChecker{ - advisories: map[csaf.TLPLabel]util.Set[string]{}, + advisories: map[csaf.TLPLabel]util.Set[string]{}, + basicClient: p.createBasicClient(), } // Phase 2: check for integrity. From 248e0a52a4a7d94506b93d530fb251f473ff8635 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 21 Jun 2023 15:38:40 +0200 Subject: [PATCH 28/40] Enable Req 5 in rules.go --- cmd/csaf_checker/rules.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 0fef547..58a165c 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -165,8 +165,7 @@ func (p *processor) eval(requirement int) bool { return len(p.noneTLS) == 0 case 5: - // TODO: implement me! - return true + return !p.badAmberRedPermissions.hasErrors() case 6: return len(p.redirects) == 0 case 7: From d393a42d61ec38b882098e197a84cbc074990d2a Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 21 Jun 2023 16:21:23 +0200 Subject: [PATCH 29/40] Formatting permission check --- cmd/csaf_checker/roliecheck.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 96f6618..cbcafcc 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -109,32 +109,24 @@ func (ca *rolieLabelChecker) check( advisory, advisoryLabel, ca.feedURL, ca.feedLabel) } + res, err := ca.basicClient.Get(advisory) switch { case advisoryRank == 1: p.badWhitePermissions.use() - case advisoryRank > 2: - p.badAmberRedPermissions.use() - } - - res, err := ca.basicClient.Get(advisory) - if err != nil { - switch { - case advisoryRank == 1: + if err != nil { p.badWhitePermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) - case advisoryRank > 2: - p.badAmberRedPermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) - } - } - switch res.StatusCode { - case http.StatusOK: - if advisoryRank > 2 { - p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) - } - case http.StatusForbidden: - if advisoryRank == 1 { + } else if res.StatusCode == http.StatusForbidden { // TODO: Differentiate between error and warning based on whether the advisory appears in a not access protected location as well. p.badWhitePermissions.warn("Advisory %s of TLP level WHITE is access protected.", advisory) } + case advisoryRank > 2: + p.badAmberRedPermissions.use() + if err != nil { + p.badAmberRedPermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) + } else if res.StatusCode == http.StatusOK { + p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) + + } } } From 7dc1a6530e45541b4b367bc4478941b21c231c49 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 22 Jun 2023 13:27:48 +0200 Subject: [PATCH 30/40] add badProviderMetadata to rules --- cmd/csaf_checker/rules.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/csaf_checker/rules.go b/cmd/csaf_checker/rules.go index 58a165c..0899480 100644 --- a/cmd/csaf_checker/rules.go +++ b/cmd/csaf_checker/rules.go @@ -169,9 +169,7 @@ func (p *processor) eval(requirement int) bool { case 6: return len(p.redirects) == 0 case 7: - // TODO: implement me! - return true - + return !p.badProviderMetadata.hasErrors() case 8: return !p.badSecurity.hasErrors() case 9: From 18732f26baad1a1faf98ef57e94bb7738501e950 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 22 Jun 2023 13:40:00 +0200 Subject: [PATCH 31/40] Amend checker docs to explain why authorization for RED/AMBER advisories needs to be genuine --- docs/csaf_checker.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/csaf_checker.md b/docs/csaf_checker.md index e418813..74e7475 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -49,3 +49,8 @@ The checker result is a success if no checks resulted in type 2, and a failure o The `role` given in the `provider-metadata.json` is not yet considered to change the overall result, see https://github.com/csaf-poc/csaf_distribution/issues/221 . + +If a provider hosts one or more advisories with a TLP level of AMBER or RED, then these advisories should be access protected. +To check these advisories, authorization can be given via custom headers or certificates. +The authorization method chosen should grant access to all advisories, as otherwise the +checker will be unable to check all advisories and returns likely wrong output. From 9967bfffe65eb7c848dd247f73851c8dd2a6f99d Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 22 Jun 2023 13:46:16 +0200 Subject: [PATCH 32/40] Amend checker docs to explain why authorization for RED/AMBER advisories needs to be genuine --- docs/csaf_checker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csaf_checker.md b/docs/csaf_checker.md index 74e7475..96bb6c7 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -52,5 +52,5 @@ see https://github.com/csaf-poc/csaf_distribution/issues/221 . If a provider hosts one or more advisories with a TLP level of AMBER or RED, then these advisories should be access protected. To check these advisories, authorization can be given via custom headers or certificates. -The authorization method chosen should grant access to all advisories, as otherwise the -checker will be unable to check all advisories and returns likely wrong output. +The authorization method chosen needs to grant access to all advisories, as otherwise the +checker will be unable to check the advisories it doesn't have permission for, falsifying the result. From 594e6b4b0df97e4c406cb5b88d3ee4f968a623e8 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 22 Jun 2023 14:28:45 +0200 Subject: [PATCH 33/40] Try to minimize redundant downloads. --- cmd/csaf_checker/main.go | 8 ++++ cmd/csaf_checker/processor.go | 31 ++++++++++--- cmd/csaf_checker/roliecheck.go | 80 ++++++++++++++++++++-------------- 3 files changed, 80 insertions(+), 39 deletions(-) diff --git a/cmd/csaf_checker/main.go b/cmd/csaf_checker/main.go index 9fd0fa0..eafa9c8 100644 --- a/cmd/csaf_checker/main.go +++ b/cmd/csaf_checker/main.go @@ -74,6 +74,14 @@ func (o *options) prepare() error { return nil } +// protectedAccess returns true if we have client certificates or +// extra http headers configured. +// This may be a wrong assumption, because the certs are not checked +// for their domain and custom headers may have other purposes. +func (o *options) protectedAccess() bool { + return len(o.clientCerts) > 0 || len(o.ExtraHeader) > 0 +} + // writeJSON writes the JSON encoding of the given report to the given stream. // It returns nil, otherwise an error. func writeJSON(report *Report, w io.WriteCloser) error { diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index c883fd2..9b26093 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -407,12 +407,8 @@ func (p *processor) checkRedirect(r *http.Request, via []*http.Request) error { return nil } -func (p *processor) httpClient() util.Client { - - if p.client != nil { - return p.client - } - +// fullClient returns a fully configure HTTP client. +func (p *processor) fullClient() util.Client { hClient := http.Client{} hClient.CheckRedirect = p.checkRedirect @@ -452,8 +448,29 @@ func (p *processor) httpClient() util.Client { Limiter: rate.NewLimiter(rate.Limit(*p.opts.Rate), 1), } } + return client +} - p.client = client +// basicClient returns a http Client w/o certs and headers. +func (p *processor) basicClient() *http.Client { + if p.opts.Insecure { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + return &http.Client{Transport: tr} + } + return &http.Client{} +} + +// httpClient returns a cached HTTP client to be used to +// download remote ressources. +func (p *processor) httpClient() util.Client { + + if p.client != nil { + return p.client + } + + p.client = p.fullClient() return p.client } diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index cbcafcc..009af34 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -9,7 +9,6 @@ package main import ( - "crypto/tls" "net/http" "net/url" "sort" @@ -25,8 +24,8 @@ type rolieLabelChecker struct { feedURL string feedLabel csaf.TLPLabel - advisories map[csaf.TLPLabel]util.Set[string] - basicClient *http.Client + advisories map[csaf.TLPLabel]util.Set[string] + openClient util.Client } // tlpLevel returns an inclusion order of TLP colors. @@ -55,17 +54,6 @@ func tlpLabel(label *csaf.TLPLabel) csaf.TLPLabel { return csaf.TLPLabelUnlabeled } -// createBasicClient creates and returns a http Client -func (p *processor) createBasicClient() *http.Client { - if p.opts.Insecure { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - return &http.Client{Transport: tr} - } - return &http.Client{} -} - // check tests if in advisory is in the right TLP color of the // currently tested feed. func (ca *rolieLabelChecker) check( @@ -109,23 +97,38 @@ func (ca *rolieLabelChecker) check( advisory, advisoryLabel, ca.feedURL, ca.feedLabel) } - res, err := ca.basicClient.Get(advisory) - switch { - case advisoryRank == 1: - p.badWhitePermissions.use() - if err != nil { - p.badWhitePermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) - } else if res.StatusCode == http.StatusForbidden { - // TODO: Differentiate between error and warning based on whether the advisory appears in a not access protected location as well. - p.badWhitePermissions.warn("Advisory %s of TLP level WHITE is access protected.", advisory) - } - case advisoryRank > 2: - p.badAmberRedPermissions.use() - if err != nil { - p.badAmberRedPermissions.error("Unexpected Error %v when trying to fetch: %s", err, advisory) - } else if res.StatusCode == http.StatusOK { - p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) + // If we have an open client then the actual data was downloaded + // through an authorizing client. + if ca.openClient != nil { + switch { + // If we are checking WHITE and we have a test client + // and we get a status forbidden then the access is not open. + case ca.feedLabel == csaf.TLPLabelWhite: + p.badWhitePermissions.use() + res, err := ca.openClient.Get(advisory) + if err != nil { + p.badWhitePermissions.error( + "Unexpected Error %v when trying to fetch: %s", err, advisory) + } else if res.StatusCode == http.StatusForbidden { + p.badWhitePermissions.error( + "Advisory %s of TLP level WHITE is access protected.", advisory) + } + // If we are checking AMBER or above we need to download + // the data again with the open client. + // If this does not result in status forbidden the + // server may be wrongly configured. + case ca.feedLabel >= csaf.TLPLabelAmber: + p.badAmberRedPermissions.use() + res, err := ca.openClient.Get(advisory) + if err != nil { + p.badAmberRedPermissions.error( + "Unexpected Error %v when trying to fetch: %s", err, advisory) + } else if res.StatusCode == http.StatusOK { + p.badAmberRedPermissions.error( + "Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) + + } } } } @@ -168,9 +171,9 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { advisories[feed] = advs } } + p.labelChecker = &rolieLabelChecker{ - advisories: map[csaf.TLPLabel]util.Set[string]{}, - basicClient: p.createBasicClient(), + advisories: map[csaf.TLPLabel]util.Set[string]{}, } // Phase 2: check for integrity. @@ -208,6 +211,19 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { p.labelChecker.feedURL = feedURL.String() p.labelChecker.feedLabel = label + // If we are using an authorizing client + // we need a an open client to check + // WHITE, AMBER and RED feeds. + var openClient util.Client + if (label == csaf.TLPLabelWhite || label >= csaf.TLPLabelAmber) && + p.opts.protectedAccess() { + openClient = p.basicClient() + } + p.labelChecker.openClient = openClient + + // TODO: Issue a warning if we want check AMBER+ without an + // authorizing client. + if err := p.integrity(files, feedBase, rolieMask, p.badProviderMetadata.add); err != nil { if err != errContinue { return err From daa4a6bf7afb12a27c34f9165380587c79dc95ac Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Thu, 22 Jun 2023 14:46:06 +0200 Subject: [PATCH 34/40] Add TODO for fulfilling requierement 4 --- cmd/csaf_checker/roliecheck.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 009af34..925082b 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -224,6 +224,8 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { // TODO: Issue a warning if we want check AMBER+ without an // authorizing client. + // TODO: Complete criteria for requirement 4. + if err := p.integrity(files, feedBase, rolieMask, p.badProviderMetadata.add); err != nil { if err != errContinue { return err From 55f6a48db15efd3de1a20a499a0049c8cdaef271 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Thu, 22 Jun 2023 14:48:34 +0200 Subject: [PATCH 35/40] Remove solved TODO --- cmd/csaf_checker/roliecheck.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 925082b..296e278 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -29,7 +29,6 @@ type rolieLabelChecker struct { } // tlpLevel returns an inclusion order of TLP colors. -// TODO: Is this the right location to put the p.[...].use()? func tlpLevel(label csaf.TLPLabel) int { switch label { case csaf.TLPLabelWhite: From a02d9c36a7e1de6aee3d8ab249e4edbc032316da Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Fri, 23 Jun 2023 11:03:22 +0200 Subject: [PATCH 36/40] Add check for should-be-access-protected advisories for not-authorized client. --- cmd/csaf_checker/processor.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 9b26093..83d0be7 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -715,6 +715,11 @@ func (p *processor) integrity( "Extracting 'tlp level' from %s failed: %v", u, err) } else { tlpe := extractTLP(tlpa) + // If the client has no authorization it shouldn't be able to access TLP:AMBER or TLP:RED advisories + if !p.opts.protectedAccess() && (tlpe == "AMBER" || tlpe == "RED") { + p.badAmberRedPermissions.use() + p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not access protected.", u, tlpe) + } // check if current feed has correct or all of their tlp levels entries. if p.labelChecker != nil { p.labelChecker.check(p, tlpe, u) From 65536f51a48b52fca9c8fc9a5d2a5c102770d8cb Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Fri, 23 Jun 2023 11:40:09 +0200 Subject: [PATCH 37/40] Break overly long lines. Use defined constants for TLP levels. --- cmd/csaf_checker/processor.go | 11 ++++++++--- cmd/csaf_checker/roliecheck.go | 3 ++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 83d0be7..64f0db2 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -715,10 +715,15 @@ func (p *processor) integrity( "Extracting 'tlp level' from %s failed: %v", u, err) } else { tlpe := extractTLP(tlpa) - // If the client has no authorization it shouldn't be able to access TLP:AMBER or TLP:RED advisories - if !p.opts.protectedAccess() && (tlpe == "AMBER" || tlpe == "RED") { + // If the client has no authorization it shouldn't be able + // to access TLP:AMBER or TLP:RED advisories + if !p.opts.protectedAccess() && + (tlpe == csaf.TLPLabelAmber || tlpe == csaf.TLPLabelRed) { + p.badAmberRedPermissions.use() - p.badAmberRedPermissions.error("Advisory %s of TLP level %v is not access protected.", u, tlpe) + p.badAmberRedPermissions.error( + "Advisory %s of TLP level %v is not access protected.", + u, tlpe) } // check if current feed has correct or all of their tlp levels entries. if p.labelChecker != nil { diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 296e278..f3ac139 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -125,7 +125,8 @@ func (ca *rolieLabelChecker) check( "Unexpected Error %v when trying to fetch: %s", err, advisory) } else if res.StatusCode == http.StatusOK { p.badAmberRedPermissions.error( - "Advisory %s of TLP level %v is not properly access protected.", advisory, advisoryLabel) + "Advisory %s of TLP level %v is not properly access protected.", + advisory, advisoryLabel) } } From 569822486b77a8166a186cd0687f6b8f67733f28 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer <107021473+JanHoefelmeyer@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:58:38 +0200 Subject: [PATCH 38/40] Update docs/csaf_checker.md Fix wrong keyword in checker docs Co-authored-by: tschmidtb51 <65305130+tschmidtb51@users.noreply.github.com> --- 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 96bb6c7..30091e5 100644 --- a/docs/csaf_checker.md +++ b/docs/csaf_checker.md @@ -50,7 +50,7 @@ The `role` given in the `provider-metadata.json` is not yet considered to change the overall result, see https://github.com/csaf-poc/csaf_distribution/issues/221 . -If a provider hosts one or more advisories with a TLP level of AMBER or RED, then these advisories should be access protected. +If a provider hosts one or more advisories with a TLP level of AMBER or RED, then these advisories must be access protected. To check these advisories, authorization can be given via custom headers or certificates. The authorization method chosen needs to grant access to all advisories, as otherwise the checker will be unable to check the advisories it doesn't have permission for, falsifying the result. From c1765e696727eedcea9e0b09148183e2e9595e21 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer <107021473+JanHoefelmeyer@users.noreply.github.com> Date: Tue, 27 Jun 2023 09:59:15 +0200 Subject: [PATCH 39/40] Update cmd/csaf_checker/roliecheck.go Fix typo in roliecheck.go comment Co-authored-by: tschmidtb51 <65305130+tschmidtb51@users.noreply.github.com> --- cmd/csaf_checker/roliecheck.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index f3ac139..aaefa89 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -212,7 +212,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { p.labelChecker.feedLabel = label // If we are using an authorizing client - // we need a an open client to check + // we need an open client to check // WHITE, AMBER and RED feeds. var openClient util.Client if (label == csaf.TLPLabelWhite || label >= csaf.TLPLabelAmber) && From 04c11d7922f972aba79f3cc1c815229e80f904be Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Tue, 27 Jun 2023 10:05:49 +0200 Subject: [PATCH 40/40] formatting --- cmd/csaf_checker/roliecheck.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index aaefa89..fe217d6 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -212,7 +212,7 @@ func (p *processor) processROLIEFeeds(feeds [][]csaf.Feed) error { p.labelChecker.feedLabel = label // If we are using an authorizing client - // we need an open client to check + // we need an open client to check // WHITE, AMBER and RED feeds. var openClient util.Client if (label == csaf.TLPLabelWhite || label >= csaf.TLPLabelAmber) &&