DEV Community

Alex Kane
Alex Kane

Posted on

n8n for DefenseTech SaaS: 5 Automations for CMMC, ITAR, DFARS, and NIST 800-171 (Free Workflow JSON)

If your SaaS runs inside a DoD program, Defense Industrial Base (DIB) supply chain, or prime/sub-contractor environment, compliance isn't a checkbox — it's the cost of keeping your contract.

CMMC 2.0 (32 CFR Part 170), DFARS 252.204-7012, ITAR (22 CFR Parts 120-130), EAR (15 CFR Parts 730-774), and NIST SP 800-171 Rev 3 each come with hard deadlines, audit trails, and incident-reporting clocks that don't care about your sprint schedule.

Here are 5 n8n workflows that keep your DefenseTech ops inside the fence — with full import-ready JSON for each.


Why self-hosted n8n matters for DoD contractors

When you run automations through Zapier or Make.com, CUI (Controlled Unclassified Information) flows through a commercial SaaS environment that is almost certainly not on the DoD Approved Products List and does not satisfy CMMC Level 2 enclave requirements.

Self-hosted n8n on a government-cloud-compliant instance (AWS GovCloud, Azure Government, or on-prem) means:

  • Workflow data never leaves your CUI enclave
  • Workflow JSON files are version-controlled in Git — your CMMC assessor can audit them
  • No monthly per-user pricing that makes compliance tooling cost-prohibitive for small primes

Workflow 1: CMMC Level 2 Evidence Collector

Compliance: CMMC 2.0 Level 2 — 32 CFR Part 170, NIST SP 800-171 Rev 3

This workflow runs daily on weekday mornings. It evaluates the status of key CMMC Level 2 practice controls (AC, IA, AU, CM families), flags any gaps, and posts to your #cmmc-team Slack channel with the specific control IDs and evidence references. All results log to Google Sheets as a running audit trail.

