diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 3232bb5..36a22a4 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -40,10 +40,11 @@ import ( type topicMessages []Message type processor struct { - opts *options - validator csaf.RemoteValidator - client util.Client - ageAccept func(time.Time) bool + opts *options + validator csaf.RemoteValidator + client util.Client + unauthClient util.Client + ageAccept func(time.Time) bool redirects map[string][]string noneTLS util.Set[string] @@ -462,15 +463,27 @@ func (p *processor) basicClient() *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 + if p.client == nil { + p.client = p.fullClient() } - - p.client = p.fullClient() return p.client } +// unauthorizedClient returns a cached HTTP client without +// authentification. +func (p *processor) unauthorizedClient() util.Client { + if p.unauthClient == nil { + p.unauthClient = p.basicClient() + } + return p.unauthClient +} + +// usedAuthorizedClient tells if an authorized client is used +// for downloading. +func (p *processor) usedAuthorizedClient() bool { + return p.opts.protectedAccess() +} + // rolieFeedEntries loads the references to the advisory files for a given feed. func (p *processor) rolieFeedEntries(feed string) ([]csaf.AdvisoryFile, error) { diff --git a/cmd/csaf_checker/roliecheck.go b/cmd/csaf_checker/roliecheck.go index 59dc3f5..4a27fdc 100644 --- a/cmd/csaf_checker/roliecheck.go +++ b/cmd/csaf_checker/roliecheck.go @@ -25,7 +25,6 @@ type rolieLabelChecker struct { feedLabel csaf.TLPLabel advisories map[csaf.TLPLabel]util.Set[string] - openClient util.Client } // tlpLevel returns an inclusion order of TLP colors. @@ -53,39 +52,103 @@ func tlpLabel(label *csaf.TLPLabel) csaf.TLPLabel { return csaf.TLPLabelUnlabeled } -// check tests if in advisory is in the right TLP color of the -// currently tested feed. -func (ca *rolieLabelChecker) check( - p *processor, - advisoryLabel csaf.TLPLabel, - advisory string, -) { - // Assign int to tlp levels for easy comparison - var ( - advisoryRank = tlpLevel(advisoryLabel) - feedRank = tlpLevel(ca.feedLabel) - ) - - // Associate advisory label to urls. - advs := ca.advisories[advisoryLabel] +// add registers a given url to a label. +func (ca *rolieLabelChecker) add(label csaf.TLPLabel, url string) { + advs := ca.advisories[label] if advs == nil { advs = util.Set[string]{} - ca.advisories[advisoryLabel] = advs + ca.advisories[label] = advs } - advs.Add(advisory) + advs.Add(url) +} - // If entry shows up in feed of higher tlp level, - // give out info or warning +// check tests if the TLP label of an advisory is used correctly. +func (ca *rolieLabelChecker) check( + p *processor, + label csaf.TLPLabel, + url string, +) { + // Associate advisory label to urls. + ca.add(label, url) + + // If entry shows up in feed of higher tlp level, give out info or warning. + ca.checkRank(p, label, url) + + // Issue warnings or errors if the advisory is not protected properly. + ca.checkProtection(p, label, url) +} + +// checkProtection tests if a given advisory has the right level +// of protection. +func (ca *rolieLabelChecker) checkProtection( + p *processor, + label csaf.TLPLabel, + url string, +) { 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 label == csaf.TLPLabelWhite: + p.badWhitePermissions.use() + // We only need to download it with an unauthorized client + // if have not done it yet. + if p.usedAuthorizedClient() { + res, err := p.unauthorizedClient().Get(url) + if err != nil { + p.badWhitePermissions.error( + "Unexpected Error %v when trying to fetch: %s", err, url) + } else if res.StatusCode == http.StatusForbidden { + p.badWhitePermissions.error( + "Advisory %s of TLP level WHITE is access protected.", url) + } + } + + // 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 label >= csaf.TLPLabelAmber: + p.badAmberRedPermissions.use() + // It is an error if we downloaded the advisory with + // an unauthorized client. + if !p.usedAuthorizedClient() { + p.badAmberRedPermissions.error( + "Advisory %s of TLP level %v is not properly access protected.", + url, label) + } else { + // We came here by an authorized download which is okay. + // So its bad if we can download it with an unauthorized client, too. + res, err := p.unauthorizedClient().Get(url) + if err != nil { + p.badAmberRedPermissions.error( + "Unexpected Error %v when trying to fetch: %s", err, url) + } else if res.StatusCode == http.StatusOK { + p.badAmberRedPermissions.error( + "Advisory %s of TLP level %v is not properly access protected.", + url, label) + } + } + } +} + +// checkRank tests if a given advisory is contained by the +// the right feed color. +func (ca *rolieLabelChecker) checkRank( + p *processor, + label csaf.TLPLabel, + url string, +) { + switch advisoryRank, feedRank := tlpLevel(label), tlpLevel(ca.feedLabel); { + case advisoryRank < feedRank: if advisoryRank == 0 { // All kinds of 'UNLABELED' p.badROLIEFeed.info( "Found unlabeled advisory %q in feed %q.", - advisory, ca.feedURL) + url, ca.feedURL) } else { p.badROLIEFeed.warn( "Found advisory %q labled TLP:%s in feed %q (TLP:%s).", - advisory, advisoryLabel, + url, label, ca.feedURL, ca.feedLabel) } @@ -93,43 +156,7 @@ func (ca *rolieLabelChecker) check( // Must not happen, give 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) - } - - // 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 advisoryLabel == 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 advisoryLabel >= 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) - - } - } + url, label, ca.feedURL, ca.feedLabel) } } @@ -211,16 +238,6 @@ 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 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.