Resources »

Workflows »

DMARC Reports

Introduction

This workflow automatically parses DMARC report attachments in email.

Installation

This workflow is built into Cerb 11.0+. It will automatically update.

You can enable it from Search » Workflows » (+) » DMARC Reports.

Usage

Testing the DMARC report interaction

Navigate to Search » Attachments.

Open the card for the file: customer.example!cerb.example!1695078003!1695164407.xml.gz

Click the Open DMARC Report button at the bottom of the popup.

The DMARC Reporting card widget will only appear on these attachments.

Configure delivery of DMARC reports to Cerb

In the DNS of all sender domains, you should have a DMARC record configured like:



TXT _dmarc.cerb.example
v=DMARC1; p=reject; sp=reject; rf=afrf; adkim=s; aspf=s; pct=100; ri=86400;fo=1;


Add the following options to enable DMARC delivery reports. Replace dmarc-reports@cerb.example with an email address that delivers into Cerb.



rua=mailto:dmarc-reports@cerb.example; ruf=mailto:dmarc-reports@cerb.example;


Reference

You can build your own DMARC reports workflow using this template as a reference.

Change occurrences of cerb.email.dmarc_reports to your own workflow identifier. Use a prefix based on a domain you own (e.g. com.example.workflow).



workflow:
  name: cerb.email.dmarc_reports
  version: 2024-10-14T00:00:00Z
  description: Parse DMARC report attachments in email
  website: https://cerb.ai/workflows/cerb.email.dmarc_reports/
  requirements:
    cerb_version: >=11.0 <11.1
    cerb_plugins: cerberusweb.core

