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..a98939f 100755 --- a/ci-changes.sh +++ b/ci-changes.sh @@ -1,6 +1,6 @@ #!/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 +# 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 @@ -13,12 +13,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