Author SHA1 Message Date
Wesley van TilburgandClaude Opus 5.5 3df9ce77d6 fedora-intune: Defender script also turns on behavior monitoring
Behavior monitoring is off by default and the repair script never
checked it, so on devices without the image's managed config (another
distro, a plain install) it stayed off and the compliance rule failed.
Enable it, and cloud-delivered protection, when they're off and not
managed.

Only start mdatp-firstboot where the image installed it; elsewhere every
run logged that first-boot setup hadn't completed and tried to start a
unit that doesn't exist.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-21 11:31:14 +00:00
Misthios 99400fb03e Merge pull request 'fedora-intune: query Defender through systemd-run instead of a snapshot' (#8) from misthios/defender-query-via-systemd-run into main
Build containers / Compute changes (push) Successful in 21s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Skipped
Build containers / fedora-intune (44) (push) Successful in 12m25s
Build containers / Prune old releases and tags (push) Successful in 27s
Reviewed-on: #8
2026-10-20 14:16:28 +00:00
Wesley van TilburgandClaude Opus 5.5 ca115922bd fedora-intune: query Defender through systemd-run instead of a snapshot
Build containers / fedora-cosmic (44) (pull_request) Canceled after 0s
Build containers / fedora-cosmic (rawhide) (pull_request) Canceled after 0s
Build containers / fedora-remote (44) (pull_request) Canceled after 0s
Build containers / fedora-intune (44) (pull_request) Canceled after 0s
Build containers / Compute changes (pull_request) Canceled after 14s
Build containers / Prune old releases and tags (pull_request) Skipped
The health snapshot's temp directory, chmod and mv in /run every 5
minutes showed up in Defender's timeline as "Data Staged" and
"Executable permissions were added". Drop the snapshot service and
timer. The compliance script now runs `mdatp health` through
systemd-run, so systemd starts mdatp and Defender answers it even when
the script runs inside himmelblaud-tasks. Without systemd-run access it
asks mdatp directly, as before. No image support needed, so the script
works on other distros as is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 14:12:59 +00:00
Misthios 082ae219b1 Merge pull request 'fedora-intune: take the first Defender health snapshot right after boot' (#7) from misthios/defender-snapshot-at-boot into main
Build containers / Compute changes (push) Successful in 20s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Skipped
Build containers / fedora-intune (44) (push) Successful in 12m28s
Build containers / Prune old releases and tags (push) Successful in 27s
Reviewed-on: #7
2026-10-20 14:02:43 +00:00
Wesley van TilburgandClaude Opus 5.5 49b360dffa fedora-intune: take the first Defender health snapshot right after boot
Build containers / fedora-cosmic (44) (pull_request) Canceled after 0s
Build containers / fedora-cosmic (rawhide) (pull_request) Canceled after 0s
Build containers / fedora-remote (44) (pull_request) Canceled after 0s
Build containers / fedora-intune (44) (pull_request) Canceled after 0s
Build containers / Compute changes (pull_request) Canceled after 12s
Build containers / Prune old releases and tags (pull_request) Skipped
The first snapshot came 2 minutes after boot, so the compliance check at
the first sign-in found none, fell back to mdatp (which won't answer it)
and reported Defender as not running. Himmelblau then doesn't check again
for 8 hours. Start the timer 20 seconds after boot, and wait up to
2 minutes for the daemon to answer so that snapshot isn't a false
"not running". The timer runs it in the background, so boot isn't held up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:47:07 +00:00
Misthios 811025773e Merge pull request 'fedora-intune: give the Intune compliance check a Defender health snapshot' (#6) from misthios/defender-health-snapshot into main
Build containers / Compute changes (push) Successful in 19s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Skipped
Build containers / fedora-intune (44) (push) Successful in 12m31s
Build containers / Prune old releases and tags (push) Successful in 26s
Reviewed-on: #6
2026-10-20 13:25:02 +00:00
Wesley van TilburgandClaude Opus 5.5 111d3bea66 fedora-intune: give the Intune compliance check a Defender health snapshot
Build containers / fedora-cosmic (44) (pull_request) Canceled after 0s
Build containers / fedora-cosmic (rawhide) (pull_request) Canceled after 0s
Build containers / fedora-remote (44) (pull_request) Canceled after 0s
Build containers / fedora-intune (44) (pull_request) Canceled after 0s
Build containers / Compute changes (pull_request) Canceled after 9s
Build containers / Prune old releases and tags (pull_request) Skipped
Himmelblau runs the custom compliance script inside himmelblaud-tasks,
and Defender refuses to answer `mdatp health` there: the daemon can't
read /proc/<himmelblaud_tasks>/cmdline while checking the caller
("Permission denied" in microsoft_defender_err.log). The script saw no
daemon, and every Defender rule failed although Defender was healthy.

Add mdatp-health-snapshot.timer, which saves the health fields to
/run/mdatp-health every 5 minutes as root, outside that service. The
compliance script reads the snapshot when it's under 20 minutes old and
otherwise asks mdatp directly, so it still works on other distros.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 13:23:17 +00:00
Misthios 4d780e900a Merge pull request 'Misthios/fedora intune defender os version' (#5) from misthios/fedora-intune-defender-os-version into main
Build containers / Compute changes (push) Successful in 22s
Build containers / fedora-cosmic (44) (push) Skipped
Build containers / fedora-cosmic (rawhide) (push) Skipped
Build containers / fedora-remote (44) (push) Skipped
Build containers / fedora-intune (44) (push) Successful in 12m35s
Build containers / Prune old releases and tags (push) Successful in 26s
Reviewed-on: #5
2026-10-20 12:49:33 +00:00
2 changed files with 32 additions and 14 deletions

No files matched your search

@@ -2,20 +2,26 @@
# Intune custom compliance discovery script for Linux: is Microsoft Defender # Intune custom compliance discovery script for Linux: is Microsoft Defender
# for Endpoint (mdatp) installed, onboarded, licensed and protecting? # for Endpoint (mdatp) installed, onboarded, licensed and protecting?
# #
# Upload in Intune > Devices > Compliance > Scripts (Linux), and pair it with # Upload in Intune > Endpoint security > Device compliance > Scripts (Linux),
# defender-compliance.json in a Linux compliance policy (Custom compliance). # and pair it with defender-compliance.json in a Linux compliance policy
# Himmelblau runs it as root inside himmelblaud-tasks.service and sends the # (Custom compliance). Himmelblau runs it as root inside
# JSON on stdout to Intune, which evaluates it against the rules. Every setting # himmelblaud-tasks.service and sends the JSON on stdout to Intune, which
# is always emitted, so a missing or stopped Defender reads as non-compliant # evaluates it against the rules. Every setting is always emitted, so a missing
# instead of as a script error. # or stopped Defender reads as non-compliant instead of as a script error.
MDATP=/usr/bin/mdatp MDATP=/usr/bin/mdatp
# Print one `mdatp health` field without quotes or a trailing " [managed]". # Defender won't answer `mdatp health` started from inside himmelblaud-tasks
# Unlicensed or unonboarded, mdatp prints "ATTENTION: ..." lines first. # (it can't read that process while checking the caller), so ask through
# systemd, which runs mdatp as its own child. Elsewhere, ask mdatp directly.
health="$(systemd-run --quiet --wait --pipe --collect "${MDATP}" health 2>/dev/null)"
grep -q '^healthy' <<< "${health}" || health="$("${MDATP}" health 2>/dev/null)"
# Print one field of the `mdatp health` output ("name : value") without
# quotes or a trailing " [managed]".
field() { field() {
"${MDATP}" health --field "$1" 2>/dev/null | grep -v '^ATTENTION' | tail -n1 \ grep -m1 "^$1 *:" <<< "${health}" \
| sed -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//' | sed -e 's/^[^:]*: *//' -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//'
} }
bool() { bool() {
@@ -56,7 +56,7 @@ if [[ ! -x "${MDATP}" ]]; then
fi fi
# The image finishes the Defender install on first boot (mdatp-firstboot). # The image finishes the Defender install on first boot (mdatp-firstboot).
if [[ ! -f /var/lib/mdatp-firstboot.done ]]; then if [[ -x /usr/libexec/mdatp-firstboot && ! -f /var/lib/mdatp-firstboot.done ]]; then
log "Defender first-boot setup has not completed; starting it." log "Defender first-boot setup has not completed; starting it."
systemctl start mdatp-firstboot.service systemctl start mdatp-firstboot.service
fi fi
@@ -93,18 +93,30 @@ if [[ -z "$(field org_id)" ]]; then
fi fi
fi fi
# Settings Defender's own policy manages show " [managed]" and can't be # Settings a managed config (mdatp_managed.json) sets show " [managed]" and
# changed locally; this only fixes a locally disabled real-time protection. # can't be changed locally; these fix devices without one, where behavior
# monitoring is off by default. Behavior monitoring needs real-time
# protection, so that goes first.
if [[ "$(field real_time_protection_enabled)" != true ]]; then if [[ "$(field real_time_protection_enabled)" != true ]]; then
log "Real-time protection is off; enabling it." log "Real-time protection is off; enabling it."
"${MDATP}" config real-time-protection --value enabled "${MDATP}" config real-time-protection --value enabled
fi fi
if [[ "$(field behavior_monitoring)" != enabled ]]; then
log "Behavior monitoring is off; enabling it."
"${MDATP}" config behavior-monitoring --value enabled
fi
if [[ "$(field cloud_enabled)" != true ]]; then
log "Cloud-delivered protection is off; enabling it."
"${MDATP}" config cloud --value enabled
fi
if [[ "$(field definitions_status)" != up_to_date ]]; then if [[ "$(field definitions_status)" != up_to_date ]]; then
log "Definitions are not up to date; updating them." log "Definitions are not up to date; updating them."
"${MDATP}" definitions update "${MDATP}" definitions update
fi fi
healthy="$(field healthy)" healthy="$(field healthy)"
log "healthy=${healthy} licensed=$(field licensed) org_id=$(field org_id) rtp=$(field real_time_protection_enabled) definitions=$(field definitions_status)" log "healthy=${healthy} licensed=$(field licensed) org_id=$(field org_id) rtp=$(field real_time_protection_enabled) behavior_monitoring=$(field behavior_monitoring) cloud=$(field cloud_enabled) definitions=$(field definitions_status)"
[[ "${healthy}" == true ]] [[ "${healthy}" == true ]]