Author SHA1 Message Date
Wesley van TilburgandClaude Opus 5.5 ebe1d80f80 fedora-intune: enforce Defender protection settings
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 17s
Build containers / Prune old releases and tags (pull_request) Skipped
Defender's defaults left behavior monitoring off and every setting
unmanaged. Ship /etc/opt/microsoft/mdatp/managed/mdatp_managed.json:
real-time protection, behavior monitoring, PUA blocking, cloud-delivered
protection at the high block level, automatic definition updates and
sample submission, and scheduled scans (daily quick at 12:00, weekly full
on Wednesday 12:30, low priority). Users can't change managed settings.
The build fails if the file isn't valid JSON.

Network protection (Insiders channels only) and tamper protection
(audit-only preview on Linux) are left out.

The compliance script and rules now also check behavior monitoring and
cloud-delivered protection.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:19:17 +00:00
Wesley van TilburgandClaude Opus 5.5 4213575206 fedora-intune: validate the onboarding JSON in the Defender script
A paste into Intune that lost the first "{" line produced an unreadable
mdatp_onboard.json, and Defender reported "No license found". Check that
the pasted JSON parses and has onboardingInfo before writing it, and log
what to fix otherwise.

The placeholder also appeared twice, so a find-and-replace of it broke
the script; match it by prefix so it appears only once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-20 12:10:58 +00:00
5 changed files with 13 additions and 39 deletions

No files matched your search

+2 -5
View File
@@ -34,10 +34,7 @@ add-files:
# reference: https://learn.microsoft.com/defender-endpoint/linux-preferences # reference: https://learn.microsoft.com/defender-endpoint/linux-preferences
# Not set here: network protection (Insiders channels only) and tamper # Not set here: network protection (Insiders channels only) and tamper
# protection (audit-only preview on Linux). # protection (audit-only preview on Linux).
- ["mdatp_managed.json", "/usr/share/mdatp-managed/mdatp_managed.json"] - ["mdatp_managed.json", "/etc/opt/microsoft/mdatp/managed/mdatp_managed.json"]
# Installed into /etc/opt/microsoft/mdatp/managed/ whenever Defender starts;
# see the drop-in.
- ["mdatp-managed-config.conf", "/usr/lib/systemd/system/mdatp.service.d/20-managed-config.conf"]
postprocess: postprocess:
- | - |
@@ -55,6 +52,6 @@ postprocess:
done done
semodule -n -i "${pp}" semodule -n -i "${pp}"
# 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 /etc/opt/microsoft/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 chmod 0755 /usr/libexec/mdatp-firstboot /usr/libexec/mdatp-postinst
@@ -5,23 +5,16 @@
# "44". Himmelblau reports VERSION_ID as the device's OS version and has no # "44". Himmelblau reports VERSION_ID as the device's OS version and has no
# setting for it. The rest of the system keeps the stock VERSION_ID, which # setting for it. The rest of the system keeps the stock VERSION_ID, which
# toolbox, flatpak and dnf expect to be the plain release. # toolbox, flatpak and dnf expect to be the plain release.
#
# PRETTY_NAME becomes just NAME: libhimmelblau reports "PRETTY_NAME VERSION_ID"
# as the OS version string, and Fedora's PRETTY_NAME already contains the
# version ("Fedora Linux 44.20261020.0 (Forty Four) 44.20261020.0"). Nothing
# else in Himmelblau reads PRETTY_NAME.
set -euo pipefail set -euo pipefail
SRC=/usr/lib/os-release SRC=/usr/lib/os-release
DEST=/run/himmelblau-os-release DEST=/run/himmelblau-os-release
version="$(. "${SRC}" && echo "${OSTREE_VERSION:-${IMAGE_VERSION:-}}")" version="$(. "${SRC}" && echo "${OSTREE_VERSION:-${IMAGE_VERSION:-}}")"
name="$(. "${SRC}" && echo "${NAME:-Linux}")"
tmp="$(mktemp "${DEST}.XXXXXX")" tmp="$(mktemp "${DEST}.XXXXXX")"
if [[ -n "${version}" ]]; then if [[ -n "${version}" ]]; then
sed -e "s/^VERSION_ID=.*/VERSION_ID=${version}/" \ sed "s/^VERSION_ID=.*/VERSION_ID=${version}/" "${SRC}" > "${tmp}"
-e "s/^PRETTY_NAME=.*/PRETTY_NAME=\"${name}\"/" "${SRC}" > "${tmp}"
else else
# No image version (not an ostree/bootc deployment): pass it through as-is. # No image version (not an ostree/bootc deployment): pass it through as-is.
cat "${SRC}" > "${tmp}" cat "${SRC}" > "${tmp}"
@@ -11,9 +11,6 @@ apply_policy = true
# Fedora's admin group; members of sudo_groups (Entra group object IDs) are # Fedora's admin group; members of sudo_groups (Entra group object IDs) are
# added to it. # added to it.
local_sudo_group = wheel local_sudo_group = wheel
# Linux admins: members get wheel (sudo) at sign-in, and lose it again within
# local_groups_reconcile_interval after leaving the group.
sudo_groups = c3b1cfcc-7834-47eb-948b-a22204276453
# Home directories named after the account (e.g. /home/jdoe), with a UPN alias. # Home directories named after the account (e.g. /home/jdoe), with a UPN alias.
home_attr = CN home_attr = CN
home_alias = SPN home_alias = SPN
@@ -1,7 +0,0 @@
# Install the image's enforced Defender settings each time Defender starts.
# The image can't ship them in /etc directly: mdatp-firstboot creates
# /etc/opt/microsoft/mdatp on the device, so on updates that directory counts
# as local and ostree's /etc merge never adds new files under it. Copying on
# every start also picks up changes from newer images and undoes local edits.
[Service]
ExecStartPre=+/usr/bin/install -D -Z -m 0644 /usr/share/mdatp-managed/mdatp_managed.json /etc/opt/microsoft/mdatp/managed/mdatp_managed.json
@@ -2,26 +2,20 @@
# 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 > Endpoint security > Device compliance > Scripts (Linux), # Upload in Intune > Devices > Compliance > Scripts (Linux), and pair it with
# and pair it with defender-compliance.json in a Linux compliance policy # defender-compliance.json in a Linux compliance policy (Custom compliance).
# (Custom compliance). Himmelblau runs it as root inside # Himmelblau runs it as root inside himmelblaud-tasks.service and sends the
# himmelblaud-tasks.service and sends the JSON on stdout to Intune, which # JSON on stdout to Intune, which evaluates it against the rules. Every setting
# evaluates it against the rules. Every setting is always emitted, so a missing # is always emitted, so a missing or stopped Defender reads as non-compliant
# or stopped Defender reads as non-compliant instead of as a script error. # instead of as a script error.
MDATP=/usr/bin/mdatp MDATP=/usr/bin/mdatp
# Defender won't answer `mdatp health` started from inside himmelblaud-tasks # Print one `mdatp health` field without quotes or a trailing " [managed]".
# (it can't read that process while checking the caller), so ask through # Unlicensed or unonboarded, mdatp prints "ATTENTION: ..." lines first.
# 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() {
grep -m1 "^$1 *:" <<< "${health}" \ "${MDATP}" health --field "$1" 2>/dev/null | grep -v '^ATTENTION' | tail -n1 \
| sed -e 's/^[^:]*: *//' -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//' | sed -e 's/ \[managed\]$//' -e 's/^"//' -e 's/"$//'
} }
bool() { bool() {