Step-by-step guide for deploying E8CR Squad against a real Microsoft 365 tenant.
- Python 3.10+ installed
- Access to an Azure AD tenant with Global Administrator or Application Administrator role
- Microsoft 365 licensing (E3/E5 for Intune, E5 or MDE P2 for Defender Vulnerability Management)
- A Linux server, macOS host, or Docker-capable host for running the bots
Each bot needs its own app registration for least-privilege isolation. Repeat this process for each bot (VM+PM, Identity, AppControl, Backup).
- Go to Azure Portal > Azure Active Directory > App registrations
- Click New registration
- Configure:
- Name:
E8CR-VMPM(orE8CR-Identity,E8CR-AppControl,E8CR-Backup) - Supported account types: "Accounts in this organizational directory only"
- Redirect URI: Leave blank (not needed for client credentials flow)
- Name:
- Click Register
- Note the Application (client) ID and Directory (tenant) ID from the overview page
- In the app registration, go to Certificates & secrets
- Click New client secret
- Description:
E8CR production(or similar) - Expiry: 6 months (set a calendar reminder to rotate)
- Click Add
- Copy the secret value immediately — it will not be shown again
- Go to API permissions > Add a permission > Microsoft Graph > Application permissions
- Add the permissions for the specific bot (see table below)
- Click Grant admin consent for [your tenant]
VM+PM Bot:
DeviceManagementManagedDevices.Read.AllDeviceManagementConfiguration.Read.AllWindowsUpdates.Read.All- If using MDVM:
Vulnerability.Read.All,Software.Read.All,Machine.Read.All
Identity Bot:
User.Read.AllDirectory.Read.AllPolicy.Read.AllAuditLog.Read.AllRoleManagement.Read.AllUserAuthenticationMethod.Read.All
AppControl Bot:
DeviceManagementConfiguration.Read.AllDeviceManagementManagedDevices.Read.All
Backup Bot:
DeviceManagementManagedDevices.Read.All
export AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
python3 shared/graph_auth.py --checkYou should see confirmation that the token was acquired and the organisation query succeeded.
Get the VM+PM bot running against a real tenant.
git clone https://github.com/RADobson/e8cr-squad.git
cd e8cr-squadexport AZURE_TENANT_ID="your-tenant-id"
export AZURE_CLIENT_ID="vmpm-client-id"
export AZURE_CLIENT_SECRET="vmpm-client-secret"
export E8CR_SIGNING_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(32))')"python3 shared/graph_auth.py --check# Audit mode (read-only, safe)
python3 run_all.py --bots vmpm --output ./vmpm-assessmentopen ./vmpm-assessment/e8cr-assessment.html
# Or on Linux:
# xdg-open ./vmpm-assessment/e8cr-assessment.htmlpython3 scripts/verify_evidence.py ./vmpm-assessment/vmpm/evidence-pack/manifest.jsonsudo mkdir -p /opt/e8cr/{vmpm,identity,appcontrol,backup}
sudo chown -R $(whoami) /opt/e8crCreate a .env file for each bot with its own credentials:
# /opt/e8cr/vmpm/.env
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=vmpm-client-id
AZURE_CLIENT_SECRET=vmpm-client-secret
E8CR_SIGNING_KEY=your-signing-key
E8CR_ENABLE_CHANGES=false# /opt/e8cr/identity/.env
AZURE_TENANT_ID=your-tenant-id
AZURE_CLIENT_ID=identity-client-id
AZURE_CLIENT_SECRET=identity-client-secret
E8CR_SIGNING_KEY=your-signing-key
E8CR_ENABLE_CHANGES=falseRepeat for appcontrol and backup. Secure the files:
chmod 600 /opt/e8cr/*/.env# Run unified assessment with synthetic data
docker compose --profile unified up unified
# Output is in the unified-output volume- Create a
.envfile in the repo root:
AZURE_TENANT_ID=your-tenant-id
VMPM_CLIENT_ID=vmpm-client-id
VMPM_CLIENT_SECRET=vmpm-client-secret
IDENTITY_CLIENT_ID=identity-client-id
IDENTITY_CLIENT_SECRET=identity-client-secret
APPCONTROL_CLIENT_ID=appcontrol-client-id
APPCONTROL_CLIENT_SECRET=appcontrol-client-secret
BACKUP_CLIENT_ID=backup-client-id
BACKUP_CLIENT_SECRET=backup-client-secret
E8CR_SIGNING_KEY=your-signing-key- Remove the
--demoflag fromdocker-compose.ymlcommand entries (or override):
# Run all 4 bots against the real tenant
docker compose up vmpm identity appcontrol backup- Access output:
# Copy evidence from volumes
docker compose cp vmpm:/output ./vmpm-evidence
docker compose cp identity:/output ./identity-evidenceUse cron to trigger Docker runs on a schedule:
# Daily assessment at 2am
0 2 * * * cd /opt/e8cr/e8cr-squad && docker compose up --abort-on-container-exit vmpm identity appcontrol backup >> /var/log/e8cr/daily.log 2>&1For bare-metal Linux deployment, create systemd services and timers for each bot.
Create /etc/systemd/system/e8cr-vmpm.service:
[Unit]
Description=E8CR VM+PM Bot Assessment
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=e8cr
Group=e8cr
WorkingDirectory=/opt/e8cr/e8cr-squad
EnvironmentFile=/opt/e8cr/vmpm/.env
ExecStart=/usr/bin/python3 run_all.py --bots vmpm --output /opt/e8cr/vmpm/output
StandardOutput=journal
StandardError=journal
TimeoutStartSec=600Create /etc/systemd/system/e8cr-vmpm.timer:
[Unit]
Description=E8CR VM+PM Bot Daily Timer
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now e8cr-vmpm.timerCreate matching service and timer files for e8cr-identity, e8cr-appcontrol, and e8cr-backup, staggering the timer schedules:
| Bot | Schedule |
|---|---|
| VM+PM | 02:00 |
| Identity | 02:15 |
| AppControl | 02:30 |
| Backup | 02:45 |
systemctl list-timers 'e8cr-*'
journalctl -u e8cr-vmpm --since todayFor Mac mini deployment (useful for small MSP environments).
Create ~/Library/LaunchAgents/com.e8cr.vmpm.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.e8cr.vmpm</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/opt/e8cr/e8cr-squad/run_all.py</string>
<string>--bots</string>
<string>vmpm</string>
<string>--output</string>
<string>/opt/e8cr/vmpm/output</string>
</array>
<key>EnvironmentVariables</key>
<dict>
<key>AZURE_TENANT_ID</key>
<string>your-tenant-id</string>
<key>AZURE_CLIENT_ID</key>
<string>vmpm-client-id</string>
<key>AZURE_CLIENT_SECRET</key>
<string>vmpm-client-secret</string>
<key>E8CR_SIGNING_KEY</key>
<string>your-signing-key</string>
<key>E8CR_ENABLE_CHANGES</key>
<string>false</string>
</dict>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>2</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<key>StandardOutPath</key>
<string>/opt/e8cr/vmpm/output/launchd.log</string>
<key>StandardErrorPath</key>
<string>/opt/e8cr/vmpm/output/launchd.err</string>
</dict>
</plist>launchctl load ~/Library/LaunchAgents/com.e8cr.vmpm.plistFor the multi-instance OpenClaw deployment on macOS, see examples/openclaw-multi-instance/README.md.
After each bot run, verify:
- Exit code: Non-zero means the bot encountered an error
- Report file exists: Check that the expected HTML report was generated
- Evidence files exist: Verify all files listed in
bot.contract.yamlevidence_filesare present - Evidence integrity: Run
scripts/verify_evidence.pyon the manifest
Example health check script:
#!/bin/bash
BOT=$1
OUTPUT_DIR="/opt/e8cr/$BOT/output"
# Check report exists
if [ ! -f "$OUTPUT_DIR"/*-report.html ]; then
echo "ALERT: $BOT report missing"
exit 1
fi
# Verify evidence integrity
python3 /opt/e8cr/e8cr-squad/scripts/verify_evidence.py \
"$OUTPUT_DIR/manifest.json"
if [ $? -ne 0 ]; then
echo "ALERT: $BOT evidence integrity check failed"
exit 1
fi
echo "OK: $BOT healthy"Integrate with your existing alerting:
- Systemd: Use
OnFailure=in the service unit to trigger a notification script - Docker: Check container exit codes in your cron wrapper
- Email: Pipe health check output to
sendmailor a webhook - Webhook: POST to Slack, Teams, or PagerDuty on failure
Create /etc/systemd/system/e8cr-notify-failure@.service:
[Unit]
Description=E8CR Failure Notification for %i
[Service]
Type=oneshot
ExecStart=/opt/e8cr/scripts/notify-failure.sh %iAdd to each bot service:
[Unit]
OnFailure=e8cr-notify-failure@%n.serviceBefore going to production, verify:
- Each bot has its own Azure AD app registration
- Permissions are read-only (no
.ReadWritescopes) - Admin consent is granted for all permissions
-
E8CR_ENABLE_CHANGES=falseis set - Credentials are in environment variables, not in code
-
.envfiles havechmod 600 -
E8CR_SIGNING_KEYis set for evidence signing - Network egress is restricted to required endpoints only
- No inbound ports are open to the bot hosts
- Logs are being collected and retained
- A credential rotation schedule is in place
See SECURITY.md for the full security hardening guide.