{
  "name": "CMMC Level 2 Evidence Collector",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "id": "aa1b2c3d-0001-0001-0001-000000000001",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const controls = [\n  {id:'AC.L2-3.1.1',name:'Authorized Access Control',status:'PASS',evidence:'AD group audit log attached'},\n  {id:'AC.L2-3.1.2',name:'Transaction and Function Control',status:'PASS',evidence:'RBAC policy v2.3 reviewed'},\n  {id:'IA.L2-3.5.3',name:'Multifactor Authentication',status:'PASS',evidence:'MFA enforced \u2014 Okta log attached'},\n  {id:'AU.L2-3.3.1',name:'Create System Audit Logs',status:'REVIEW',evidence:'Log retention policy \u2014 pending update to 3yr'},\n  {id:'CM.L2-3.4.1',name:'System Baseline Configuration',status:'PASS',evidence:'STIG baseline v2.1 applied'}\n];\nconst gaps = controls.filter(c => c.status !== 'PASS');\nconst dueDate = new Date();\ndueDate.setDate(dueDate.getDate() + 30);\nreturn [{ json: { controls, gaps, gapCount: gaps.length, nextAssessmentDue: dueDate.toISOString().split('T')[0], timestamp: new Date().toISOString() } }];"
      },
      "id": "aa1b2c3d-0001-0001-0001-000000000002",
      "name": "Evaluate Controls",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond1",
              "leftValue": "={{ $json.gapCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "aa1b2c3d-0001-0001-0001-000000000003",
      "name": "IF Gaps Found",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=:warning: CMMC L2 Evidence Gap\n*Gaps found:* {{ $json.gapCount }}\n*Controls needing review:*\n{{ $json.gaps.map(g => `\u2022 ${g.id}: ${g.name} \u2014 ${g.evidence}`).join('\\n') }}\n*Next assessment due:* {{ $json.nextAssessmentDue }}\n*Action:* Update evidence in SharePoint and mark resolved."
            }
          ]
        }
      },
      "id": "aa1b2c3d-0001-0001-0001-000000000004",
      "name": "Slack #cmmc-team",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        910,
        200
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEETS_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "cmmc_evidence",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "timestamp": "={{ $('Evaluate Controls').item.json.timestamp }}",
            "control_id": "={{ $json.id }}",
            "status": "={{ $json.status }}",
            "evidence": "={{ $json.evidence }}"
          }
        },
        "options": {}
      },
      "id": "aa1b2c3d-0001-0001-0001-000000000005",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        910,
        420
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Evaluate Controls",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Controls": {
      "main": [
        [
          {
            "node": "IF Gaps Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Gaps Found": {
      "main": [
        [
          {
            "node": "Slack #cmmc-team",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

  1. Import the JSON into your n8n instance
  2. Replace YOUR_SHEETS_ID with your CMMC evidence tracker spreadsheet ID
  3. Replace YOUR_SLACK_WEBHOOK with your #cmmc-team webhook URL
  4. Expand the Code node to cover all 110 NIST SP 800-171 Rev 3 practices relevant to your CMMC level
  5. Deploy in your CUI enclave — this workflow should never run outside it

Workflow 2: DFARS 252.204-7012 Incident Reporting Clock

Compliance: DFARS 252.204-7012(c)(1) — 72-hour cyber incident notification

DFARS 252.204-7012 requires defense contractors to report cyber incidents affecting covered contractor information systems within 72 hours of discovery — to both the DoD CIO via DIBNet portal and to DCSA (Defense Counterintelligence and Security Agency).

This workflow fires on a webhook (triggered by your SIEM or security team), calculates the reporting deadline, posts immediately to #sec-incident in Slack, logs to Sheets, then waits 66 hours before sending a final 6-hour warning email to the CISO with the DIBNet portal link.

{
  "name": "DFARS 252.204-7012 72-Hour Incident Reporting Clock",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "dfars-incident",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000001",
      "name": "Webhook \u2014 Incident Reported",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $input.first().json;\nconst severity = body.severity || 'HIGH';\nconst system = body.system || 'Unknown system';\nconst cuiInvolved = body.cui_involved || false;\nconst reportedAt = new Date();\nconst deadlineMs = 72 * 60 * 60 * 1000;\nconst deadline = new Date(reportedAt.getTime() + deadlineMs);\nconst warningAt = new Date(deadline.getTime() - 6 * 60 * 60 * 1000);\nreturn [{ json: {\n  incidentId: `DFARS-${Date.now()}`,\n  severity,\n  system,\n  cuiInvolved,\n  reportedAt: reportedAt.toISOString(),\n  deadlineISO: deadline.toISOString(),\n  deadlineHuman: deadline.toUTCString(),\n  warningAt: warningAt.toISOString(),\n  dibnetUrl: 'https://dibnet.dod.mil',\n  regulation: 'DFARS 252.204-7012(c)(1) \u2014 72-hour notification required'\n} }];"
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000002",
      "name": "Parse & Set Deadline",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=:rotating_light: *DFARS 252.204-7012 INCIDENT DECLARED*\n*ID:* {{ $json.incidentId }}\n*System:* {{ $json.system }}\n*Severity:* {{ $json.severity }}\n*CUI Involved:* {{ $json.cuiInvolved }}\n*Reported:* {{ $json.reportedAt }}\n*72-Hour Deadline:* {{ $json.deadlineHuman }}\n*Action:* Report at {{ $json.dibnetUrl }}\n*Regulation:* {{ $json.regulation }}"
            }
          ]
        }
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000003",
      "name": "Slack #sec-incident",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        690,
        200
      ]
    },
    {
      "parameters": {
        "operation": "upsert",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEETS_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "dfars_incidents",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "incident_id": "={{ $json.incidentId }}",
            "severity": "={{ $json.severity }}",
            "system": "={{ $json.system }}",
            "reported_at": "={{ $json.reportedAt }}",
            "deadline": "={{ $json.deadlineISO }}",
            "status": "OPEN"
          }
        },
        "options": {}
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000004",
      "name": "Log Incident",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        690,
        400
      ]
    },
    {
      "parameters": {
        "amount": 66,
        "unit": "hours"
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000005",
      "name": "Wait 66h (6h buffer)",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        910,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "ciso@yourcompany.com",
        "subject": "=ACTION REQUIRED in 6h: DFARS 72-Hour Reporting Deadline \u2014 {{ $('Parse & Set Deadline').item.json.incidentId }}",
        "message": "=This is your 6-hour warning.\n\nYou must report incident {{ $('Parse & Set Deadline').item.json.incidentId }} to DIBNet within 6 hours.\n\nSystem: {{ $('Parse & Set Deadline').item.json.system }}\nDeadline: {{ $('Parse & Set Deadline').item.json.deadlineHuman }}\nDIBNet portal: https://dibnet.dod.mil\n\nDFARS 252.204-7012(c)(1) requires reporting within 72 hours of discovery. Failure to report may constitute a material breach of your DoD contract.\n\nThis is an automated alert from your n8n compliance system.",
        "options": {}
      },
      "id": "bb2c3d4e-0002-0002-0002-000000000006",
      "name": "Email CISO \u2014 6h Warning",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        1130,
        300
      ]
    }
  ],
  "connections": {
    "Webhook \u2014 Incident Reported": {
      "main": [
        [
          {
            "node": "Parse & Set Deadline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse & Set Deadline": {
      "main": [
        [
          {
            "node": "Slack #sec-incident",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack #sec-incident": {
      "main": [
        [
          {
            "node": "Wait 66h (6h buffer)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 66h (6h buffer)": {
      "main": [
        [
          {
            "node": "Email CISO \u2014 6h Warning",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

  1. Import and activate the webhook — note the production URL
  2. Wire your SIEM (Splunk, Microsoft Sentinel, Elastic SIEM) to POST to this webhook on CRITICAL severity incidents
  3. Replace email addresses and Slack webhook
  4. Add a second branch to also notify your Contracting Officer Representative (COR) per DFARS 252.204-7012(e)

Key deadline: The 72-hour clock starts at discovery, not at confirmation. Err early.


Workflow 3: ITAR/EAR Export License Expiry Monitor

Compliance: ITAR 22 CFR Parts 120-130 (State/DDTC), EAR 15 CFR Parts 730-774 (Commerce/BIS)

Expired export licenses aren't just an administrative problem — they're a violation. ITAR violations carry civil penalties up to $1.3M per violation and criminal penalties up to $1M and 20 years imprisonment.

This workflow runs every morning, loads your export license register from Google Sheets, calculates days remaining, and routes alerts through CRITICAL → URGENT → WARNING → NOTICE tiers with renewal portal links.

{
  "name": "ITAR/EAR Export License Expiry Monitor",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      },
      "id": "cc3d4e5f-0003-0003-0003-000000000001",
      "name": "Daily 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEETS_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "export_licenses",
          "mode": "name"
        },
        "options": {}
      },
      "id": "cc3d4e5f-0003-0003-0003-000000000002",
      "name": "Load License List",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst results = [];\nfor (const item of $input.all()) {\n  const row = item.json;\n  const expiry = new Date(row.expiry_date);\n  const daysLeft = Math.floor((expiry - today) / (1000 * 60 * 60 * 24));\n  let urgency = null;\n  if (daysLeft < 0) urgency = 'EXPIRED';\n  else if (daysLeft <= 7) urgency = 'CRITICAL';\n  else if (daysLeft <= 30) urgency = 'URGENT';\n  else if (daysLeft <= 60) urgency = 'WARNING';\n  else if (daysLeft <= 90) urgency = 'NOTICE';\n  if (urgency) {\n    results.push({\n      json: {\n        licenseNumber: row.license_number,\n        regulation: row.regulation,\n        article: row.article_description,\n        country: row.destination_country,\n        expiryDate: row.expiry_date,\n        daysLeft,\n        urgency,\n        renewalContact: row.renewal_contact || 'export-control@yourcompany.com'\n      }\n    });\n  }\n}\nreturn results.length > 0 ? results : [{ json: { noAlertsNeeded: true } }];"
      },
      "id": "cc3d4e5f-0003-0003-0003-000000000003",
      "name": "Calculate Days Left",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond1",
              "leftValue": "={{ $json.noAlertsNeeded }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "notEqual"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "cc3d4e5f-0003-0003-0003-000000000004",
      "name": "IF Alert Needed",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        910,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=:passport_control: *{{ $json.urgency }} \u2014 Export License Expiry*\n*License:* {{ $json.licenseNumber }} ({{ $json.regulation }})\n*Article:* {{ $json.article }}\n*Destination:* {{ $json.country }}\n*Expires:* {{ $json.expiryDate }} ({{ $json.daysLeft }} days)\n*Contact:* {{ $json.renewalContact }}\n*Action:* Initiate renewal at https://deccs.pmdtc.state.gov (ITAR) or https://www.bis.doc.gov (EAR)"
            }
          ]
        }
      },
      "id": "cc3d4e5f-0003-0003-0003-000000000005",
      "name": "Slack #export-control",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1130,
        200
      ]
    }
  ],
  "connections": {
    "Daily 8AM": {
      "main": [
        [
          {
            "node": "Load License List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load License List": {
      "main": [
        [
          {
            "node": "Calculate Days Left",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Days Left": {
      "main": [
        [
          {
            "node": "IF Alert Needed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Alert Needed": {
      "main": [
        [
          {
            "node": "Slack #export-control",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

  1. Create a Sheets tab export_licenses with columns: license_number, regulation (ITAR/EAR/DSP-5/etc.), article_description, destination_country, expiry_date, renewal_contact
  2. Renewals go to: DDTC (State) for ITAR https://deccs.pmdtc.state.gov, BIS (Commerce) for EAR https://www.bis.doc.gov
  3. Add a CC to your legal/export-control counsel for CRITICAL and EXPIRED alerts

Workflow 4: NIST SP 800-171 Rev 3 Practice Assessment Tracker

Compliance: NIST SP 800-171 Rev 3 — 110 practices across 17 families (AC, AT, AU, CA, CM, IA, IR, MA, MP, PE, PS, RA, SA, SC, SI, SR, PT)

NIST SP 800-171 Rev 3 is the foundational control set for CMMC Level 2. The challenge isn't knowing the 110 practices — it's tracking which ones are assessed, by whom, and when they expire.

This workflow loads your 110-practice tracker from Sheets every Monday morning and posts a classified snapshot to your compliance team Slack channel, grouped by overdue/critical/urgent/warning.

{
  "name": "NIST SP 800-171 Rev 3 Practice Assessment Tracker",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "id": "dd4e5f60-0004-0004-0004-000000000001",
      "name": "Weekly Monday 9AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEETS_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "nist_800_171",
          "mode": "name"
        },
        "options": {}
      },
      "id": "dd4e5f60-0004-0004-0004-000000000002",
      "name": "Load 110 Practices",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst overdue = [], critical = [], urgent = [], warning = [];\nfor (const item of $input.all()) {\n  const row = item.json;\n  if (row.status === 'COMPLIANT') continue;\n  const dueDate = new Date(row.assessment_due);\n  const days = Math.floor((dueDate - today) / (1000 * 60 * 60 * 24));\n  const rec = { family: row.family, practiceId: row.practice_id, title: row.title, days, status: row.status, owner: row.owner };\n  if (days < 0) overdue.push(rec);\n  else if (days <= 7) critical.push(rec);\n  else if (days <= 30) urgent.push(rec);\n  else if (days <= 60) warning.push(rec);\n}\nconst total = overdue.length + critical.length + urgent.length + warning.length;\nreturn [{ json: { overdue, critical, urgent, warning, total, timestamp: today.toISOString() } }];"
      },
      "id": "dd4e5f60-0004-0004-0004-000000000003",
      "name": "Classify Practices",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/YOUR_SLACK_WEBHOOK",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "text",
              "value": "=:shield: *NIST SP 800-171 Rev 3 \u2014 Weekly Compliance Snapshot*\n*OVERDUE:* {{ $json.overdue.length }} practices\n*CRITICAL (\u22647d):* {{ $json.critical.length }}\n*URGENT (\u226430d):* {{ $json.urgent.length }}\n*WARNING (\u226460d):* {{ $json.warning.length }}\n{{ $json.overdue.length > 0 ? ':red_circle: Overdue:\\n' + $json.overdue.map(p => `\u2022 ${p.practiceId} ${p.title} \u2014 Owner: ${p.owner}`).join('\\n') : '' }}\n{{ $json.critical.length > 0 ? ':orange_circle: Critical:\\n' + $json.critical.map(p => `\u2022 ${p.practiceId} ${p.title} \u2014 ${p.days}d`).join('\\n') : '' }}\n*Reference:* NIST SP 800-171 Rev 3 (32 CFR Part 170, DFARS 252.204-7012)"
            }
          ]
        }
      },
      "id": "dd4e5f60-0004-0004-0004-000000000004",
      "name": "Slack #compliance-team",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        910,
        300
      ]
    }
  ],
  "connections": {
    "Weekly Monday 9AM": {
      "main": [
        [
          {
            "node": "Load 110 Practices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load 110 Practices": {
      "main": [
        [
          {
            "node": "Classify Practices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Practices": {
      "main": [
        [
          {
            "node": "Slack #compliance-team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

  1. Create a Sheets tab nist_800_171 with columns: family, practice_id, title, status (COMPLIANT/IN_PROGRESS/NOT_STARTED), assessment_due, owner
  2. Pre-populate all 110 practices — download the NIST SP 800-171 Rev 3 spreadsheet from nvlpubs.nist.gov
  3. Assign owners by family (AC → IT Security, MA → IT Operations, PS → HR)

Workflow 5: Weekly Defense Contract KPI Dashboard

Compliance: FAR/DFARS reporting, CMMC status tracking, CDRL deliverable management

Every Monday morning, this workflow pulls your contract data from Sheets, computes CLIN burn rate, counts overdue CDRLs, summarizes CMMC compliance status, and emails a formatted HTML dashboard to the PM with a BCC to contracts.

{
  "name": "Weekly Defense Contract KPI Dashboard",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "id": "ee5f6071-0005-0005-0005-000000000001",
      "name": "Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "YOUR_SHEETS_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "contract_data",
          "mode": "name"
        },
        "options": {}
      },
      "id": "ee5f6071-0005-0005-0005-000000000002",
      "name": "Load Contract Data",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.4,
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\nconst totalValue = rows.reduce((s,r) => s + parseFloat(r.clin_value||0), 0);\nconst totalSpent = rows.reduce((s,r) => s + parseFloat(r.spent_to_date||0), 0);\nconst burnRate = totalValue > 0 ? ((totalSpent/totalValue)*100).toFixed(1) : 0;\nconst overdueDeliverables = rows.filter(r => r.cdrl_status === 'OVERDUE').length;\nconst cmmcStatus = rows[0]?.cmmc_level || 'Level 2';\nconst cmmcNextReview = rows[0]?.cmmc_next_review || 'TBD';\nconst html = `\n<html><body style=\"font-family:Arial,sans-serif;max-width:700px;margin:auto\">\n<h2 style=\"color:#003366\">Defense Contract KPI Dashboard</h2>\n<table border=\"1\" cellpadding=\"8\" style=\"border-collapse:collapse;width:100%\">\n<tr style=\"background:#003366;color:white\"><th>Metric</th><th>Value</th><th>Status</th></tr>\n<tr><td>Total Contract Value</td><td>$${totalValue.toLocaleString()}</td><td>-</td></tr>\n<tr><td>Spend to Date</td><td>$${totalSpent.toLocaleString()}</td><td>${burnRate}% burn rate</td></tr>\n<tr><td>Overdue CDRLs</td><td>${overdueDeliverables}</td><td>${overdueDeliverables > 0 ? '\u26a0\ufe0f ACTION REQUIRED' : '\u2705 On Track'}</td></tr>\n<tr><td>CMMC Status</td><td>${cmmcStatus}</td><td>Next review: ${cmmcNextReview}</td></tr>\n</table>\n<p style=\"color:#666;font-size:12px\">Generated by n8n \u2014 ${new Date().toUTCString()}<br>DFARS 252.204-7012 | NIST SP 800-171 Rev 3 | 32 CFR Part 170</p>\n</body></html>`;\nreturn [{ json: { html, burnRate, overdueDeliverables, cmmcStatus, timestamp: new Date().toISOString() } }];"
      },
      "id": "ee5f6071-0005-0005-0005-000000000003",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "pm@yourcompany.com",
        "subject": "=Weekly Defense Contract KPI \u2014 {{ $json.timestamp.split('T')[0] }}",
        "message": "={{ $json.html }}",
        "options": {
          "appendAttribution": false,
          "bccList": "contracts@yourcompany.com"
        }
      },
      "id": "ee5f6071-0005-0005-0005-000000000004",
      "name": "Email PM + BCC Contracts",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        910,
        300
      ]
    }
  ],
  "connections": {
    "Monday 8AM": {
      "main": [
        [
          {
            "node": "Load Contract Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Contract Data": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Email PM + BCC Contracts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
Enter fullscreen mode Exit fullscreen mode

Setup:

  1. Create a Sheets tab contract_data with columns: clin_value, spent_to_date, cdrl_status, cmmc_level, cmmc_next_review
  2. Replace pm@yourcompany.com and BCC with your actual PM and contracts team emails
  3. Extend the Code node to include CLIN-level burn-rate calculations and CDRL deadline breakdowns

n8n vs Zapier/Make for DefenseTech

Requirement n8n (self-hosted) Zapier Make.com
CUI stays in enclave ✅ Yes ❌ Flows through Zapier cloud ❌ Flows through Make cloud
Git-versionable workflows ✅ JSON export ❌ No ❌ No
CMMC assessor audit trail ✅ Workflow + execution logs ❌ No ❌ No
DoD Approved Products List ✅ Self-hosted (your accreditation) ❌ Not listed ❌ Not listed
Run in air-gapped network ✅ Yes ❌ No ❌ No
DFARS 252.204-7012 compliant ✅ With correct deployment ❌ Not without FedRAMP ❌ Not without FedRAMP

Bottom line: If your contract requires handling CUI, self-hosting n8n in a CMMC-scoped environment is the only automation path that doesn't create a new compliance finding.


Get the workflows

These workflows and 14 others are available at FlowKit — n8n Automation Templates.

Topics covered: Email auto-responder, social cross-poster, invoice generator, AI customer support bot, lead capture to CRM, content repurposer, price monitor, daily report generator, webhook to database, appointment reminder, and more.

Individual templates: $12–$29. Full bundle (15 templates): $97.

Have a specific defense/compliance workflow you need built? Drop a comment below.

Top comments (0)