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
7 changed files with 32 additions and 87 deletions

No files matched your search

+1 -6
View File
@@ -26,11 +26,6 @@ add-files:
- ["mdatp-firstboot.service", "/usr/lib/systemd/system/mdatp-firstboot.service"] - ["mdatp-firstboot.service", "/usr/lib/systemd/system/mdatp-firstboot.service"]
- ["mdatp-firstboot.conf", "/usr/lib/systemd/system/mdatp.service.d/10-firstboot.conf"] - ["mdatp-firstboot.conf", "/usr/lib/systemd/system/mdatp.service.d/10-firstboot.conf"]
- ["50-defender.preset", "/usr/lib/systemd/system-preset/50-defender.preset"] - ["50-defender.preset", "/usr/lib/systemd/system-preset/50-defender.preset"]
# Defender health for the Intune compliance script, which can't query
# Defender itself from inside himmelblaud-tasks; see the script.
- ["mdatp-health-snapshot", "/usr/libexec/mdatp-health-snapshot"]
- ["mdatp-health-snapshot.service", "/usr/lib/systemd/system/mdatp-health-snapshot.service"]
- ["mdatp-health-snapshot.timer", "/usr/lib/systemd/system/mdatp-health-snapshot.timer"]
# Enforced Defender settings (shown as "[managed]" in `mdatp health`; local # Enforced Defender settings (shown as "[managed]" in `mdatp health`; local
# changes are refused): real-time protection, behavior monitoring, PUA # changes are refused): real-time protection, behavior monitoring, PUA
# blocking, cloud protection at the "high" block level, automatic updates # blocking, cloud protection at the "high" block level, automatic updates
@@ -62,4 +57,4 @@ postprocess:
# Fail the build on a typo rather than ship a config Defender can't read. # Fail the build on a typo rather than ship a config Defender can't read.
python3 -m json.tool /usr/share/mdatp-managed/mdatp_managed.json > /dev/null python3 -m json.tool /usr/share/mdatp-managed/mdatp_managed.json > /dev/null
rpm -q --qf '%{POSTIN}' mdatp > /usr/libexec/mdatp-postinst rpm -q --qf '%{POSTIN}' mdatp > /usr/libexec/mdatp-postinst
chmod 0755 /usr/libexec/mdatp-firstboot /usr/libexec/mdatp-postinst /usr/libexec/mdatp-health-snapshot chmod 0755 /usr/libexec/mdatp-firstboot /usr/libexec/mdatp-postinst
@@ -2,5 +2,3 @@
enable mdatp-firstboot.service enable mdatp-firstboot.service
enable mdatp.service enable mdatp.service
enable mde_netfilter_v2.socket enable mde_netfilter_v2.socket
# Health snapshot for the Intune compliance script.
enable mdatp-health-snapshot.timer
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
# Save `mdatp health` fields to /run/mdatp-health/<field> for the Intune
# custom compliance script (intune/defender-compliance.sh).
#
# That script runs inside himmelblaud-tasks.service, and Defender refuses to
# answer it there: the daemon checks the calling process tree, can't read
# /proc/<himmelblaud_tasks pid>/cmdline ("Permission denied" in
# microsoft_defender_err.log), and the script sees no daemon. This runs from a
# timer outside that service instead. Each file holds the raw
# `mdatp health --field` output; the directory is replaced as a whole.
set -euo pipefail
MDATP=/usr/bin/mdatp
DEST=/run/mdatp-health
FIELDS=(
healthy
licensed
org_id
real_time_protection_enabled
behavior_monitoring
cloud_enabled
definitions_status
definitions_updated_minutes_ago
app_version
)
tmp="$(mktemp -d "${DEST}.XXXXXX")"
trap 'rm -rf "${tmp}"' EXIT
for field in "${FIELDS[@]}"; do
"${MDATP}" health --field "${field}" > "${tmp}/${field}" 2>/dev/null || true
done
chmod 0755 "${tmp}"
chmod 0644 "${tmp}"/*
# Swap in the new snapshot; readers see either the old or the new one.
rm -rf "${DEST}.old"
[[ -d "${DEST}" ]] && mv "${DEST}" "${DEST}.old"
mv "${tmp}" "${DEST}"
rm -rf "${DEST}.old"
trap - EXIT
@@ -1,7 +0,0 @@
[Unit]
Description=Save Microsoft Defender health for the Intune compliance check
After=mdatp.service
[Service]
Type=oneshot
ExecStart=/usr/libexec/mdatp-health-snapshot
@@ -1,9 +0,0 @@
[Unit]
Description=Save Microsoft Defender health every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
[Install]
WantedBy=timers.target
@@ -2,30 +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
# Under Himmelblau, Defender won't answer `mdatp health` from this script, so
# a root timer may save the fields in /run/mdatp-health (the fedora-intune
# image does). Used when under 20 minutes old, otherwise mdatp is asked.
SNAPSHOT=/run/mdatp-health
if [[ -n "$(find "${SNAPSHOT}/healthy" -mmin -20 2>/dev/null)" ]]; then # Defender won't answer `mdatp health` started from inside himmelblaud-tasks
raw_field() { cat "${SNAPSHOT}/$1" 2>/dev/null; } # (it can't read that process while checking the caller), so ask through
else # systemd, which runs mdatp as its own child. Elsewhere, ask mdatp directly.
raw_field() { "${MDATP}" health --field "$1" 2>/dev/null; } health="$(systemd-run --quiet --wait --pipe --collect "${MDATP}" health 2>/dev/null)"
fi grep -q '^healthy' <<< "${health}" || health="$("${MDATP}" health 2>/dev/null)"
# Print one `mdatp health` field without quotes or a trailing " [managed]". # Print one field of the `mdatp health` output ("name : value") without
# Unlicensed or unonboarded, mdatp prints "ATTENTION: ..." lines first. # quotes or a trailing " [managed]".
field() { field() {
raw_field "$1" | 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 ]]