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
# for Endpoint (mdatp) installed, onboarded, licensed and protecting?
#
# Upload in Intune > Devices > Compliance > Scripts (Linux), and pair it with
# defender-compliance.json in a Linux compliance policy (Custom compliance).
# Himmelblau runs it as root inside himmelblaud-tasks.service and sends the
# JSON on stdout to Intune, which evaluates it against the rules. Every setting
# is always emitted, so a missing or stopped Defender reads as non-compliant
# instead of as a script error.
# Upload in Intune > Endpoint security > Device compliance > Scripts (Linux),
# and pair it with defender-compliance.json in a Linux compliance policy
# (Custom compliance). Himmelblau runs it as root inside
# himmelblaud-tasks.service and sends the JSON on stdout to Intune, which
# evaluates it against the rules. Every setting is always emitted, so a missing
# or stopped Defender reads as non-compliant instead of as a script error.
MDATP=/usr/bin/mdatp
# Print one `mdatp health` field without quotes or a trailing " [managed]".
# Unlicensed or unonboarded, mdatp prints "ATTENTION: ..." lines first.
# Defender won't answer `mdatp health` started from inside himmelblaud-tasks
# (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() {
"${MDATP}" health --field "$1" 2>/dev/null | grep -v '^ATTENTION' | tail -n1 \
| sed -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//'
grep -m1 "^$1 *:" <<< "${health}" \
| sed -e 's/^[^:]*: *//' -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//'
}
bool() {
@@ -56,7 +56,7 @@ if [[ ! -x "${MDATP}" ]]; then
fi
# 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."
systemctl start mdatp-firstboot.service
fi
@@ -93,18 +93,30 @@ if [[ -z "$(field org_id)" ]]; then
fi
fi
# Settings Defender's own policy manages show " [managed]" and can't be
# changed locally; this only fixes a locally disabled real-time protection.
# Settings a managed config (mdatp_managed.json) sets show " [managed]" and
# 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
log "Real-time protection is off; enabling it."
"${MDATP}" config real-time-protection --value enabled
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
log "Definitions are not up to date; updating them."
"${MDATP}" definitions update
fi
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 ]]