diff --git a/scubagoggles/Testing/Unit/Rego/gmail/gmail03_test.rego b/scubagoggles/Testing/Unit/Rego/gmail/gmail03_test.rego index 0be880403..f5dce019c 100644 --- a/scubagoggles/Testing/Unit/Rego/gmail/gmail03_test.rego +++ b/scubagoggles/Testing/Unit/Rego/gmail/gmail03_test.rego @@ -152,4 +152,24 @@ test_SPF_Incorrect_V3 if { not RuleOutput[0].NoSuchEvent RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) } + +test_SPF_Incorrect_V4 if { + # Fail if multiple records + PolicyId := GmailId3_1 + Output := tests with input as { + "spf_records": [ + { + "domain": "test.name", + "rdata": ["v=spf1 include:_spf.google.com -all", "v=spf1 include:_spf.google.com -all"] + } + ], + "domains": ["test.name"] + } + + RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] + count(RuleOutput) == 1 + not RuleOutput[0].RequirementMet + not RuleOutput[0].NoSuchEvent + RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) +} #-- diff --git a/scubagoggles/Testing/Unit/Rego/gmail/gmail04_test.rego b/scubagoggles/Testing/Unit/Rego/gmail/gmail04_test.rego index d1ab62fe6..e7672fbf1 100644 --- a/scubagoggles/Testing/Unit/Rego/gmail/gmail04_test.rego +++ b/scubagoggles/Testing/Unit/Rego/gmail/gmail04_test.rego @@ -1,6 +1,7 @@ package gmail import future.keywords +MultipleWarning := "1 domain(s) have multiple DMARC records: test.name." # # GWS.GMAIL.4.1 #-- @@ -101,6 +102,30 @@ test_DMARC_Incorrect_V2 if { RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) } +test_DMARC_Incorrect_V3 if { + # Test DMARC when there are multiple dmarc records + PolicyId := GmailId4_1 + Output := tests with input as { + "dmarc_records": [ + { + "domain": "test.name", + "rdata": [ + "v=DMARC1; p=reject; pct=100; rua=mailto:DMARC@hq.dhs.gov, mailto:reports@dmarc.cyber.dhs.gov", + "v=DMARC1; p=reject" + ] + } + ], + "domains": ["test.name"] + } + + RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] + count(RuleOutput) == 1 + not RuleOutput[0].RequirementMet + not RuleOutput[0].NoSuchEvent + RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", + MultipleWarning, DNSLink]) +} + # # GWS.GMAIL.4.2 #-- @@ -201,6 +226,30 @@ test_DMARCMessageReject_Incorrect_V2 if { RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) } +test_DMARCMessageReject_Incorrect_V3 if { + # Test DMARC when there are multiple dmarc records + PolicyId := GmailId4_2 + Output := tests with input as { + "dmarc_records": [ + { + "domain": "test.name", + "rdata": [ + "v=DMARC1; p=reject; pct=100; rua=mailto:DMARC@hq.dhs.gov, mailto:reports@dmarc.cyber.dhs.gov", + "v=DMARC1; p=reject" + ] + } + ], + "domains": ["test.name"] + } + + RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] + count(RuleOutput) == 1 + not RuleOutput[0].RequirementMet + not RuleOutput[0].NoSuchEvent + RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", + MultipleWarning, DNSLink]) +} + # # GWS.GMAIL.4.3 #-- @@ -301,6 +350,31 @@ test_DMARCAggregateReports_Incorrect_V2 if { RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) } +test_DMARCAggregateReports_Incorrect_V3 if { + # Test DMARC when there are multiple dmarc records + PolicyId := GmailId4_3 + Output := tests with input as { + "dmarc_records": [ + { + "domain": "test.name", + "rdata": [ + "v=DMARC1; p=reject; pct=100; rua=mailto:DMARC@hq.dhs.gov, mailto:reports@dmarc.cyber.dhs.gov", + "v=DMARC1; p=reject" + ] + } + ], + "domains": ["test.name"] + } + + RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] + count(RuleOutput) == 1 + not RuleOutput[0].RequirementMet + not RuleOutput[0].NoSuchEvent + RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", + MultipleWarning, DNSLink]) +} + + # # GWS.GMAIL.4.4 #-- @@ -400,4 +474,29 @@ test_DMARCAgencyPOC_Incorrect_V2 if { not RuleOutput[0].NoSuchEvent RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", DNSLink]) } + +test_DMARCAgencyPOC_Incorrect_V3 if { + # Test DMARC when there are multiple dmarc records + PolicyId := GmailId4_4 + Output := tests with input as { + "dmarc_records": [ + { + "domain": "test.name", + "rdata": [ + "v=DMARC1; p=reject; pct=100; rua=mailto:DMARC@hq.dhs.gov, mailto:reports@dmarc.cyber.dhs.gov", + "v=DMARC1; p=reject" + ] + } + ], + "domains": ["test.name"] + } + + RuleOutput := [Result | some Result in Output; Result.PolicyId == PolicyId] + count(RuleOutput) == 1 + not RuleOutput[0].RequirementMet + not RuleOutput[0].NoSuchEvent + RuleOutput[0].ReportDetails == concat(" ", ["1 of 1 agency domain(s) found in violation: test.name.", + MultipleWarning, + DNSLink]) +} #-- diff --git a/scubagoggles/rego/Gmail.rego b/scubagoggles/rego/Gmail.rego index 14a4d0e2b..d5e994d09 100644 --- a/scubagoggles/rego/Gmail.rego +++ b/scubagoggles/rego/Gmail.rego @@ -116,6 +116,8 @@ GmailId3_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.3.1") # Not applicable at OU or Group level DomainsWithSpf contains SpfRecord.domain if { some SpfRecord in input.spf_records + # Ensure that there's only 1 SPF record + count([Answer | some Answer in SpfRecord.rdata; startswith(Answer, "v=spf1")]) == 1 some Rdata in SpfRecord.rdata startswith(Rdata, "v=spf1 ") # Ensure that the policy either ends with "-all", "~all", or directs to a different SPF policy @@ -155,8 +157,26 @@ GmailId4_1 := utils.PolicyIdWithSuffix("GWS.GMAIL.4.1") # Not applicable at OU or Group level DomainsWithDmarc contains DmarcRecord.domain if { some DmarcRecord in input.dmarc_records - some Rdata in DmarcRecord.rdata - startswith(Rdata, "v=DMARC1;") + ValidAnswers := [Answer | some Answer in DmarcRecord.rdata; startswith(Answer, "v=DMARC1;")] + count(ValidAnswers) == 1 +} + +DomainsWithMultipleDmarc contains DmarcRecord.domain if { + some DmarcRecord in input.dmarc_records + ValidAnswers := [Answer | some Answer in DmarcRecord.rdata; startswith(Answer, "v=DMARC1;")] + count(ValidAnswers) > 1 +} + +MultiDmarcWarning := " " if { count(DomainsWithMultipleDmarc) == 0 } +MultiDmarcWarning := Warning if { + count(DomainsWithMultipleDmarc) > 0 + Warning := concat("", [ + " ", + format_int(count(DomainsWithMultipleDmarc), 10), + " domain(s) have multiple DMARC records: ", + concat(", ", DomainsWithMultipleDmarc), + ". " + ]) } tests contains { @@ -167,7 +187,10 @@ tests contains { "get_dmarc_records" ], "Criticality": "Shall", - "ReportDetails": concat(" ", [ReportDetailsArray(Status, DomainsWithoutDmarc, AllDomains), DNSLink]), + "ReportDetails": concat("", [ + ReportDetailsArray(Status, DomainsWithoutDmarc, AllDomains), + MultiDmarcWarning, + DNSLink]), "ActualValue": input.dmarc_records, "RequirementMet": Status, "NoSuchEvent": false @@ -189,13 +212,18 @@ DomainsWithPreject contains DmarcRecord.domain if { some DmarcRecord in input.dmarc_records some Rdata in DmarcRecord.rdata contains(Rdata, "p=reject;") + DmarcRecord.domain in DomainsWithDmarc } tests contains { "PolicyId": GmailId4_2, "Prerequisites": ["directory/v1/domains/list", "get_dmarc_records"], "Criticality": "Shall", - "ReportDetails": concat(" ", [ReportDetailsArray(Status, DomainsWithoutPreject, AllDomains), DNSLink]), + "ReportDetails": concat("", [ + ReportDetailsArray(Status, DomainsWithoutPreject, AllDomains), + MultiDmarcWarning, + DNSLink + ]), "ActualValue": input.dmarc_records, "RequirementMet": Status, "NoSuchEvent": false @@ -217,13 +245,18 @@ DomainsWithDHSContact contains DmarcRecord.domain if { some DmarcRecord in input.dmarc_records some Rdata in DmarcRecord.rdata contains(Rdata, "mailto:reports@dmarc.cyber.dhs.gov") + DmarcRecord.domain in DomainsWithDmarc } tests contains { "PolicyId": GmailId4_3, "Prerequisites": ["directory/v1/domains/list", "get_dmarc_records"], "Criticality": "Shall", - "ReportDetails": concat(" ", [ReportDetailsArray(Status, DomainsWithoutDHSContact, AllDomains), DNSLink]), + "ReportDetails": concat("", [ + ReportDetailsArray(Status, DomainsWithoutDHSContact, AllDomains), + MultiDmarcWarning, + DNSLink + ]), "ActualValue": input.dmarc_records, "RequirementMet": Status, "NoSuchEvent": false @@ -245,13 +278,18 @@ DomainsWithAgencyContact contains DmarcRecord.domain if { some DmarcRecord in input.dmarc_records some Rdata in DmarcRecord.rdata count(split(Rdata, "@")) >= 3 + DmarcRecord.domain in DomainsWithDmarc } tests contains { "PolicyId": GmailId4_4, "Prerequisites": ["directory/v1/domains/list", "get_dmarc_records"], "Criticality": "Should", - "ReportDetails": concat(" ", [ReportDetailsArray(Status, DomainsWithoutAgencyContact, AllDomains), DNSLink]), + "ReportDetails": concat("", [ + ReportDetailsArray(Status, DomainsWithoutAgencyContact, AllDomains), + MultiDmarcWarning, + DNSLink + ]), "ActualValue": input.dmarc_records, "RequirementMet": Status, "NoSuchEvent": false diff --git a/scubagoggles/reporter/reporter.py b/scubagoggles/reporter/reporter.py index a3f1a1e8f..378904f51 100644 --- a/scubagoggles/reporter/reporter.py +++ b/scubagoggles/reporter/reporter.py @@ -875,7 +875,7 @@ def _build_report_html(self, "Query Name": qname, "Query Method": query['query_method'], "Summary": query['query_result'], - "Answers": '\n'.join(answers) + "Answers": '
'.join(answers) }) log_table = self.create_html_table(logs) log_table = log_table.replace("", "
")