From e2ab1903e745f3b6642ff97f22457d87f3b4aeb3 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Sun, 12 Nov 2023 10:17:28 +0100 Subject: [PATCH 1/8] Support legacy security.txt location as fallback. --- csaf/providermetaloader.go | 100 ++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 47 deletions(-) diff --git a/csaf/providermetaloader.go b/csaf/providermetaloader.go index 4e4eb49..62e8876 100644 --- a/csaf/providermetaloader.go +++ b/csaf/providermetaloader.go @@ -132,8 +132,7 @@ func (pmdl *ProviderMetadataLoader) Load(domain string) *LoadedProviderMetadata } // Next load the PMDs from security.txt - secURL := "https://" + domain + "/.well-known/security.txt" - secResults := pmdl.loadFromSecurity(secURL) + secResults := pmdl.loadFromSecurity(domain) // Filter out the results which are valid. var secGoods []*LoadedProviderMetadata @@ -199,56 +198,63 @@ func (pmdl *ProviderMetadataLoader) Load(domain string) *LoadedProviderMetadata } // loadFromSecurity loads the PMDs mentioned in the security.txt. -func (pmdl *ProviderMetadataLoader) loadFromSecurity(path string) []*LoadedProviderMetadata { +func (pmdl *ProviderMetadataLoader) loadFromSecurity(domain string) []*LoadedProviderMetadata { - res, err := pmdl.client.Get(path) - if err != nil { - pmdl.messages.Add( - HTTPFailed, - fmt.Sprintf("Fetching %q failed: %v", path, err)) - return nil - } - if res.StatusCode != http.StatusOK { - pmdl.messages.Add( - HTTPFailed, - fmt.Sprintf("Fetching %q failed: %s (%d)", path, res.Status, res.StatusCode)) - return nil - } - - // Extract all potential URLs from CSAF. - urls, err := func() ([]string, error) { - defer res.Body.Close() - return ExtractProviderURL(res.Body, true) - }() - - if err != nil { - pmdl.messages.Add( - HTTPFailed, - fmt.Sprintf("Loading %q failed: %v", path, err)) - return nil - } - - var loaded []*LoadedProviderMetadata - - // Load the URLs -nextURL: - for _, url := range urls { - lpmd := pmdl.loadFromURL(url) - // If loading failed note it down. - if !lpmd.Valid() { - pmdl.messages.AppendUnique(lpmd.Messages) + // If .well-known fails try legacy location. + for _, path := range []string{ + "https://" + domain + "/.well-known/security.txt", + "https://" + domain + "/security.txt", + } { + res, err := pmdl.client.Get(path) + if err != nil { + pmdl.messages.Add( + HTTPFailed, + fmt.Sprintf("Fetching %q failed: %v", path, err)) continue } - // Check for duplicates - for _, l := range loaded { - if l == lpmd { - continue nextURL - } + if res.StatusCode != http.StatusOK { + pmdl.messages.Add( + HTTPFailed, + fmt.Sprintf("Fetching %q failed: %s (%d)", path, res.Status, res.StatusCode)) + continue } - loaded = append(loaded, lpmd) - } - return loaded + // Extract all potential URLs from CSAF. + urls, err := func() ([]string, error) { + defer res.Body.Close() + return ExtractProviderURL(res.Body, true) + }() + + if err != nil { + pmdl.messages.Add( + HTTPFailed, + fmt.Sprintf("Loading %q failed: %v", path, err)) + continue + } + + var loaded []*LoadedProviderMetadata + + // Load the URLs + nextURL: + for _, url := range urls { + lpmd := pmdl.loadFromURL(url) + // If loading failed note it down. + if !lpmd.Valid() { + pmdl.messages.AppendUnique(lpmd.Messages) + continue + } + // Check for duplicates + for _, l := range loaded { + if l == lpmd { + continue nextURL + } + } + loaded = append(loaded, lpmd) + } + + return loaded + } + return nil } // loadFromURL loads a provider metadata from a given URL. From 0a2b69bd5510ec7b4f6d9489a9b1b1590dd71226 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 13 Nov 2023 09:59:12 +0100 Subject: [PATCH 2/8] Adjust checker, too. --- cmd/csaf_checker/processor.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 8eb6404..39bd141 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1263,9 +1263,26 @@ func (p *processor) checkProviderMetadata(domain string) bool { // the value of this field. Returns an empty string if no error was encountered, // the errormessage otherwise. func (p *processor) checkSecurity(domain string) string { + var msgs []string + // Try well-known first and fall back to legacy when it fails. + for _, folder := range []string{ + "https://" + domain + "/.well-known/", + "https://" + domain + "/", + } { + msg := p.checkSecurityFolder(folder) + if msg == "" { + break + } + msgs = append(msgs, msg) + } + return strings.Join(msgs, "; ") +} + +// checkSecurityFolder checks the security.txt in a given folder. +func (p *processor) checkSecurityFolder(folder string) string { client := p.httpClient() - path := "https://" + domain + "/.well-known/security.txt" + path := folder + "security.txt" res, err := client.Get(path) if err != nil { return fmt.Sprintf("Fetching %s failed: %v", path, err) @@ -1298,7 +1315,7 @@ func (p *processor) checkSecurity(domain string) string { return fmt.Sprintf("CSAF URL '%s' invalid: %v", u, err) } - base, err := url.Parse("https://" + domain + "/.well-known/") + base, err := url.Parse(folder) if err != nil { return err.Error() } From e27d64e42c09445d0037a2c44de51e05bb7e6a11 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Tue, 14 Nov 2023 07:55:53 +0100 Subject: [PATCH 3/8] Add path of offending security.txt to error message since now multiple paths are checked --- cmd/csaf_checker/processor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 39bd141..f32c618 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1273,7 +1273,9 @@ func (p *processor) checkSecurity(domain string) string { if msg == "" { break } - msgs = append(msgs, msg) + // Show which security.txt caused this message + lmsg := folder + "security.txt:" + msg + msgs = append(msgs, lmsg) } return strings.Join(msgs, "; ") } From 3935d9aa7ae13b693a62c8221b88a89892eb8168 Mon Sep 17 00:00:00 2001 From: "Sascha L. Teichmann" Date: Mon, 20 Nov 2023 21:53:51 +0100 Subject: [PATCH 4/8] Update cmd/csaf_checker/processor.go Co-authored-by: tschmidtb51 <65305130+tschmidtb51@users.noreply.github.com> --- cmd/csaf_checker/processor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index f32c618..304d68f 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1274,7 +1274,7 @@ func (p *processor) checkSecurity(domain string) string { break } // Show which security.txt caused this message - lmsg := folder + "security.txt:" + msg + lmsg := folder + "security.txt: " + msg msgs = append(msgs, lmsg) } return strings.Join(msgs, "; ") From 318c898a83e2f261b4c66961404110e45c3508d3 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Tue, 21 Nov 2023 12:09:37 +0100 Subject: [PATCH 5/8] Change: cmd/csaf_checker/processor.go: Seperate check of security.txt under .well-known and legacy location into different messages to improve readability --- cmd/csaf_checker/processor.go | 55 +++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 304d68f..ac9cca4 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1262,22 +1262,20 @@ func (p *processor) checkProviderMetadata(domain string) bool { // It checks the existence of the CSAF field in the file content and tries to fetch // the value of this field. Returns an empty string if no error was encountered, // the errormessage otherwise. -func (p *processor) checkSecurity(domain string) string { - var msgs []string - // Try well-known first and fall back to legacy when it fails. - for _, folder := range []string{ - "https://" + domain + "/.well-known/", - "https://" + domain + "/", - } { - msg := p.checkSecurityFolder(folder) - if msg == "" { - break - } - // Show which security.txt caused this message - lmsg := folder + "security.txt: " + msg - msgs = append(msgs, lmsg) +func (p *processor) checkSecurity(domain string, legacy bool) (int, string) { + folder := "https://" + domain + "/" + if !legacy { + folder = folder + ".well-known/" } - return strings.Join(msgs, "; ") + msg := p.checkSecurityFolder(folder) + if msg == "" { + if !legacy { + return 0, "Found valid security.txt within the well-known directory" + } else { + return 2, "Found valid security.txt in the legacy location" + } + } + return 1, folder + "security.txt: " + msg } // checkSecurityFolder checks the security.txt in a given folder. @@ -1410,7 +1408,13 @@ func (p *processor) checkWellknown(domain string) string { func (p *processor) checkWellknownSecurityDNS(domain string) error { warningsW := p.checkWellknown(domain) - warningsS := p.checkSecurity(domain) + // Security check for well known (default) and legacy location + warningsS, sDMessage := p.checkSecurity(domain, false) + // if the security.txt under .well-known was not okay + sLMessage := "" + if warningsS == 1 { + warningsS, sLMessage = p.checkSecurity(domain, true) + } warningsD := p.checkDNS(domain) p.badWellknownMetadata.use() @@ -1418,17 +1422,30 @@ func (p *processor) checkWellknownSecurityDNS(domain string) error { p.badDNSPath.use() var kind MessageType - if warningsS == "" || warningsD == "" || warningsW == "" { + if warningsS != 1 || warningsD == "" || warningsW == "" { kind = WarnType } else { kind = ErrorType } + // Info, Warning or Error depending on kind and warningS + kindSD := kind + if warningsS == 0 { + kindSD = InfoType + } + kindSL := kind + if warningsS == 2 { + kindSL = InfoType + } + if warningsW != "" { p.badWellknownMetadata.add(kind, warningsW) } - if warningsS != "" { - p.badSecurity.add(kind, warningsS) + p.badSecurity.add(kindSD, sDMessage) + // only if the well-known security.txt was not successful: + // report about the legacy location + if warningsS != 0 { + p.badSecurity.add(kindSL, sLMessage) } if warningsD != "" { p.badDNSPath.add(kind, warningsD) From 4a9f8a6f031240cd50b12a4fbff4526cfa9dc792 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Tue, 21 Nov 2023 12:14:45 +0100 Subject: [PATCH 6/8] Change: cmd/csaf_checker/processor.go: Improve comment --- cmd/csaf_checker/processor.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index ac9cca4..2e61ae8 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1411,6 +1411,7 @@ func (p *processor) checkWellknownSecurityDNS(domain string) error { // Security check for well known (default) and legacy location warningsS, sDMessage := p.checkSecurity(domain, false) // if the security.txt under .well-known was not okay + // check for a security.txt within its legacy location sLMessage := "" if warningsS == 1 { warningsS, sLMessage = p.checkSecurity(domain, true) From fb7c77b419099c8c215f1d0c432b6417b41dbcc4 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Tue, 21 Nov 2023 13:45:46 +0100 Subject: [PATCH 7/8] Remove unnecessary else block --- cmd/csaf_checker/processor.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/csaf_checker/processor.go b/cmd/csaf_checker/processor.go index 2e61ae8..2a5161c 100644 --- a/cmd/csaf_checker/processor.go +++ b/cmd/csaf_checker/processor.go @@ -1271,9 +1271,8 @@ func (p *processor) checkSecurity(domain string, legacy bool) (int, string) { if msg == "" { if !legacy { return 0, "Found valid security.txt within the well-known directory" - } else { - return 2, "Found valid security.txt in the legacy location" } + return 2, "Found valid security.txt in the legacy location" } return 1, folder + "security.txt: " + msg } From a6bf44f7cced151123b1b47111d958276e1a40c7 Mon Sep 17 00:00:00 2001 From: JanHoefelmeyer Date: Wed, 22 Nov 2023 08:17:05 +0100 Subject: [PATCH 8/8] Removed impossible to achieve condition in reporters --- cmd/csaf_checker/reporters.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/csaf_checker/reporters.go b/cmd/csaf_checker/reporters.go index 51731e1..c707a14 100644 --- a/cmd/csaf_checker/reporters.go +++ b/cmd/csaf_checker/reporters.go @@ -251,10 +251,6 @@ func (r *securityReporter) report(p *processor, domain *Domain) { req.message(WarnType, "Performed no in-depth test of security.txt.") return } - if len(p.badSecurity) == 0 { - req.message(InfoType, "Found CSAF entry in security.txt.") - return - } req.Messages = p.badSecurity }