control 'microsoft-365-foundations-2.1.10' do
  title 'Ensure DMARC Records for all Exchange Online domains are published'
  desc 'DMARC, or Domain-based Message Authentication, Reporting, and Conformance, assists recipient mail systems in determining the appropriate action to take when messages from a domain fail to meet SPF or DKIM authentication criteria.'

  desc 'check',
       "Ensure DMARC Records for all Exchange Online domains are published:
        1.Open a command prompt.
        2.For each of the Accepted Domains in Exchange Online run the following in PowerShell:
            Resolve-DnsName _dmarc.[domain1.com] txt
        3.Ensure that the record exists and has at minimum the following flags defined as follows: v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:<reporting email address> and ruf=mailto:<reporting email address>
    The below example records would pass as they contain a policy that would either quarantine or reject messages failing DMARC, the policy affects 100% of mail pct=100 as well as containing valid reporting addresses:
            v=DMARC1; p=reject; pct=100; rua=mailto:rua@contoso.com;
            ruf=mailto:ruf@contoso.com; fo=1
            v=DMARC1; p=reject; pct=100; fo=1; ri=3600; rua=mailto:rua@contoso.com;
            ruf=mailto:ruf@contoso.com
            v=DMARC1; p=quarantine; pct=100; sp=none; fo=1; ri=3600; rua=mailto:rua@contoso.com;
            ruf=ruf@contoso.com;
        4.Ensure the Microsoft MOERA domain is also configured.
            Resolve-DnsName _dmarc.[tenant].onmicrosoft.com txt
        5.Ensure the record meets the same criteria listed in step #3."

  desc 'fix',
       "To add DMARC records, use the following steps:
        1.For each Exchange Online Accepted Domain, add the following record to DNS: Record: _dmarc.domain1.com Type: TXT Value: v=DMARC1; p=none; rua=mailto:<rua-report@example.com>; ruf=mailto:<ruf-report@example.com>
        2.This will create a basic DMARC policy that will allow the organization to start monitoring message statistics.
        3.The next steps will involve first implementing quarantine and next a reject policy with 100 percent of email is affected. Microsoft has a list of best practices for implementing DMARC that cover these steps in detail.
    To establish a DMARC record for the MOERA domain:
        1.Navigate to the Microsoft 365 admin center https://admin.microsoft.com/
        2.Expand Settings and select Domains.
        3.Select your tenant domain (for example, contoso.onmicrosoft.com).
        4.Select DNS records and click + Add record.
        5.Add a new record with the TXT name of _dmarc with the appropriate values outlined above.
    Note: The remediation portion involves a multi-staged approach over a period of time. First, a baseline of the current state of email will be established with p=none and rua and ruf. Once the environment is better understood and reports have been analyzed an organization will move to the final state with dmarc record values as outlined in the audit section."

  desc 'rationale',
       "DMARC strengthens the trustworthiness of messages sent from an organization's
        domain to destination email systems. By integrating DMARC with SPF (Sender Policy
        Framework) and DKIM (DomainKeys Identified Mail), organizations can significantly
        enhance their defenses against email spoofing and phishing attempts."

  impact 0.5
  tag severity: 'medium'
  tag cis_controls: [{ '8' => ['9.5'] }, { '7' => ['7.8'] }]
  tag nist: ['SC-7']

  ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/email-authentication-dmarc-configure?view=o365-worldwide'
  ref 'https://learn.microsoft.com/en-us/microsoft-365/security/office-365-security/step-by-step-guides/how-to-enable-dmarc-reporting-for-microsoft-online-email-routing-address-moera-and-parked-domains?view=o365-worldwide'

  dmarc_domain_list = %("#{input('dmarc_domains').sort.join('", "')}")
  check_dmarc_domain_script = %{
      $domains = (#{dmarc_domain_list})
      try{
        Import-Module DNSClient -ErrorAction Stop
      }
      catch{
      }
      foreach ($domain in $domains) {
        try {
            $dmarcRecord = Resolve-DnsName -Name "_dmarc.$domain" -Type TXT -ErrorAction Stop
            $dmarcText = $dmarcRecord.Strings -join ";"

            if ($dmarcText.Contains("v=DMARC1") -and
                ($dmarcText.Contains("p=quarantine") -or $dmarcText.Contains("p=reject")) -and
                $dmarcText.Contains("pct=100") -and
                $dmarcText.Contains("rua=mailto:#{input('reporting_mail_address')}") -and
                $dmarcText.Contains("ruf=mailto:#{input('reporting_mail_address')}")) {
                Write-Output "DMARC record for $domain is valid."
            } else {
                Write-Output "DMARC record for $domain is invalid or missing required flags."
            }
        } catch {
            Write-Output "No DMARC record found for $domain."
        }
    }
    }
  powershell_output_dmarc = pwsh_single_session_executor(check_dmarc_domain_script).run_script_in_graph_exchange
  raise Inspec::Error, "The powershell output returned the following error:  #{powershell_output_dmarc.stderr}" if powershell_output_dmarc.exit_status != 0

  describe "Ensure the number of DMARC domains that do not contain a record or does not contain the following substring in the record v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}" do
    subject { powershell_output_dmarc.stdout.strip }
    it 'is 0' do
      failure_message = "The following DMARC domains have failed: #{powershell_output_dmarc.stdout.strip.split("\n").join(',')}"
      expect(subject).to be_empty, failure_message
    end
  end
  check_moera_domain_script = %{
      try{
        Import-Module DNSClient -ErrorAction Stop
      }
      catch{

      }
      $domain = "_dmarc.$tenantid.onmicrosoft.com"
      try {
          $moeraRecord = Resolve-DnsName -Name $domain -Type TXT -ErrorAction Stop
          $moeraText = $moeraRecord.Strings -join ";"

          if ($moeraText.Contains("v=DMARC1") -and
              ($moeraText.Contains("p=quarantine") -or $moeraText.Contains("p=reject")) -and
              $moeraText.Contains("pct=100") -and
              $moeraText.Contains("rua=mailto:#{input('reporting_mail_address')}") -and
              $moeraText.Contains("ruf=mailto:#{input('reporting_mail_address')}")) {
              Write-Output "MOERA record for $domain is valid."
          } else {
              Write-Output "MOERA record for $domain is invalid or missing required flags."
          }
      } catch {
          Write-Output "No MOERA record found for $domain."
      }
    }
  powershell_output_moera = pwsh_single_session_executor(check_moera_domain_script).run_script_in_graph_exchange
  raise Inspec::Error, "The powershell output returned the following error:  #{powershell_output_moera.stderr}" if powershell_output_moera.exit_status != 0

  describe "Ensure the number of MOERA domains that do not contain a record or does not contain the following substring in the record v=DMARC1; (p=quarantine OR p=reject), pct=100, rua=mailto:#{input('reporting_mail_address')} and ruf=mailto:#{input('reporting_mail_address')}" do
    subject { powershell_output_moera.stdout.strip }
    it 'is 0' do
      failure_message = "The following MOERA domains have failed: #{powershell_output_moera.stdout.strip.split("\n").join(',')}"
      expect(subject).to be_empty, failure_message
    end
  end
end
