1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00

Build reporters from role

This commit is contained in:
Sascha L. Teichmann 2023-05-15 12:12:42 +02:00
parent 018a1814f0
commit bd7831d7c3
2 changed files with 79 additions and 31 deletions

View file

@ -21,7 +21,9 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"sort"
"github.com/csaf-poc/csaf_distribution/csaf"
"github.com/csaf-poc/csaf_distribution/util" "github.com/csaf-poc/csaf_distribution/util"
"github.com/jessevdk/go-flags" "github.com/jessevdk/go-flags"
) )
@ -140,26 +142,69 @@ func writeReport(report *Report, opts *options) error {
return writer(report, w) 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. // buildReporters initializes each report by assigning a number and description to it.
// It returns an array of the reporter interface type. // It returns an array of the reporter interface type.
func buildReporters() []reporter { func buildReporters(role csaf.MetadataRole) []reporter {
return []reporter{ var reps []reporter
&validReporter{baseReporter{num: 1, description: "Valid CSAF documents"}}, reqs := requirements(role)
&filenameReporter{baseReporter{num: 2, description: "Filename"}}, // sort to have them ordered by there number.
&tlsReporter{baseReporter{num: 3, description: "TLS"}}, sort.Slice(reqs, func(i, j int) bool { return reqs[i][0] < reqs[j][0] })
&redirectsReporter{baseReporter{num: 6, description: "Redirects"}}, for _, req := range reqs {
&providerMetadataReport{baseReporter{num: 7, description: "provider-metadata.json"}}, from, to := req[0]-1, req[1]-1
&securityReporter{baseReporter{num: 8, description: "security.txt"}}, for i := from; i <= to; i++ {
&wellknownMetadataReporter{baseReporter{num: 9, description: "/.well-known/csaf/provider-metadata.json"}}, if rep := reporters[i]; rep != nil {
&dnsPathReporter{baseReporter{num: 10, description: "DNS path"}}, reps = append(reps, rep)
&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"}},
} }
return reps
} }
// run uses a processor to check all the given domains or direct urls // 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 return nil, err
} }
defer p.close() defer p.close()
return p.run(buildReporters(), domains) return p.run(domains)
} }
func main() { func main() {

View file

@ -223,7 +223,7 @@ func (p *processor) clean() {
// run calls checkDomain function for each domain in the given "domains" parameter. // 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. // 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. // 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{ report := Report{
Date: ReportTime{Time: time.Now().UTC()}, Date: ReportTime{Time: time.Now().UTC()},
@ -231,19 +231,24 @@ func (p *processor) run(reporters []reporter, domains []string) (*Report, error)
} }
for _, d := range domains { for _, d := range domains {
if err := p.checkDomain(d); err != nil { if p.checkProviderMetadata(d) {
if err == errContinue || err == errStop { if err := p.checkDomain(d); err != nil {
continue if err == errContinue || err == errStop {
continue
}
return nil, err
} }
return nil, err
} }
domain := &Domain{Name: d} domain := &Domain{Name: d}
for _, r := range reporters {
r.report(p, domain)
}
if err := p.fillMeta(domain); err != nil { if err := p.fillMeta(domain); err != nil {
log.Printf("Filling meta data failed: %v\n", err) 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) 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://") direct := strings.HasPrefix(domain, "https://")
checks := []func(*processor, string) error{ checks := []func(*processor, string) error{
(*processor).checkProviderMetadata,
(*processor).checkPGPKeys, (*processor).checkPGPKeys,
} }
@ -1107,8 +1111,7 @@ func (p *processor) checkListing(string) error {
// decodes, and validates against the JSON schema. // decodes, and validates against the JSON schema.
// According to the result, the respective error messages added to // According to the result, the respective error messages added to
// badProviderMetadata. // badProviderMetadata.
// It returns nil if all checks are passed. func (p *processor) checkProviderMetadata(domain string) bool {
func (p *processor) checkProviderMetadata(domain string) error {
p.badProviderMetadata.use() p.badProviderMetadata.use()
@ -1123,14 +1126,14 @@ func (p *processor) checkProviderMetadata(domain string) error {
if !lpmd.Valid() { if !lpmd.Valid() {
p.badProviderMetadata.error("No valid provider-metadata.json found.") p.badProviderMetadata.error("No valid provider-metadata.json found.")
p.badProviderMetadata.error("STOPPING here - cannot perform other checks.") p.badProviderMetadata.error("STOPPING here - cannot perform other checks.")
return errStop return false
} }
p.pmdURL = lpmd.URL p.pmdURL = lpmd.URL
p.pmd256 = lpmd.Hash p.pmd256 = lpmd.Hash
p.pmd = lpmd.Document p.pmd = lpmd.Document
return nil return true
} }
// checkSecurity checks the security.txt file by making HTTP request to fetch it. // checkSecurity checks the security.txt file by making HTTP request to fetch it.