From 809bdc3bcb28d53ba4ac47493fc027cac5c05a28 Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 09:49:51 +0000 Subject: [PATCH 1/6] fedora-intune: minimal GNOME workstation with Intune and Defender New image for testing Microsoft Intune enrollment and Linux compliance policies: a minimal GNOME workstation (gnome-initial-setup, Ptyxis, Files) with the Intune app, the Microsoft identity broker, Edge and Defender for Endpoint (mdatp) from packages.microsoft.com (RHEL 10 builds, limited to those packages with includepkgs). No Kanidm: the Intune app installs its own authselect profile with pam_intune for the password compliance checks. Its %post cannot select the profile or load the SELinux module during a compose, so both are redone in postprocess. build.sh now copies every *.yaml of an image, so manifests can be split into more files than manifest.yaml and custom.yaml. Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitea/workflows/build.yaml | 28 ++++++- build.sh | 3 +- ci-changes.sh | 11 ++- images/fedora-intune/build.conf | 10 +++ images/fedora-intune/custom.yaml | 55 ++++++++++++++ images/fedora-intune/defender.yaml | 39 ++++++++++ .../fedora-intune/files/10-intune-sshd.conf | 6 ++ images/fedora-intune/files/50-defender.preset | 3 + images/fedora-intune/files/50-intune.preset | 3 + .../fedora-intune/files/50-workstation.preset | 3 + images/fedora-intune/intune.yaml | 76 +++++++++++++++++++ images/fedora-intune/manifest.yaml | 25 ++++++ images/fedora-intune/repos/microsoft.repo | 16 ++++ 13 files changed, 270 insertions(+), 8 deletions(-) create mode 100644 images/fedora-intune/build.conf create mode 100644 images/fedora-intune/custom.yaml create mode 100644 images/fedora-intune/defender.yaml create mode 100644 images/fedora-intune/files/10-intune-sshd.conf create mode 100644 images/fedora-intune/files/50-defender.preset create mode 100644 images/fedora-intune/files/50-intune.preset create mode 100644 images/fedora-intune/files/50-workstation.preset create mode 100644 images/fedora-intune/intune.yaml create mode 100644 images/fedora-intune/manifest.yaml create mode 100644 images/fedora-intune/repos/microsoft.repo diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 68d93d0..95efdb0 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -18,6 +18,7 @@ jobs: outputs: cosmic: ${{ steps.changes.outputs.cosmic }} remote: ${{ steps.changes.outputs.remote }} + intune: ${{ steps.changes.outputs.intune }} steps: - name: Checkout uses: actions/checkout@v4 @@ -40,6 +41,7 @@ jobs: eval "$(printf '%s\n' "${changed}" | ./ci-changes.sh)" echo "cosmic=${cosmic}" >> "$GITHUB_OUTPUT" echo "remote=${remote}" >> "$GITHUB_OUTPUT" + echo "intune=${intune}" >> "$GITHUB_OUTPUT" build-cosmic: name: ${{ matrix.image }} (${{ matrix.distro }}) @@ -170,10 +172,30 @@ jobs: steps: *build_steps + build-intune: + name: fedora-intune (44) + needs: changes + if: needs.changes.outputs.intune == 'true' + runs-on: job-v2 + + container: + image: "quay.io/fedora-ostree-desktops/buildroot:44" + options: "--security-opt=label=disable --privileged --user 0:0 --device=/dev/fuse --volume /:/run/host:rw" + + env: + IMAGE: fedora-intune + DISTRO: "44" + ARCH: x86_64 + REGISTRY: git.plabble.org/misthios + GITEA_URL: https://git.plabble.org + GITEA_REPO: Misthios/bootc-images + + steps: *build_steps + prune: name: Prune old releases and tags runs-on: job-v2 - needs: [changes, build-cosmic, build-remote] + needs: [changes, build-cosmic, build-remote, build-intune] # Run even if some builds were skipped or failed. if: ${{ always() && github.event_name != 'pull_request' }} container: @@ -191,7 +213,7 @@ jobs: env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - for image in fedora-cosmic fedora-remote; do + for image in fedora-cosmic fedora-remote fedora-intune; do ./prune-releases.sh "${image}" done - name: Prune old registry tags @@ -203,6 +225,6 @@ jobs: authfile=/tmp/prune-auth.json echo "${REGISTRY_TOKEN}" | skopeo login -u "${REGISTRY_USERNAME}" \ --password-stdin --authfile "${authfile}" git.plabble.org - for image in fedora-cosmic fedora-remote; do + for image in fedora-cosmic fedora-remote fedora-intune; do AUTHFILE="${authfile}" ./prune-registry.sh "${image}" Misthios 44 rawhide done diff --git a/build.sh b/build.sh index 6dddb87..c29c9bf 100755 --- a/build.sh +++ b/build.sh @@ -191,8 +191,7 @@ case "${BUILD_MODE}" in # Overlay our manifests/support files into the upstream tree so that # relative includes and *.repo discovery keep working. - cp "${IMAGE_DIR}/manifest.yaml" "${BUILD_DIR}/upstream/" - cp "${IMAGE_DIR}/custom.yaml" "${BUILD_DIR}/upstream/" + cp "${IMAGE_DIR}"/*.yaml "${BUILD_DIR}/upstream/" if compgen -G "${IMAGE_DIR}/repos/*.repo" >/dev/null; then cp "${IMAGE_DIR}"/repos/*.repo "${BUILD_DIR}/upstream/" fi diff --git a/ci-changes.sh b/ci-changes.sh index d2cfd35..3e05a0d 100755 --- a/ci-changes.sh +++ b/ci-changes.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# Given changed paths on stdin, print `cosmic=` and `remote=` so the -# workflow can skip images that didn't change. Shared files or build tooling -# changes rebuild everything; empty input (schedule/manual) rebuilds everything. +# Given changed paths on stdin, print `cosmic=`, `remote=` and +# `intune=` so the workflow can skip images that didn't change. Shared +# files or build tooling changes rebuild everything; empty input +# (schedule/manual) rebuilds everything. set -euo pipefail changed="$(cat || true)" @@ -13,12 +14,16 @@ grep -qE '^images/shared/' <<< "${changed}" && all=1 cosmic=false remote=false +intune=false if [[ "${all}" == 1 ]]; then cosmic=true remote=true + intune=true fi grep -qE '^images/fedora-cosmic/' <<< "${changed}" && cosmic=true grep -qE '^images/fedora-remote/' <<< "${changed}" && remote=true +grep -qE '^images/fedora-intune/' <<< "${changed}" && intune=true echo "cosmic=${cosmic}" echo "remote=${remote}" +echo "intune=${intune}" diff --git a/images/fedora-intune/build.conf b/images/fedora-intune/build.conf new file mode 100644 index 0000000..bc566ac --- /dev/null +++ b/images/fedora-intune/build.conf @@ -0,0 +1,10 @@ +# Build configuration for images/fedora-intune. Sourced by build.sh. + +BUILD_MODE=upstream + +UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git" + +# Stable only; Microsoft builds the Intune app against RHEL, not rawhide. +declare -A UPSTREAM_REFS=( + [44]=f44 +) diff --git a/images/fedora-intune/custom.yaml b/images/fedora-intune/custom.yaml new file mode 100644 index 0000000..cc99cea --- /dev/null +++ b/images/fedora-intune/custom.yaml @@ -0,0 +1,55 @@ +# Minimal GNOME workstation. + +packages: + # Fedora integration normally provided by the upstream fedora.yaml (which we + # opt out of to avoid the Firefox RPM). + - fedora-release + - fedora-release-ostree-desktop + - fedora-flathub-remote + + # GNOME desktop: gdm pulls gnome-session, gnome-settings-daemon, + # accountsservice, dconf and gnome-keyring-pam. gnome-initial-setup creates + # the first local user on first boot. + - gdm + - gnome-shell + - gnome-control-center + - gnome-initial-setup + - nautilus + - ptyxis + - pipewire + - pipewire-pulseaudio + - wireplumber + - xdg-desktop-portal-gnome + - xdg-user-dirs-gtk + - flatpak + + # Session infrastructure base-atomic lacks with recommends off; see + # fedora-remote/custom.yaml. + - dbus-daemon + - systemd-pam + - mesa-dri-drivers + - mesa-libEGL + + # The upstream initramfs config adds dracut's tpm2-tss module, which needs the + # tpm2 binary. fedora-remote gets it through Kanidm; list it explicitly here. + - tpm2-tools + + # Proxmox guest integration (agent, console clipboard and resizing). + - qemu-guest-agent + - spice-vdagent + +# Microsoft Edge is the browser (Conditional Access needs it). +exclude-packages: + - firefox + +add-files: + - ["50-workstation.preset", "/usr/lib/systemd/system-preset/50-workstation.preset"] + # Key-only SSH for local accounts. + - ["10-intune-sshd.conf", "/etc/ssh/sshd_config.d/10-intune-sshd.conf"] + +postprocess: + # uresourced breaks the GDM greeter's user manager; see fedora-remote. + - | + #!/usr/bin/env bash + set -xeuo pipefail + ln -sf /dev/null /etc/systemd/system/uresourced.service diff --git a/images/fedora-intune/defender.yaml b/images/fedora-intune/defender.yaml new file mode 100644 index 0000000..95ddb8d --- /dev/null +++ b/images/fedora-intune/defender.yaml @@ -0,0 +1,39 @@ +# Microsoft Defender for Endpoint on Linux (mdatp), RHEL 10 build. +# +# mdatp's %post does the real install: it writes mdatp.service and the +# mde_netfilter_v2 units, loads its SELinux module and creates a machine GUID +# and state under /var/opt/microsoft/mdatp. In a compose the daemon cannot +# start and /var is not shipped, so the units and SELinux module are handled +# here and the per-machine state is left to the first start on the device. +# +# Onboarding is per tenant and not part of the image: run the onboarding script +# from the Defender portal on the device (see the Intune test doc). + +repo-packages: + - repo: microsoft-rhel10 + packages: + - mdatp + +packages: + # Used by the Defender TVM/inventory scripts and the onboarding script. + - python3 + +add-files: + - ["50-defender.preset", "/usr/lib/systemd/system-preset/50-defender.preset"] + +postprocess: + - | + #!/usr/bin/env bash + set -xeuo pipefail + conf=/opt/microsoft/mdatp/conf + # Units the %post copies into place; install them if it did not get there. + for unit in mdatp.service mde_netfilter_v2.service mde_netfilter_v2.socket; do + if [[ ! -f "/usr/lib/systemd/system/${unit}" ]]; then + install -m 0644 "${conf}/${unit}" "/usr/lib/systemd/system/${unit}" + fi + done + semodule -n -i "${conf}/selinux_policies/out/mdatp_service.pp" + # Never ship a machine GUID or onboarding state baked in at build time. + ls -laR /etc/opt/microsoft /var/opt/microsoft 2>/dev/null || true + rm -rf /var/opt/microsoft/mdatp + rm -f /etc/opt/microsoft/mdatp/mdatp_onboard.json diff --git a/images/fedora-intune/files/10-intune-sshd.conf b/images/fedora-intune/files/10-intune-sshd.conf new file mode 100644 index 0000000..9f429e1 --- /dev/null +++ b/images/fedora-intune/files/10-intune-sshd.conf @@ -0,0 +1,6 @@ +# Key-only SSH for local accounts (add keys to ~/.ssh/authorized_keys). +PubkeyAuthentication yes +PermitRootLogin no +PasswordAuthentication no +PermitEmptyPasswords no +KbdInteractiveAuthentication no diff --git a/images/fedora-intune/files/50-defender.preset b/images/fedora-intune/files/50-defender.preset new file mode 100644 index 0000000..fb0cbb5 --- /dev/null +++ b/images/fedora-intune/files/50-defender.preset @@ -0,0 +1,3 @@ +# Defender daemon and its network filter (socket-activated). +enable mdatp.service +enable mde_netfilter_v2.socket diff --git a/images/fedora-intune/files/50-intune.preset b/images/fedora-intune/files/50-intune.preset new file mode 100644 index 0000000..bf4de3b --- /dev/null +++ b/images/fedora-intune/files/50-intune.preset @@ -0,0 +1,3 @@ +# Intune daemon (socket-activated) and the device identity broker. +enable intune-daemon.socket +enable microsoft-identity-device-broker.service diff --git a/images/fedora-intune/files/50-workstation.preset b/images/fedora-intune/files/50-workstation.preset new file mode 100644 index 0000000..5501062 --- /dev/null +++ b/images/fedora-intune/files/50-workstation.preset @@ -0,0 +1,3 @@ +enable gdm.service +enable qemu-guest-agent.service +enable sshd.service diff --git a/images/fedora-intune/intune.yaml b/images/fedora-intune/intune.yaml new file mode 100644 index 0000000..3bb1d14 --- /dev/null +++ b/images/fedora-intune/intune.yaml @@ -0,0 +1,76 @@ +# Microsoft Intune app for Linux (intune-portal), the Microsoft identity broker +# and Microsoft Edge (needed for Conditional Access to Microsoft 365 web apps). +# +# Fedora is not a supported Intune distribution. The RHEL 10 builds are used +# because they link against the same library generation as Fedora 44. The +# repos are limited to these packages with includepkgs (repos/microsoft.repo), +# so nothing else from the RHEL repo can replace a Fedora package. + +repos: + - microsoft-rhel10 + - microsoft-edge + +repo-packages: + - repo: microsoft-rhel10 + packages: + - intune-portal + - microsoft-identity-broker + - repo: microsoft-edge + packages: + - microsoft-edge-stable + +packages: + # intune-portal scriptlet dependencies (authselect profile + SELinux module) + # and the sssd base its authselect profile is derived from. + - authselect + - checkpolicy + - policycoreutils + - sssd-client + # Secret storage for the identity broker tokens. + - gnome-keyring + - libsecret + +add-files: + - ["50-intune.preset", "/usr/lib/systemd/system-preset/50-intune.preset"] + +postprocess: + # The intune-portal %post builds an authselect profile with pam_intune.so and + # an SELinux module, but neither reliably survives a compose (no running + # SELinux, authselect state under /etc). Redo both here so the image is + # deterministic either way. + - | + #!/usr/bin/env bash + set -xeuo pipefail + profile=/etc/authselect/custom/intune-profile + if [[ ! -d "${profile}" ]]; then + authselect create-profile intune-profile -b sssd --symlink-meta --symlink-pam + fi + for f in system-auth password-auth; do + grep -q pam_intune.so "${profile}/${f}" && continue + sed -i \ + -e 's/^\(auth[[:space:]]\+\)sufficient\([[:space:]]\+pam_unix.so\)/\1required \2/' \ + -e 's/^\(password[[:space:]]\+\)sufficient\([[:space:]]\+pam_unix.so\)/\1required \2/' \ + "${profile}/${f}" + sed -i \ + -e '/^auth[[:space:]]\+required[[:space:]]\+pam_unix.so/a auth sufficient pam_intune.so' \ + -e '/^password[[:space:]]\+required[[:space:]]\+pam_unix.so/a password sufficient pam_intune.so' \ + -e '/^session[[:space:]]\+required[[:space:]]\+pam_unix.so/a session optional pam_intune.so' \ + "${profile}/${f}" + done + authselect select custom/intune-profile --force --nobackup + + - | + #!/usr/bin/env bash + set -xeuo pipefail + cd /usr/share/selinux/packages/intune + tmp="$(mktemp -d)" + checkmodule -M -m -o "${tmp}/intune.mod" intune.te + semodule_package -o "${tmp}/intune.pp" -m "${tmp}/intune.mod" + semodule -n -i "${tmp}/intune.pp" + rm -rf "${tmp}" + + # intune-agent (user timer) reports compliance; enable it for every user. + - | + #!/usr/bin/env bash + set -xeuo pipefail + systemctl --user --global preset-all diff --git a/images/fedora-intune/manifest.yaml b/images/fedora-intune/manifest.yaml new file mode 100644 index 0000000..1cfc6fd --- /dev/null +++ b/images/fedora-intune/manifest.yaml @@ -0,0 +1,25 @@ +# Minimal GNOME workstation with the Microsoft Intune app, Microsoft Edge and +# Microsoft Defender for Endpoint, for testing Intune enrollment and Linux +# compliance policies. Local accounts only: no Kanidm, because the Intune app +# needs its own authselect profile (pam_intune) for the password checks. + +metadata: + summary: Fedora minimal GNOME workstation with Microsoft Intune and Defender + +variables: + # Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM. + distro: "fedora-intune" + variant: "intune" + +# Keep it small: no weak/optional dependencies. +recommends: false + +default_target: graphical.target + +ref: fedora-intune/${releasever_ref}/${basearch}/intune + +include: + - base-atomic.yaml + - custom.yaml + - intune.yaml + - defender.yaml diff --git a/images/fedora-intune/repos/microsoft.repo b/images/fedora-intune/repos/microsoft.repo new file mode 100644 index 0000000..9247f7d --- /dev/null +++ b/images/fedora-intune/repos/microsoft.repo @@ -0,0 +1,16 @@ +[microsoft-rhel10] +name=Microsoft Production (RHEL 10) +baseurl=https://packages.microsoft.com/rhel/10/prod/ +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=https://packages.microsoft.com/keys/microsoft-2025.asc +includepkgs=intune-portal microsoft-identity-broker mdatp + +[microsoft-edge] +name=Microsoft Edge +baseurl=https://packages.microsoft.com/yumrepos/edge/ +enabled=1 +gpgcheck=1 +gpgkey=https://packages.microsoft.com/keys/microsoft.asc +includepkgs=microsoft-edge-stable From 591ccd342eedb1014d9d99666c114049d8b55732 Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 09:51:54 +0000 Subject: [PATCH 2/6] ci: skip registry login for pull requests PR builds never push, and Gitea gives them no secrets, so the login with an empty token failed every PR build before it started. Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitea/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 95efdb0..140a38f 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -104,6 +104,8 @@ jobs: fetch-depth: 0 - name: Log in to registry + # Only the push step needs it, and PR builds get no secrets. + if: github.event_name != 'pull_request' env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} From 6f27bfb4dc109438e6083bb7813ff3a4d80f5dc6 Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 09:55:20 +0000 Subject: [PATCH 3/6] fedora-intune: drop no-op preset for the D-Bus activated device broker Co-Authored-By: Claude Opus 5.5 (1M context) --- images/fedora-intune/files/50-intune.preset | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/images/fedora-intune/files/50-intune.preset b/images/fedora-intune/files/50-intune.preset index bf4de3b..67f1565 100644 --- a/images/fedora-intune/files/50-intune.preset +++ b/images/fedora-intune/files/50-intune.preset @@ -1,3 +1,3 @@ -# Intune daemon (socket-activated) and the device identity broker. +# Intune daemon (socket-activated). The device identity broker is D-Bus +# activated and has no [Install] section, so it needs no preset. enable intune-daemon.socket -enable microsoft-identity-device-broker.service From 4f71d350fa6af0fb9f600f3be4a512bc4eb031f4 Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 11:06:50 +0000 Subject: [PATCH 4/6] fedora-intune: fix boot issues, add Himmelblau variant Fixes found by building and inspecting the image: - Select the Intune authselect profile with-altfiles. Without it system users in /usr/lib/passwd (gdm, pipewire, mdatp) do not resolve and the login screen never starts. The build now fails if gdm does not resolve. - Ship /opt as a plain directory (opt-usrlocal: root). Defender installs to /opt/microsoft/mdatp and keeps state in /var/opt/microsoft/mdatp; with the default /opt -> /var/opt symlink those were the same read-only directory. rpm-ostree still relocates /opt content to /usr/lib/opt, so finalize.d/60-opt-links links it back (only for images where /opt is a directory; fedora-cosmic/remote are unaffected). - Run Defender's %post on first boot (mdatp-firstboot.service): in a compose /var is read-only and the machine GUID/state must not be shared. mdatp.service waits for it. New image fedora-intune-himmelblau: the same workstation with Entra ID sign-in through Himmelblau (Community Nightly packages), which joins the device at the first sign-in and enrolls it in Intune (apply_policy) instead of the Microsoft Intune app. Workstation, Edge and Defender manifests move to images/shared so both images use them; build.sh copies images/shared/*.yaml and CI builds both images in one matrix job. Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitea/workflows/build.yaml | 13 ++-- build.sh | 5 +- ci-changes.sh | 3 +- images/fedora-intune-himmelblau/build.conf | 10 +++ .../files/50-himmelblau-user.preset | 3 + .../files/50-himmelblau.preset | 4 ++ .../files/himmelblau.conf | 17 +++++ .../fedora-intune-himmelblau/himmelblau.yaml | 68 +++++++++++++++++++ images/fedora-intune-himmelblau/manifest.yaml | 37 ++++++++++ .../repos/himmelblau.repo | 9 +++ images/fedora-intune/defender.yaml | 39 ----------- images/fedora-intune/files/50-defender.preset | 3 - images/fedora-intune/intune.yaml | 21 +++--- images/fedora-intune/manifest.yaml | 17 ++++- images/shared/defender.yaml | 46 +++++++++++++ .../files/10-workstation-sshd.conf} | 0 images/shared/files/50-defender.preset | 4 ++ .../files/50-workstation.preset | 0 images/shared/files/finalize.d/60-opt-links | 20 ++++++ images/shared/files/mdatp-firstboot | 17 +++++ images/shared/files/mdatp-firstboot.conf | 3 + images/shared/files/mdatp-firstboot.service | 15 ++++ images/shared/microsoft-edge.yaml | 7 ++ .../repos/microsoft.repo | 3 + .../custom.yaml => shared/workstation.yaml} | 5 +- 25 files changed, 304 insertions(+), 65 deletions(-) create mode 100644 images/fedora-intune-himmelblau/build.conf create mode 100644 images/fedora-intune-himmelblau/files/50-himmelblau-user.preset create mode 100644 images/fedora-intune-himmelblau/files/50-himmelblau.preset create mode 100644 images/fedora-intune-himmelblau/files/himmelblau.conf create mode 100644 images/fedora-intune-himmelblau/himmelblau.yaml create mode 100644 images/fedora-intune-himmelblau/manifest.yaml create mode 100644 images/fedora-intune-himmelblau/repos/himmelblau.repo delete mode 100644 images/fedora-intune/defender.yaml delete mode 100644 images/fedora-intune/files/50-defender.preset create mode 100644 images/shared/defender.yaml rename images/{fedora-intune/files/10-intune-sshd.conf => shared/files/10-workstation-sshd.conf} (100%) create mode 100644 images/shared/files/50-defender.preset rename images/{fedora-intune => shared}/files/50-workstation.preset (100%) create mode 100755 images/shared/files/finalize.d/60-opt-links create mode 100644 images/shared/files/mdatp-firstboot create mode 100644 images/shared/files/mdatp-firstboot.conf create mode 100644 images/shared/files/mdatp-firstboot.service create mode 100644 images/shared/microsoft-edge.yaml rename images/{fedora-intune => shared}/repos/microsoft.repo (75%) rename images/{fedora-intune/custom.yaml => shared/workstation.yaml} (86%) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 140a38f..1949247 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -175,17 +175,22 @@ jobs: steps: *build_steps build-intune: - name: fedora-intune (44) + name: ${{ matrix.image }} (44) needs: changes if: needs.changes.outputs.intune == 'true' runs-on: job-v2 + strategy: + fail-fast: false + matrix: + image: [fedora-intune, fedora-intune-himmelblau] + container: image: "quay.io/fedora-ostree-desktops/buildroot:44" options: "--security-opt=label=disable --privileged --user 0:0 --device=/dev/fuse --volume /:/run/host:rw" env: - IMAGE: fedora-intune + IMAGE: ${{ matrix.image }} DISTRO: "44" ARCH: x86_64 REGISTRY: git.plabble.org/misthios @@ -215,7 +220,7 @@ jobs: env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - for image in fedora-cosmic fedora-remote fedora-intune; do + for image in fedora-cosmic fedora-remote fedora-intune fedora-intune-himmelblau; do ./prune-releases.sh "${image}" done - name: Prune old registry tags @@ -227,6 +232,6 @@ jobs: authfile=/tmp/prune-auth.json echo "${REGISTRY_TOKEN}" | skopeo login -u "${REGISTRY_USERNAME}" \ --password-stdin --authfile "${authfile}" git.plabble.org - for image in fedora-cosmic fedora-remote fedora-intune; do + for image in fedora-cosmic fedora-remote fedora-intune fedora-intune-himmelblau; do AUTHFILE="${authfile}" ./prune-registry.sh "${image}" Misthios 44 rawhide done diff --git a/build.sh b/build.sh index c29c9bf..95027c6 100755 --- a/build.sh +++ b/build.sh @@ -201,7 +201,10 @@ case "${BUILD_MODE}" in if [[ -d "${IMAGE_DIR}/files" ]]; then cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/" fi - # Shared overlay (files/repos used by multiple images). + # Shared overlay (manifests/files/repos used by multiple images). + if compgen -G "${REPO_ROOT}/images/shared/*.yaml" >/dev/null; then + cp "${REPO_ROOT}"/images/shared/*.yaml "${BUILD_DIR}/upstream/" + fi if [[ -d "${REPO_ROOT}/images/shared/files" ]]; then cp -a "${REPO_ROOT}/images/shared/files/." "${BUILD_DIR}/upstream/" fi diff --git a/ci-changes.sh b/ci-changes.sh index 3e05a0d..b5f603b 100755 --- a/ci-changes.sh +++ b/ci-changes.sh @@ -22,7 +22,8 @@ if [[ "${all}" == 1 ]]; then fi grep -qE '^images/fedora-cosmic/' <<< "${changed}" && cosmic=true grep -qE '^images/fedora-remote/' <<< "${changed}" && remote=true -grep -qE '^images/fedora-intune/' <<< "${changed}" && intune=true +# Both fedora-intune and fedora-intune-himmelblau. +grep -qE '^images/fedora-intune' <<< "${changed}" && intune=true echo "cosmic=${cosmic}" echo "remote=${remote}" diff --git a/images/fedora-intune-himmelblau/build.conf b/images/fedora-intune-himmelblau/build.conf new file mode 100644 index 0000000..2029669 --- /dev/null +++ b/images/fedora-intune-himmelblau/build.conf @@ -0,0 +1,10 @@ +# Build configuration for images/fedora-intune-himmelblau. Sourced by build.sh. + +BUILD_MODE=upstream + +UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git" + +# Stable only, like fedora-intune. +declare -A UPSTREAM_REFS=( + [44]=f44 +) diff --git a/images/fedora-intune-himmelblau/files/50-himmelblau-user.preset b/images/fedora-intune-himmelblau/files/50-himmelblau-user.preset new file mode 100644 index 0000000..54b6ed8 --- /dev/null +++ b/images/fedora-intune-himmelblau/files/50-himmelblau-user.preset @@ -0,0 +1,3 @@ +# Intune compliance check at login and on a timer, for every user. +enable himmelblau-compliance-check.service +enable himmelblau-compliance-check.timer diff --git a/images/fedora-intune-himmelblau/files/50-himmelblau.preset b/images/fedora-intune-himmelblau/files/50-himmelblau.preset new file mode 100644 index 0000000..9822af2 --- /dev/null +++ b/images/fedora-intune-himmelblau/files/50-himmelblau.preset @@ -0,0 +1,4 @@ +# Himmelblau daemons; they pull in their sockets with Requires=. +enable himmelblaud.service +enable himmelblaud-tasks.service +enable himmelblau-hsm-pin-init.service diff --git a/images/fedora-intune-himmelblau/files/himmelblau.conf b/images/fedora-intune-himmelblau/files/himmelblau.conf new file mode 100644 index 0000000..b973b44 --- /dev/null +++ b/images/fedora-intune-himmelblau/files/himmelblau.conf @@ -0,0 +1,17 @@ +# Himmelblau configuration. See himmelblau.conf(5) and +# https://himmelblau-idm.org/docs/reference/himmelblau-conf/ +# +# No tenant settings are needed: `domain` defaults to the domain of the first +# user who signs in, and that user joins the device to Entra ID. Restrict who +# may sign in with pam_allow_groups (group object IDs or UPNs). + +[global] +# Enroll the device in Intune and apply/report compliance policies. +apply_policy = true +# Fedora's admin group; members of sudo_groups (Entra group object IDs) are +# added to it. +local_sudo_group = wheel +# Home directories named after the account (e.g. /home/jdoe), with a UPN alias. +home_attr = CN +home_alias = SPN +use_etc_skel = true diff --git a/images/fedora-intune-himmelblau/himmelblau.yaml b/images/fedora-intune-himmelblau/himmelblau.yaml new file mode 100644 index 0000000..b8b8e96 --- /dev/null +++ b/images/fedora-intune-himmelblau/himmelblau.yaml @@ -0,0 +1,68 @@ +# Himmelblau: Entra ID sign-in (PAM/NSS), Entra join, Hello PIN, Intune +# enrollment and compliance, and browser SSO. + +repo-packages: + - repo: himmelblau + packages: + - himmelblau + - pam-himmelblau + - nss-himmelblau + - himmelblau-selinux + # Shows a QR code on the login screen for the MFA/device-code prompts. + - himmelblau-qr-greeter + # Entra SSO in Edge (native messaging host) via the Himmelblau broker. + - himmelblau-broker + - himmelblau-sso + +packages: + - authselect + - nss-altfiles + # himmelblau-selinux compiles its policy module at install time. + - make + - selinux-policy-devel + +add-files: + - ["himmelblau.conf", "/etc/himmelblau/himmelblau.conf"] + - ["50-himmelblau.preset", "/usr/lib/systemd/system-preset/50-himmelblau.preset"] + - ["50-himmelblau-user.preset", "/usr/lib/systemd/user-preset/50-himmelblau-user.preset"] + +postprocess: + # The package's %post selects its authselect vendor profile but drops + # with-altfiles; without it system users (gdm, pipewire, mdatp, ...) in + # /usr/lib/passwd do not resolve and the login screen never starts. + - | + #!/usr/bin/env bash + set -xeuo pipefail + authselect select himmelblau with-altfiles with-pam-gnome-keyring --force --nobackup + for user in gdm mdatp; do + getent passwd "${user}" >/dev/null + done + grep -q pam_himmelblau.so /etc/pam.d/system-auth + + # himmelblau-selinux builds and loads its module in %posttrans, which does + # nothing in a compose; do it here. + - | + #!/usr/bin/env bash + set -xeuo pipefail + tmp="$(mktemp -d)" + cp /usr/share/selinux/packages/himmelblaud/himmelblaud.{te,if,fc} "${tmp}/" + make -C "${tmp}" -f /usr/share/selinux/devel/Makefile himmelblaud.pp + semodule -n -i "${tmp}/himmelblaud.pp" + rm -rf "${tmp}" + + # Show the QR greeter on the GDM login screen, not only in user sessions. + - | + #!/usr/bin/env bash + set -xeuo pipefail + install -d /etc/dconf/db/gdm.d + cat > /etc/dconf/db/gdm.d/01-himmelblau-qr-greeter <<'CONF' + [org/gnome/shell] + enabled-extensions=['qr-greeter@himmelblau-idm.org'] + CONF + dconf update + + # Compliance check (user timer) and broker for every user. + - | + #!/usr/bin/env bash + set -xeuo pipefail + systemctl --user --global preset-all diff --git a/images/fedora-intune-himmelblau/manifest.yaml b/images/fedora-intune-himmelblau/manifest.yaml new file mode 100644 index 0000000..dca6595 --- /dev/null +++ b/images/fedora-intune-himmelblau/manifest.yaml @@ -0,0 +1,37 @@ +# Minimal GNOME workstation where users sign in with their Entra ID account +# (Himmelblau), with Microsoft Edge and Microsoft Defender for Endpoint. +# Himmelblau joins the device to Entra ID at the first sign-in and, with +# apply_policy, enrolls it in Intune and reports compliance itself, so the +# Microsoft Intune app (fedora-intune) is not included. + +metadata: + summary: Fedora minimal GNOME workstation with Entra ID sign-in (Himmelblau) + +variables: + # Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM. + distro: "fedora-intune-himmelblau" + variant: "intune-himmelblau" + +# Keep it small: no weak/optional dependencies. +recommends: false + +default_target: graphical.target + +ref: fedora-intune-himmelblau/${releasever_ref}/${basearch}/intune-himmelblau + +# /opt as a plain directory; see fedora-intune/manifest.yaml. +opt-usrlocal: root + +repos: + - himmelblau + - microsoft-rhel10 + - microsoft-edge + +include: + - base-atomic.yaml + # Shared with fedora-intune (images/shared). + - workstation.yaml + - microsoft-edge.yaml + - defender.yaml + # Entra ID sign-in and Intune. + - himmelblau.yaml diff --git a/images/fedora-intune-himmelblau/repos/himmelblau.repo b/images/fedora-intune-himmelblau/repos/himmelblau.repo new file mode 100644 index 0000000..81bbf0b --- /dev/null +++ b/images/fedora-intune-himmelblau/repos/himmelblau.repo @@ -0,0 +1,9 @@ +# Himmelblau Community Nightly packages (free development builds). The +# maintained Stable repositories need a subscription entitlement token: +# https://himmelblau-idm.org/stable-repositories/ +[himmelblau] +name=Himmelblau Community Nightly (Fedora 44) +baseurl=https://packages.himmelblau-idm.org/nightly/latest/rpm/fedora44 +enabled=1 +gpgcheck=1 +gpgkey=https://packages.himmelblau-idm.org/himmelblau.asc diff --git a/images/fedora-intune/defender.yaml b/images/fedora-intune/defender.yaml deleted file mode 100644 index 95ddb8d..0000000 --- a/images/fedora-intune/defender.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Microsoft Defender for Endpoint on Linux (mdatp), RHEL 10 build. -# -# mdatp's %post does the real install: it writes mdatp.service and the -# mde_netfilter_v2 units, loads its SELinux module and creates a machine GUID -# and state under /var/opt/microsoft/mdatp. In a compose the daemon cannot -# start and /var is not shipped, so the units and SELinux module are handled -# here and the per-machine state is left to the first start on the device. -# -# Onboarding is per tenant and not part of the image: run the onboarding script -# from the Defender portal on the device (see the Intune test doc). - -repo-packages: - - repo: microsoft-rhel10 - packages: - - mdatp - -packages: - # Used by the Defender TVM/inventory scripts and the onboarding script. - - python3 - -add-files: - - ["50-defender.preset", "/usr/lib/systemd/system-preset/50-defender.preset"] - -postprocess: - - | - #!/usr/bin/env bash - set -xeuo pipefail - conf=/opt/microsoft/mdatp/conf - # Units the %post copies into place; install them if it did not get there. - for unit in mdatp.service mde_netfilter_v2.service mde_netfilter_v2.socket; do - if [[ ! -f "/usr/lib/systemd/system/${unit}" ]]; then - install -m 0644 "${conf}/${unit}" "/usr/lib/systemd/system/${unit}" - fi - done - semodule -n -i "${conf}/selinux_policies/out/mdatp_service.pp" - # Never ship a machine GUID or onboarding state baked in at build time. - ls -laR /etc/opt/microsoft /var/opt/microsoft 2>/dev/null || true - rm -rf /var/opt/microsoft/mdatp - rm -f /etc/opt/microsoft/mdatp/mdatp_onboard.json diff --git a/images/fedora-intune/files/50-defender.preset b/images/fedora-intune/files/50-defender.preset deleted file mode 100644 index fb0cbb5..0000000 --- a/images/fedora-intune/files/50-defender.preset +++ /dev/null @@ -1,3 +0,0 @@ -# Defender daemon and its network filter (socket-activated). -enable mdatp.service -enable mde_netfilter_v2.socket diff --git a/images/fedora-intune/intune.yaml b/images/fedora-intune/intune.yaml index 3bb1d14..4b28509 100644 --- a/images/fedora-intune/intune.yaml +++ b/images/fedora-intune/intune.yaml @@ -1,23 +1,14 @@ -# Microsoft Intune app for Linux (intune-portal), the Microsoft identity broker -# and Microsoft Edge (needed for Conditional Access to Microsoft 365 web apps). +# Microsoft Intune app for Linux (intune-portal) and the Microsoft identity +# broker, from the microsoft-rhel10 repo (shared/repos/microsoft.repo). # # Fedora is not a supported Intune distribution. The RHEL 10 builds are used -# because they link against the same library generation as Fedora 44. The -# repos are limited to these packages with includepkgs (repos/microsoft.repo), -# so nothing else from the RHEL repo can replace a Fedora package. - -repos: - - microsoft-rhel10 - - microsoft-edge +# because they link against the same library generation as Fedora 44. repo-packages: - repo: microsoft-rhel10 packages: - intune-portal - microsoft-identity-broker - - repo: microsoft-edge - packages: - - microsoft-edge-stable packages: # intune-portal scriptlet dependencies (authselect profile + SELinux module) @@ -26,6 +17,7 @@ packages: - checkpolicy - policycoreutils - sssd-client + - nss-altfiles # Secret storage for the identity broker tokens. - gnome-keyring - libsecret @@ -57,7 +49,10 @@ postprocess: -e '/^session[[:space:]]\+required[[:space:]]\+pam_unix.so/a session optional pam_intune.so' \ "${profile}/${f}" done - authselect select custom/intune-profile --force --nobackup + # with-altfiles: system users (gdm, pipewire, mdatp, ...) live in + # /usr/lib/passwd on rpm-ostree systems, not in /etc/passwd as on RHEL. + authselect select custom/intune-profile with-altfiles --force --nobackup + getent passwd gdm >/dev/null - | #!/usr/bin/env bash diff --git a/images/fedora-intune/manifest.yaml b/images/fedora-intune/manifest.yaml index 1cfc6fd..acecd60 100644 --- a/images/fedora-intune/manifest.yaml +++ b/images/fedora-intune/manifest.yaml @@ -18,8 +18,21 @@ default_target: graphical.target ref: fedora-intune/${releasever_ref}/${basearch}/intune +# Ship /opt as a plain read-only directory instead of a symlink to /var/opt. +# Defender installs to /opt/microsoft/mdatp and keeps its state in +# /var/opt/microsoft/mdatp; with the default symlink those are the same, +# read-only directory. Needs composefs, which upstream enables. +opt-usrlocal: root + +repos: + - microsoft-rhel10 + - microsoft-edge + include: - base-atomic.yaml - - custom.yaml - - intune.yaml + # Shared with fedora-intune-himmelblau (images/shared). + - workstation.yaml + - microsoft-edge.yaml - defender.yaml + # Microsoft Intune app. + - intune.yaml diff --git a/images/shared/defender.yaml b/images/shared/defender.yaml new file mode 100644 index 0000000..f1a4232 --- /dev/null +++ b/images/shared/defender.yaml @@ -0,0 +1,46 @@ +# Microsoft Defender for Endpoint on Linux (mdatp), RHEL 10 build, from the +# microsoft-rhel10 repo (shared/repos/microsoft.repo). +# +# mdatp's %post does the real install: units, SELinux module, and per-machine +# state under /var/opt/microsoft/mdatp (machine GUID, definitions database, +# logs). In a compose /var is read-only, and that state must not be shared +# between machines anyway, so the %post is saved into the image and run once on +# first boot by mdatp-firstboot.service. mdatp.service waits for it. +# +# Onboarding is per tenant and not part of the image: run the onboarding script +# from the Defender portal on the device. + +repo-packages: + - repo: microsoft-rhel10 + packages: + - mdatp + +packages: + # The %post is a bash script; Defender's inventory scripts and the + # onboarding script are Python. + - bash + - python3 + +add-files: + - ["mdatp-firstboot", "/usr/libexec/mdatp-firstboot"] + - ["mdatp-firstboot.service", "/usr/lib/systemd/system/mdatp-firstboot.service"] + - ["mdatp-firstboot.conf", "/usr/lib/systemd/system/mdatp.service.d/10-firstboot.conf"] + - ["50-defender.preset", "/usr/lib/systemd/system-preset/50-defender.preset"] + +postprocess: + - | + #!/usr/bin/env bash + set -xeuo pipefail + # The %post wrote the units during the compose; fail the build if a newer + # mdatp stops doing that, rather than shipping a Defender that never starts. + for unit in mdatp.service mde_netfilter_v2.service mde_netfilter_v2.socket; do + test -f "/usr/lib/systemd/system/${unit}" + done + # rpm-ostree keeps /opt content in /usr/lib/opt until the commit. + for root in /usr/lib/opt /opt; do + pp="${root}/microsoft/mdatp/conf/selinux_policies/out/mdatp_service.pp" + [[ -f "${pp}" ]] && break + done + semodule -n -i "${pp}" + rpm -q --qf '%{POSTIN}' mdatp > /usr/libexec/mdatp-postinst + chmod 0755 /usr/libexec/mdatp-firstboot /usr/libexec/mdatp-postinst diff --git a/images/fedora-intune/files/10-intune-sshd.conf b/images/shared/files/10-workstation-sshd.conf similarity index 100% rename from images/fedora-intune/files/10-intune-sshd.conf rename to images/shared/files/10-workstation-sshd.conf diff --git a/images/shared/files/50-defender.preset b/images/shared/files/50-defender.preset new file mode 100644 index 0000000..dec91a5 --- /dev/null +++ b/images/shared/files/50-defender.preset @@ -0,0 +1,4 @@ +# Defender: first-boot setup, the daemon and its network filter. +enable mdatp-firstboot.service +enable mdatp.service +enable mde_netfilter_v2.socket diff --git a/images/fedora-intune/files/50-workstation.preset b/images/shared/files/50-workstation.preset similarity index 100% rename from images/fedora-intune/files/50-workstation.preset rename to images/shared/files/50-workstation.preset diff --git a/images/shared/files/finalize.d/60-opt-links b/images/shared/files/finalize.d/60-opt-links new file mode 100755 index 0000000..0eed2b7 --- /dev/null +++ b/images/shared/files/finalize.d/60-opt-links @@ -0,0 +1,20 @@ +#!/bin/bash +# Runs in the target root during `rpm-ostree compose image` (edition 2024 +# finalize.d hook). rpm-ostree relocates RPM content under /opt to +# /usr/lib/opt. With `opt-usrlocal: root` /opt is a plain directory in the +# image and nothing links it back, so link each vendor directory. Images on the +# default (/opt -> /var/opt, linked by tmpfiles.d) are left alone. +set -euo pipefail + +root="${PWD}" + +[[ -d "${root}/usr/lib/opt" ]] || exit 0 +if [[ -L "${root}/opt" || ! -d "${root}/opt" ]]; then + exit 0 +fi + +for dir in "${root}"/usr/lib/opt/*; do + name="$(basename "${dir}")" + ln -sfn "../usr/lib/opt/${name}" "${root}/opt/${name}" + echo "Linked /opt/${name} -> /usr/lib/opt/${name}" +done diff --git a/images/shared/files/mdatp-firstboot b/images/shared/files/mdatp-firstboot new file mode 100644 index 0000000..2659d11 --- /dev/null +++ b/images/shared/files/mdatp-firstboot @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Finish the Defender (mdatp) install on first boot by running the package's +# own %post, saved at build time: it creates the machine GUID, state, logs and +# definitions database under /var and then enables and starts mdatp.service. +set -euo pipefail + +MARKER=/var/lib/mdatp-firstboot.done + +[[ -f "${MARKER}" ]] && exit 0 + +# mdatp.service only starts once the marker exists (10-firstboot.conf), and the +# %post starts it, so create the marker first and drop it again on failure. +touch "${MARKER}" +if ! bash /usr/libexec/mdatp-postinst 1; then + rm -f "${MARKER}" + exit 1 +fi diff --git a/images/shared/files/mdatp-firstboot.conf b/images/shared/files/mdatp-firstboot.conf new file mode 100644 index 0000000..32b84b9 --- /dev/null +++ b/images/shared/files/mdatp-firstboot.conf @@ -0,0 +1,3 @@ +# Don't start Defender before mdatp-firstboot.service has created its state. +[Unit] +ConditionPathExists=/var/lib/mdatp-firstboot.done diff --git a/images/shared/files/mdatp-firstboot.service b/images/shared/files/mdatp-firstboot.service new file mode 100644 index 0000000..44b2d97 --- /dev/null +++ b/images/shared/files/mdatp-firstboot.service @@ -0,0 +1,15 @@ +[Unit] +Description=Finish the Microsoft Defender for Endpoint install on first boot +Documentation=https://learn.microsoft.com/en-us/defender-endpoint/microsoft-defender-endpoint-linux +After=network-online.target +Wants=network-online.target +ConditionPathExists=!/var/lib/mdatp-firstboot.done + +[Service] +Type=oneshot +ExecStart=/usr/libexec/mdatp-firstboot +RemainAfterExit=yes +TimeoutStartSec=15min + +[Install] +WantedBy=multi-user.target diff --git a/images/shared/microsoft-edge.yaml b/images/shared/microsoft-edge.yaml new file mode 100644 index 0000000..fefb37f --- /dev/null +++ b/images/shared/microsoft-edge.yaml @@ -0,0 +1,7 @@ +# Microsoft Edge, from the microsoft-edge repo (shared/repos/microsoft.repo). +# Conditional Access to Microsoft 365 web apps on Linux requires Edge. + +repo-packages: + - repo: microsoft-edge + packages: + - microsoft-edge-stable diff --git a/images/fedora-intune/repos/microsoft.repo b/images/shared/repos/microsoft.repo similarity index 75% rename from images/fedora-intune/repos/microsoft.repo rename to images/shared/repos/microsoft.repo index 9247f7d..7ae2a91 100644 --- a/images/fedora-intune/repos/microsoft.repo +++ b/images/shared/repos/microsoft.repo @@ -1,3 +1,6 @@ +# packages.microsoft.com, limited with includepkgs to the packages the images +# use, so nothing else from the RHEL repo can replace a Fedora package. + [microsoft-rhel10] name=Microsoft Production (RHEL 10) baseurl=https://packages.microsoft.com/rhel/10/prod/ diff --git a/images/fedora-intune/custom.yaml b/images/shared/workstation.yaml similarity index 86% rename from images/fedora-intune/custom.yaml rename to images/shared/workstation.yaml index cc99cea..3d66f76 100644 --- a/images/fedora-intune/custom.yaml +++ b/images/shared/workstation.yaml @@ -1,4 +1,5 @@ -# Minimal GNOME workstation. +# Minimal GNOME workstation, shared by the fedora-intune images. Include it +# from a manifest that sets `opt-usrlocal: root` (finalize.d/60-opt-links). packages: # Fedora integration normally provided by the upstream fedora.yaml (which we @@ -45,7 +46,7 @@ exclude-packages: add-files: - ["50-workstation.preset", "/usr/lib/systemd/system-preset/50-workstation.preset"] # Key-only SSH for local accounts. - - ["10-intune-sshd.conf", "/etc/ssh/sshd_config.d/10-intune-sshd.conf"] + - ["10-workstation-sshd.conf", "/etc/ssh/sshd_config.d/10-workstation-sshd.conf"] postprocess: # uresourced breaks the GDM greeter's user manager; see fedora-remote. From 8e6fedc05eaf7f2699d16392e77989a9570b168f Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 11:07:54 +0000 Subject: [PATCH 5/6] fedora-intune: use Himmelblau instead of the Microsoft Intune app Go with Entra ID sign-in through Himmelblau for fedora-intune and drop the Microsoft Intune app (intune-portal, identity broker) variant. With a single image, the workstation, Edge and Defender manifests move back from images/shared into images/fedora-intune, and CI builds just that image again. The Microsoft repo is now limited to mdatp and Edge. Co-Authored-By: Claude Opus 5.5 (1M context) --- .gitea/workflows/build.yaml | 13 ++-- build.sh | 5 +- ci-changes.sh | 3 +- images/fedora-intune-himmelblau/build.conf | 10 --- images/fedora-intune-himmelblau/manifest.yaml | 37 ---------- images/fedora-intune/build.conf | 2 +- .../custom.yaml} | 3 +- .../{shared => fedora-intune}/defender.yaml | 2 +- .../files/10-workstation-sshd.conf | 0 .../files/50-defender.preset | 0 .../files/50-himmelblau-user.preset | 0 .../files/50-himmelblau.preset | 0 images/fedora-intune/files/50-intune.preset | 3 - .../files/50-workstation.preset | 0 .../files/himmelblau.conf | 0 .../files/mdatp-firstboot | 0 .../files/mdatp-firstboot.conf | 0 .../files/mdatp-firstboot.service | 0 .../himmelblau.yaml | 0 images/fedora-intune/intune.yaml | 71 ------------------- images/fedora-intune/manifest.yaml | 21 +++--- .../microsoft-edge.yaml | 2 +- .../repos/himmelblau.repo | 0 .../repos/microsoft.repo | 6 +- 24 files changed, 24 insertions(+), 154 deletions(-) delete mode 100644 images/fedora-intune-himmelblau/build.conf delete mode 100644 images/fedora-intune-himmelblau/manifest.yaml rename images/{shared/workstation.yaml => fedora-intune/custom.yaml} (91%) rename images/{shared => fedora-intune}/defender.yaml (97%) rename images/{shared => fedora-intune}/files/10-workstation-sshd.conf (100%) rename images/{shared => fedora-intune}/files/50-defender.preset (100%) rename images/{fedora-intune-himmelblau => fedora-intune}/files/50-himmelblau-user.preset (100%) rename images/{fedora-intune-himmelblau => fedora-intune}/files/50-himmelblau.preset (100%) delete mode 100644 images/fedora-intune/files/50-intune.preset rename images/{shared => fedora-intune}/files/50-workstation.preset (100%) rename images/{fedora-intune-himmelblau => fedora-intune}/files/himmelblau.conf (100%) rename images/{shared => fedora-intune}/files/mdatp-firstboot (100%) rename images/{shared => fedora-intune}/files/mdatp-firstboot.conf (100%) rename images/{shared => fedora-intune}/files/mdatp-firstboot.service (100%) rename images/{fedora-intune-himmelblau => fedora-intune}/himmelblau.yaml (100%) delete mode 100644 images/fedora-intune/intune.yaml rename images/{shared => fedora-intune}/microsoft-edge.yaml (66%) rename images/{fedora-intune-himmelblau => fedora-intune}/repos/himmelblau.repo (100%) rename images/{shared => fedora-intune}/repos/microsoft.repo (76%) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 1949247..140a38f 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -175,22 +175,17 @@ jobs: steps: *build_steps build-intune: - name: ${{ matrix.image }} (44) + name: fedora-intune (44) needs: changes if: needs.changes.outputs.intune == 'true' runs-on: job-v2 - strategy: - fail-fast: false - matrix: - image: [fedora-intune, fedora-intune-himmelblau] - container: image: "quay.io/fedora-ostree-desktops/buildroot:44" options: "--security-opt=label=disable --privileged --user 0:0 --device=/dev/fuse --volume /:/run/host:rw" env: - IMAGE: ${{ matrix.image }} + IMAGE: fedora-intune DISTRO: "44" ARCH: x86_64 REGISTRY: git.plabble.org/misthios @@ -220,7 +215,7 @@ jobs: env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} run: | - for image in fedora-cosmic fedora-remote fedora-intune fedora-intune-himmelblau; do + for image in fedora-cosmic fedora-remote fedora-intune; do ./prune-releases.sh "${image}" done - name: Prune old registry tags @@ -232,6 +227,6 @@ jobs: authfile=/tmp/prune-auth.json echo "${REGISTRY_TOKEN}" | skopeo login -u "${REGISTRY_USERNAME}" \ --password-stdin --authfile "${authfile}" git.plabble.org - for image in fedora-cosmic fedora-remote fedora-intune fedora-intune-himmelblau; do + for image in fedora-cosmic fedora-remote fedora-intune; do AUTHFILE="${authfile}" ./prune-registry.sh "${image}" Misthios 44 rawhide done diff --git a/build.sh b/build.sh index 95027c6..c29c9bf 100755 --- a/build.sh +++ b/build.sh @@ -201,10 +201,7 @@ case "${BUILD_MODE}" in if [[ -d "${IMAGE_DIR}/files" ]]; then cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/" fi - # Shared overlay (manifests/files/repos used by multiple images). - if compgen -G "${REPO_ROOT}/images/shared/*.yaml" >/dev/null; then - cp "${REPO_ROOT}"/images/shared/*.yaml "${BUILD_DIR}/upstream/" - fi + # Shared overlay (files/repos used by multiple images). if [[ -d "${REPO_ROOT}/images/shared/files" ]]; then cp -a "${REPO_ROOT}/images/shared/files/." "${BUILD_DIR}/upstream/" fi diff --git a/ci-changes.sh b/ci-changes.sh index b5f603b..3e05a0d 100755 --- a/ci-changes.sh +++ b/ci-changes.sh @@ -22,8 +22,7 @@ if [[ "${all}" == 1 ]]; then fi grep -qE '^images/fedora-cosmic/' <<< "${changed}" && cosmic=true grep -qE '^images/fedora-remote/' <<< "${changed}" && remote=true -# Both fedora-intune and fedora-intune-himmelblau. -grep -qE '^images/fedora-intune' <<< "${changed}" && intune=true +grep -qE '^images/fedora-intune/' <<< "${changed}" && intune=true echo "cosmic=${cosmic}" echo "remote=${remote}" diff --git a/images/fedora-intune-himmelblau/build.conf b/images/fedora-intune-himmelblau/build.conf deleted file mode 100644 index 2029669..0000000 --- a/images/fedora-intune-himmelblau/build.conf +++ /dev/null @@ -1,10 +0,0 @@ -# Build configuration for images/fedora-intune-himmelblau. Sourced by build.sh. - -BUILD_MODE=upstream - -UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git" - -# Stable only, like fedora-intune. -declare -A UPSTREAM_REFS=( - [44]=f44 -) diff --git a/images/fedora-intune-himmelblau/manifest.yaml b/images/fedora-intune-himmelblau/manifest.yaml deleted file mode 100644 index dca6595..0000000 --- a/images/fedora-intune-himmelblau/manifest.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Minimal GNOME workstation where users sign in with their Entra ID account -# (Himmelblau), with Microsoft Edge and Microsoft Defender for Endpoint. -# Himmelblau joins the device to Entra ID at the first sign-in and, with -# apply_policy, enrolls it in Intune and reports compliance itself, so the -# Microsoft Intune app (fedora-intune) is not included. - -metadata: - summary: Fedora minimal GNOME workstation with Entra ID sign-in (Himmelblau) - -variables: - # Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM. - distro: "fedora-intune-himmelblau" - variant: "intune-himmelblau" - -# Keep it small: no weak/optional dependencies. -recommends: false - -default_target: graphical.target - -ref: fedora-intune-himmelblau/${releasever_ref}/${basearch}/intune-himmelblau - -# /opt as a plain directory; see fedora-intune/manifest.yaml. -opt-usrlocal: root - -repos: - - himmelblau - - microsoft-rhel10 - - microsoft-edge - -include: - - base-atomic.yaml - # Shared with fedora-intune (images/shared). - - workstation.yaml - - microsoft-edge.yaml - - defender.yaml - # Entra ID sign-in and Intune. - - himmelblau.yaml diff --git a/images/fedora-intune/build.conf b/images/fedora-intune/build.conf index bc566ac..967992b 100644 --- a/images/fedora-intune/build.conf +++ b/images/fedora-intune/build.conf @@ -4,7 +4,7 @@ BUILD_MODE=upstream UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git" -# Stable only; Microsoft builds the Intune app against RHEL, not rawhide. +# Stable only; Defender comes from Microsoft's RHEL 10 repo, not rawhide. declare -A UPSTREAM_REFS=( [44]=f44 ) diff --git a/images/shared/workstation.yaml b/images/fedora-intune/custom.yaml similarity index 91% rename from images/shared/workstation.yaml rename to images/fedora-intune/custom.yaml index 3d66f76..4faefce 100644 --- a/images/shared/workstation.yaml +++ b/images/fedora-intune/custom.yaml @@ -1,5 +1,4 @@ -# Minimal GNOME workstation, shared by the fedora-intune images. Include it -# from a manifest that sets `opt-usrlocal: root` (finalize.d/60-opt-links). +# Minimal GNOME workstation. packages: # Fedora integration normally provided by the upstream fedora.yaml (which we diff --git a/images/shared/defender.yaml b/images/fedora-intune/defender.yaml similarity index 97% rename from images/shared/defender.yaml rename to images/fedora-intune/defender.yaml index f1a4232..801d529 100644 --- a/images/shared/defender.yaml +++ b/images/fedora-intune/defender.yaml @@ -1,5 +1,5 @@ # Microsoft Defender for Endpoint on Linux (mdatp), RHEL 10 build, from the -# microsoft-rhel10 repo (shared/repos/microsoft.repo). +# microsoft-rhel10 repo (repos/microsoft.repo). # # mdatp's %post does the real install: units, SELinux module, and per-machine # state under /var/opt/microsoft/mdatp (machine GUID, definitions database, diff --git a/images/shared/files/10-workstation-sshd.conf b/images/fedora-intune/files/10-workstation-sshd.conf similarity index 100% rename from images/shared/files/10-workstation-sshd.conf rename to images/fedora-intune/files/10-workstation-sshd.conf diff --git a/images/shared/files/50-defender.preset b/images/fedora-intune/files/50-defender.preset similarity index 100% rename from images/shared/files/50-defender.preset rename to images/fedora-intune/files/50-defender.preset diff --git a/images/fedora-intune-himmelblau/files/50-himmelblau-user.preset b/images/fedora-intune/files/50-himmelblau-user.preset similarity index 100% rename from images/fedora-intune-himmelblau/files/50-himmelblau-user.preset rename to images/fedora-intune/files/50-himmelblau-user.preset diff --git a/images/fedora-intune-himmelblau/files/50-himmelblau.preset b/images/fedora-intune/files/50-himmelblau.preset similarity index 100% rename from images/fedora-intune-himmelblau/files/50-himmelblau.preset rename to images/fedora-intune/files/50-himmelblau.preset diff --git a/images/fedora-intune/files/50-intune.preset b/images/fedora-intune/files/50-intune.preset deleted file mode 100644 index 67f1565..0000000 --- a/images/fedora-intune/files/50-intune.preset +++ /dev/null @@ -1,3 +0,0 @@ -# Intune daemon (socket-activated). The device identity broker is D-Bus -# activated and has no [Install] section, so it needs no preset. -enable intune-daemon.socket diff --git a/images/shared/files/50-workstation.preset b/images/fedora-intune/files/50-workstation.preset similarity index 100% rename from images/shared/files/50-workstation.preset rename to images/fedora-intune/files/50-workstation.preset diff --git a/images/fedora-intune-himmelblau/files/himmelblau.conf b/images/fedora-intune/files/himmelblau.conf similarity index 100% rename from images/fedora-intune-himmelblau/files/himmelblau.conf rename to images/fedora-intune/files/himmelblau.conf diff --git a/images/shared/files/mdatp-firstboot b/images/fedora-intune/files/mdatp-firstboot similarity index 100% rename from images/shared/files/mdatp-firstboot rename to images/fedora-intune/files/mdatp-firstboot diff --git a/images/shared/files/mdatp-firstboot.conf b/images/fedora-intune/files/mdatp-firstboot.conf similarity index 100% rename from images/shared/files/mdatp-firstboot.conf rename to images/fedora-intune/files/mdatp-firstboot.conf diff --git a/images/shared/files/mdatp-firstboot.service b/images/fedora-intune/files/mdatp-firstboot.service similarity index 100% rename from images/shared/files/mdatp-firstboot.service rename to images/fedora-intune/files/mdatp-firstboot.service diff --git a/images/fedora-intune-himmelblau/himmelblau.yaml b/images/fedora-intune/himmelblau.yaml similarity index 100% rename from images/fedora-intune-himmelblau/himmelblau.yaml rename to images/fedora-intune/himmelblau.yaml diff --git a/images/fedora-intune/intune.yaml b/images/fedora-intune/intune.yaml deleted file mode 100644 index 4b28509..0000000 --- a/images/fedora-intune/intune.yaml +++ /dev/null @@ -1,71 +0,0 @@ -# Microsoft Intune app for Linux (intune-portal) and the Microsoft identity -# broker, from the microsoft-rhel10 repo (shared/repos/microsoft.repo). -# -# Fedora is not a supported Intune distribution. The RHEL 10 builds are used -# because they link against the same library generation as Fedora 44. - -repo-packages: - - repo: microsoft-rhel10 - packages: - - intune-portal - - microsoft-identity-broker - -packages: - # intune-portal scriptlet dependencies (authselect profile + SELinux module) - # and the sssd base its authselect profile is derived from. - - authselect - - checkpolicy - - policycoreutils - - sssd-client - - nss-altfiles - # Secret storage for the identity broker tokens. - - gnome-keyring - - libsecret - -add-files: - - ["50-intune.preset", "/usr/lib/systemd/system-preset/50-intune.preset"] - -postprocess: - # The intune-portal %post builds an authselect profile with pam_intune.so and - # an SELinux module, but neither reliably survives a compose (no running - # SELinux, authselect state under /etc). Redo both here so the image is - # deterministic either way. - - | - #!/usr/bin/env bash - set -xeuo pipefail - profile=/etc/authselect/custom/intune-profile - if [[ ! -d "${profile}" ]]; then - authselect create-profile intune-profile -b sssd --symlink-meta --symlink-pam - fi - for f in system-auth password-auth; do - grep -q pam_intune.so "${profile}/${f}" && continue - sed -i \ - -e 's/^\(auth[[:space:]]\+\)sufficient\([[:space:]]\+pam_unix.so\)/\1required \2/' \ - -e 's/^\(password[[:space:]]\+\)sufficient\([[:space:]]\+pam_unix.so\)/\1required \2/' \ - "${profile}/${f}" - sed -i \ - -e '/^auth[[:space:]]\+required[[:space:]]\+pam_unix.so/a auth sufficient pam_intune.so' \ - -e '/^password[[:space:]]\+required[[:space:]]\+pam_unix.so/a password sufficient pam_intune.so' \ - -e '/^session[[:space:]]\+required[[:space:]]\+pam_unix.so/a session optional pam_intune.so' \ - "${profile}/${f}" - done - # with-altfiles: system users (gdm, pipewire, mdatp, ...) live in - # /usr/lib/passwd on rpm-ostree systems, not in /etc/passwd as on RHEL. - authselect select custom/intune-profile with-altfiles --force --nobackup - getent passwd gdm >/dev/null - - - | - #!/usr/bin/env bash - set -xeuo pipefail - cd /usr/share/selinux/packages/intune - tmp="$(mktemp -d)" - checkmodule -M -m -o "${tmp}/intune.mod" intune.te - semodule_package -o "${tmp}/intune.pp" -m "${tmp}/intune.mod" - semodule -n -i "${tmp}/intune.pp" - rm -rf "${tmp}" - - # intune-agent (user timer) reports compliance; enable it for every user. - - | - #!/usr/bin/env bash - set -xeuo pipefail - systemctl --user --global preset-all diff --git a/images/fedora-intune/manifest.yaml b/images/fedora-intune/manifest.yaml index acecd60..84107b0 100644 --- a/images/fedora-intune/manifest.yaml +++ b/images/fedora-intune/manifest.yaml @@ -1,10 +1,10 @@ -# Minimal GNOME workstation with the Microsoft Intune app, Microsoft Edge and -# Microsoft Defender for Endpoint, for testing Intune enrollment and Linux -# compliance policies. Local accounts only: no Kanidm, because the Intune app -# needs its own authselect profile (pam_intune) for the password checks. +# Minimal GNOME workstation where users sign in with their Entra ID account +# (Himmelblau), with Microsoft Edge and Microsoft Defender for Endpoint. +# Himmelblau joins the device to Entra ID at the first sign-in and, with +# apply_policy, enrolls it in Intune and reports compliance. metadata: - summary: Fedora minimal GNOME workstation with Microsoft Intune and Defender + summary: Fedora minimal GNOME workstation with Entra ID sign-in and Intune variables: # Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM. @@ -21,18 +21,19 @@ ref: fedora-intune/${releasever_ref}/${basearch}/intune # Ship /opt as a plain read-only directory instead of a symlink to /var/opt. # Defender installs to /opt/microsoft/mdatp and keeps its state in # /var/opt/microsoft/mdatp; with the default symlink those are the same, -# read-only directory. Needs composefs, which upstream enables. +# read-only directory. Needs composefs, which upstream enables. The vendor +# directories are linked into /opt by finalize.d/60-opt-links. opt-usrlocal: root repos: + - himmelblau - microsoft-rhel10 - microsoft-edge include: - base-atomic.yaml - # Shared with fedora-intune-himmelblau (images/shared). - - workstation.yaml + - custom.yaml + # Entra ID sign-in and Intune. + - himmelblau.yaml - microsoft-edge.yaml - defender.yaml - # Microsoft Intune app. - - intune.yaml diff --git a/images/shared/microsoft-edge.yaml b/images/fedora-intune/microsoft-edge.yaml similarity index 66% rename from images/shared/microsoft-edge.yaml rename to images/fedora-intune/microsoft-edge.yaml index fefb37f..a61069a 100644 --- a/images/shared/microsoft-edge.yaml +++ b/images/fedora-intune/microsoft-edge.yaml @@ -1,4 +1,4 @@ -# Microsoft Edge, from the microsoft-edge repo (shared/repos/microsoft.repo). +# Microsoft Edge, from the microsoft-edge repo (repos/microsoft.repo). # Conditional Access to Microsoft 365 web apps on Linux requires Edge. repo-packages: diff --git a/images/fedora-intune-himmelblau/repos/himmelblau.repo b/images/fedora-intune/repos/himmelblau.repo similarity index 100% rename from images/fedora-intune-himmelblau/repos/himmelblau.repo rename to images/fedora-intune/repos/himmelblau.repo diff --git a/images/shared/repos/microsoft.repo b/images/fedora-intune/repos/microsoft.repo similarity index 76% rename from images/shared/repos/microsoft.repo rename to images/fedora-intune/repos/microsoft.repo index 7ae2a91..3c6438f 100644 --- a/images/shared/repos/microsoft.repo +++ b/images/fedora-intune/repos/microsoft.repo @@ -1,5 +1,5 @@ -# packages.microsoft.com, limited with includepkgs to the packages the images -# use, so nothing else from the RHEL repo can replace a Fedora package. +# packages.microsoft.com, limited with includepkgs to the packages the image +# uses, so nothing else from the RHEL repo can replace a Fedora package. [microsoft-rhel10] name=Microsoft Production (RHEL 10) @@ -8,7 +8,7 @@ enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.microsoft.com/keys/microsoft-2025.asc -includepkgs=intune-portal microsoft-identity-broker mdatp +includepkgs=mdatp [microsoft-edge] name=Microsoft Edge From ad585699689d35b45e3dafe58642e23067bd3016 Mon Sep 17 00:00:00 2001 From: Misthios Date: Mon, 19 Oct 2026 11:19:46 +0000 Subject: [PATCH 6/6] fedora-intune: Entra SSO in Microsoft Edge himmelblau-sso registers its native messaging host and force-installs the Linux Entra SSO extension only for Chrome, Chromium and Firefox. Edge reads /etc/opt/edge, so ship the same two files there. Co-Authored-By: Claude Opus 5.5 (1M context) --- images/fedora-intune/files/edge-himmelblau-policy.json | 5 +++++ images/fedora-intune/files/edge-linux_entra_sso.json | 9 +++++++++ images/fedora-intune/microsoft-edge.yaml | 8 ++++++++ 3 files changed, 22 insertions(+) create mode 100644 images/fedora-intune/files/edge-himmelblau-policy.json create mode 100644 images/fedora-intune/files/edge-linux_entra_sso.json diff --git a/images/fedora-intune/files/edge-himmelblau-policy.json b/images/fedora-intune/files/edge-himmelblau-policy.json new file mode 100644 index 0000000..0dbdbcf --- /dev/null +++ b/images/fedora-intune/files/edge-himmelblau-policy.json @@ -0,0 +1,5 @@ +{ + "ExtensionInstallForcelist": [ + "jlnfnnolkbjieggibinobhkjdfbpcohn;https://clients2.google.com/service/update2/crx" + ] +} diff --git a/images/fedora-intune/files/edge-linux_entra_sso.json b/images/fedora-intune/files/edge-linux_entra_sso.json new file mode 100644 index 0000000..50a0e32 --- /dev/null +++ b/images/fedora-intune/files/edge-linux_entra_sso.json @@ -0,0 +1,9 @@ +{ + "name": "linux_entra_sso", + "description": "Entra ID SSO via Microsoft Identity Broker", + "path": "/usr/bin/linux-entra-sso", + "type": "stdio", + "allowed_origins": [ + "chrome-extension://jlnfnnolkbjieggibinobhkjdfbpcohn/" + ] +} diff --git a/images/fedora-intune/microsoft-edge.yaml b/images/fedora-intune/microsoft-edge.yaml index a61069a..1b6530d 100644 --- a/images/fedora-intune/microsoft-edge.yaml +++ b/images/fedora-intune/microsoft-edge.yaml @@ -1,7 +1,15 @@ # Microsoft Edge, from the microsoft-edge repo (repos/microsoft.repo). # Conditional Access to Microsoft 365 web apps on Linux requires Edge. +# +# Entra SSO in Edge: himmelblau-sso only registers its native messaging host +# and force-installs the Linux Entra SSO extension for Chrome, Chromium and +# Firefox. Edge reads /etc/opt/edge, so add the same two files there. repo-packages: - repo: microsoft-edge packages: - microsoft-edge-stable + +add-files: + - ["edge-linux_entra_sso.json", "/etc/opt/edge/native-messaging-hosts/linux_entra_sso.json"] + - ["edge-himmelblau-policy.json", "/etc/opt/edge/policies/managed/himmelblau.json"]