records:
  automation/parse:
    fields:
      name: cerb.email.dmarcReports.parse
      extension_id: cerb.trigger.automation.function
      description: Parse DMARC report attachments
      script@raw:
        inputs:
          record/file:
            record_type: attachment
            required@bool: yes

        start:
          decision/format:
            # .xml.gz
            outcome/gzipXml:
              if@bool: {{inputs.file.name ends with '.xml.gz'}}
              then:
                file.read:
                  inputs:
                    uri: cerb:attachment:{{inputs.file.id}}
                    filters:
                      gzip.decompress:
                  output: file_contents

            # .zip
            outcome/zip:
              then:
                # Look inside the ZIP for the first XML file
                data.query/zip:
                  inputs:
                    query@text:
                      type:attachment.manifest
                      id:{{inputs.file.id}}
                      filter:*.xml
                      limit:1
                      format:dictionaries
                  output: results
                  on_success:
                    outcome/notFound:
                      if@bool: {{1 != results.data.cursor.num_files}}
                      then:
                        return:

                # Extract the XML file from the ZIP
                file.read:
                  inputs:
                    uri: cerb:attachment:{{inputs.file.id}}
                    extract: {{results.data.files[0].name}}
                  output: file_contents
                  on_success:
                    outcome/notXml:
                      # Must be XML
                      if@bool:
                        {{
                          not file_contents.bytes
                          or 'text/xml' != file_contents.mime_type
                        }}
                      then:
                        return:

          set/json:
            file_contents@json: {{xml_decode(file_contents.bytes)|json_encode}}

          outcome/notJson:
            # Must be JSON
            if@bool: {{not file_contents}}
            then:
              return:

          # If there's only one row, make a list for consistency
          outcome/oneRow:
            if@bool: {{file_contents.record.row is not empty}}
            then:
              var.set:
                inputs:
                  key: file_contents:record
                  value:
                    0@key: file_contents:record

          return:
            report@key: file_contents

      policy_kata@raw:
        commands:
          data.query:
            deny/type@bool: {{query.type != 'attachment.manifest'}}
            allow@bool: yes
          file.read:
            deny/uri@bool: {{inputs.uri is not prefixed ('cerb:attachment:')}}
            allow@bool: yes

  automation/interaction:
    fields:
      name: cerb.email.dmarcReports.interaction
      extension_id: cerb.trigger.interaction.worker
      description: Interaction for quick review of DMARC reports
      script@raw:
        inputs:
          record/file:
            required@bool: yes
            record_type: attachment

        start:
          function/dmarc:
            uri: cerb:automation:cerb.email.dmarcReports.parse
            output: results
            inputs:
              file: {{inputs.file.id}}
            on_success:
              await:
                form:
                  title: DMARC results
                  elements:
                    sheet/prompt_sheet:
                      data@key: results:report:record
                      schema:
                        layout:
                        columns:
                          text/ip:
                            params:
                              value_template@raw: {{row.source_ip}}
                          #interaction/ip:
                          #  params:
                          #    uri: cerb:automation:cerb.interaction.locationByIP
                          #    text_template@raw: {{row.source_ip}}
                          #    inputs:
                          #      ip@raw: {{row.source_ip}}
                          text/domain:
                            params:
                              value_template@raw: {{identifiers.header_from}}
                          text/count:
                            params:
                              value_template@raw: {{row.count}}
                          icon/spf:
                            params:
                              image_template@raw: {{'pass' == row.policy_evaluated.spf ? 'circle-ok' : 'remove-2'}}
                          icon/dkim:
                            params:
                              image_template@raw: {{'pass' == row.policy_evaluated.dkim ? 'circle-ok' : 'remove-2'}}
                          text/disposition:
                            params:
                              value_template@raw: {{row.policy_evaluated.disposition}}
      policy_kata@raw:
        commands:
          function:
            deny/uri@bool: {{uri != 'cerb:automation:cerb.email.dmarcReports.parse'}}
            allow@bool: yes

  card_widget/attachment_card:
    fields:
      name: DMARC Reporting
      extension_id: cerb.card.widget.form_interaction
      record_type: attachment
      pos: 100
      width_units: 2
      zone: content
      extension_params:
        is_popup: 0
        interactions_kata@raw:
          interaction/dmarcReport:
            label: Open DMARC Report
            uri: cerb:automation:cerb.email.dmarcReports.interaction
            icon: briefcase
            hidden@bool:
              {{
                not (
                  record_mime_type in ['application/zip','application/gzip','application/octet-stream']
                  and record_name is pattern ('*!*!*.zip','*!*!*.xml.gz')
                )
              }}
            inputs:
              file: {{record_id}}
      options_kata@raw:
        hidden@bool:
          {{
            not (
              record_mime_type in ['application/zip','application/gzip','application/octet-stream']
              and record_name is pattern ('*!*!*.zip','*!*!*.xml.gz')
            )
          }}
  attachment/dmarc_example:
    fields:
      name: customer.example!cerb.example!1695078003!1695164407.xml.gz
      mime_type: application/gzip
      content: data:application/zip;base64,H4sICI+NC2UAA2N1c3RvbWVyLmV4YW1wbGUhY2VyYi5leGFtcGxlITE2OTUwNzgwMDMhMTY5NTE2NDQwNy54bWwA7VbbjtsgFHzfr4jyXl9y21Ri2T71C9pni8BxQtcGBDhN/75gwPYm2TbqbqWqWuUhZs6YOWcYO0GPp7aZHUEbLsXDvMyK+QwElYyL/cP865fPH7bz2SO+QzUA2xH6hO9mMxT52NFRnha+oEFJbasWLGHEEo85VOp9JUgLmHbGyhZ0BifSqgZQPpQCE1rCG8xaoumnS3KoRubJalJRKSyhtuKili/fdkkNe8RuOcOk3C3okq1gXW+u7DASw41uNqg0EfvYt4N2sOfOkM3HdXG/LYolygOS6iBYXy03q1Vx75oSabP8+W6D2tREpGTD6Y9KdbuGmwMMjUhnicAU9G7sNoKBQdgTb7FBebiIoFF1j/nvACms4RtQi3IVETNCJmGKWlwW7tD9Rd/stcacsVTq1KOW3wcXjOw0hYorvMqW2SIr3d4DlEhUdsLLoDxcJTxKwZE0nbOMpYL3gRslDbc+h0IKb8IEmfC8B4oYN/toR5y2joXBk8l4Z5rujNJQiDMQltfcPQXDbQcgDHRVa9menc20MkbjCI1UcI3/vBbFLyQR6eyh0mC6xo5dnE3426wEH6BxRy51z3NmpOXICDLRrLgY/JpqoomTt+tTNy1u/bhOvV/cKD1mOT+3w5NTIm8J5zrrP78I59/KZu3ecO/ZfG02g4v/ZzbLYpEtt9liXfgf37cOaHrj/0lEQ+E9ov96RK9L3xRRlA//A38Cx2teijoKAAA=
      attach@list:
        workflow:{{workflow_id}}