From bd7831d7c32273c3ec77c5d49ab9d438013ade94 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 15 May 2023 12:12:42 +0200 Subject: [PATCH] Build reporters from role --- cmd/csaf_checker/main.go | 81 +++++++++++++++++++++++++++-------- cmd/csaf_checker/processor.go | 29 +++++++------ 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/cmd/csaf_checker/main.go b/cmd/csaf_checker/main.go index 645a17a..c1bcd03 100644 --- a/cmd/csaf_checker/main.go +++ b/cmd/csaf_checker/main.go @@ -21,7 +21,9 @@ import ( "log" "net/http" "os" + "sort" + "github.com/csaf-poc/csaf_distribution/csaf" "github.com/csaf-poc/csaf_distribution/util" "github.com/jessevdk/go-flags" ) @@ -140,26 +142,69 @@ func writeReport(report *Report, opts *options) error { return writer(report, w) } +var reporters = [23]reporter{ + &validReporter{baseReporter{num: 1, description: "Valid CSAF documents"}}, + &filenameReporter{baseReporter{num: 2, description: "Filename"}}, + &tlsReporter{baseReporter{num: 3, description: "TLS"}}, + nil, // TODO: Add 4: TLP:WHITE + nil, // TODO: Add 5: 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"}}, + nil, // TODO: Add 15: ROLIE feed + nil, // TODO: Add 16: ROLIE service document + nil, // TODO: Add 17: ROLIE category document + &integrityReporter{baseReporter{num: 18, description: "Integrity"}}, + &signaturesReporter{baseReporter{num: 19, description: "Signatures"}}, + &publicPGPKeyReporter{baseReporter{num: 20, description: "Public OpenPGP Key"}}, + nil, // TODO: Add 21: List of CSAF providers + nil, // TODO: Add 22: Two disjoint issuing parties + nil, // TODO: Add 23: 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: + own = [][2]int{{5, 7}, {8, 10}, {11, 14}, {15, 17}} + case csaf.MetadataRolePublisher: + own = [][2]int{{1, 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() []reporter { - return []reporter{ - &validReporter{baseReporter{num: 1, description: "Valid CSAF documents"}}, - &filenameReporter{baseReporter{num: 2, description: "Filename"}}, - &tlsReporter{baseReporter{num: 3, description: "TLS"}}, - &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"}}, - &integrityReporter{baseReporter{num: 18, description: "Integrity"}}, - &signaturesReporter{baseReporter{num: 19, description: "Signatures"}}, - &publicPGPKeyReporter{baseReporter{num: 20, description: "Public OpenPGP Key"}}, +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 } // run uses a processor to check all the given domains or direct urls @@ -170,7 +215,7 @@ func run(opts *options, domains []string) (*Report, error) { return nil, err } defer p.close() - return p.run(buildReporters(), domains) + return p.run(domains) } func main() { diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 3d98f3c..c928814 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -223,7 +223,7 @@ func (p *processor) clean() { // run calls checkDomain function for each domain in the given "domains" parameter. // Then it calls the report method on each report from the given "reporters" parameter for each domain. // It returns a pointer to the report and nil, otherwise an error. -func (p *processor) run(reporters []reporter, domains []string) (*Report, error) { +func (p *processor) run(domains []string) (*Report, error) { report := Report{ Date: ReportTime{Time: time.Now().UTC()}, @@ -231,19 +231,24 @@ func (p *processor) run(reporters []reporter, domains []string) (*Report, error) } for _, d := range domains { - if err := p.checkDomain(d); err != nil { - if err == errContinue || err == errStop { - continue + if p.checkProviderMetadata(d) { + if err := p.checkDomain(d); err != nil { + if err == errContinue || err == errStop { + continue + } + return nil, err } - return nil, err } domain := &Domain{Name: d} - for _, r := range reporters { - r.report(p, domain) - } if err := p.fillMeta(domain); err != nil { log.Printf("Filling meta data failed: %v\n", err) + // reporters depend on role. + continue + } + + for _, r := range buildReporters(*domain.Role) { + r.report(p, domain) } report.Domains = append(report.Domains, domain) @@ -287,7 +292,6 @@ func (p *processor) domainChecks(domain string) []func(*processor, string) error direct := strings.HasPrefix(domain, "https://") checks := []func(*processor, string) error{ - (*processor).checkProviderMetadata, (*processor).checkPGPKeys, } @@ -1107,8 +1111,7 @@ func (p *processor) checkListing(string) error { // decodes, and validates against the JSON schema. // According to the result, the respective error messages added to // badProviderMetadata. -// It returns nil if all checks are passed. -func (p *processor) checkProviderMetadata(domain string) error { +func (p *processor) checkProviderMetadata(domain string) bool { p.badProviderMetadata.use() @@ -1123,14 +1126,14 @@ func (p *processor) checkProviderMetadata(domain string) error { if !lpmd.Valid() { p.badProviderMetadata.error("No valid provider-metadata.json found.") p.badProviderMetadata.error("STOPPING here - cannot perform other checks.") - return errStop + return false } p.pmdURL = lpmd.URL p.pmd256 = lpmd.Hash p.pmd = lpmd.Document - return nil + return true } // checkSecurity checks the security.txt file by making HTTP request to fetch it.