diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index add4905..b129dd1 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -29,6 +29,10 @@ jobs: distro: rawhide arch: x86_64 runner: job-v2 + - image: fedora-remote + distro: "44" + arch: x86_64 + runner: job-v2 container: image: "quay.io/fedora-ostree-desktops/buildroot:${{ matrix.distro }}" @@ -141,7 +145,10 @@ jobs: - name: Prune old releases env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} - run: ./prune-releases.sh fedora-cosmic + run: | + for image in fedora-cosmic fedora-remote; do + ./prune-releases.sh "${image}" + done - name: Prune old registry tags env: REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} @@ -151,4 +158,6 @@ jobs: authfile=/tmp/prune-auth.json echo "${REGISTRY_TOKEN}" | skopeo login -u "${REGISTRY_USERNAME}" \ --password-stdin --authfile "${authfile}" git.plabble.org - AUTHFILE="${authfile}" ./prune-registry.sh fedora-cosmic Misthios 44 rawhide + for image in fedora-cosmic fedora-remote; do + AUTHFILE="${authfile}" ./prune-registry.sh "${image}" Misthios 44 rawhide + done diff --git a/images/fedora-remote/build.conf b/images/fedora-remote/build.conf new file mode 100644 index 0000000..0c26e63 --- /dev/null +++ b/images/fedora-remote/build.conf @@ -0,0 +1,10 @@ +# Build configuration for images/fedora-remote. Sourced by build.sh. + +BUILD_MODE=upstream + +UPSTREAM_REPO="https://gitlab.com/fedora/ostree/ci-test.git" + +# Stable only; this is a small remote-access host. +declare -A UPSTREAM_REFS=( + [44]=f44 +) diff --git a/images/fedora-remote/custom.yaml b/images/fedora-remote/custom.yaml new file mode 100644 index 0000000..8a82a6b --- /dev/null +++ b/images/fedora-remote/custom.yaml @@ -0,0 +1,89 @@ +# Customizations for the minimal headless remote host. + +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 Remote Desktop headless remote login. gdm pulls gnome-session, + # gnome-settings-daemon, accountsservice, dconf and gnome-keyring-pam. + - gdm + - gnome-shell + - gnome-remote-desktop + - pipewire + - wireplumber + - xdg-desktop-portal-gnome + + # Terminal (foot has no Flathub build) and Flatpak. + - foot + - flatpak + + # Kanidm Unix authentication (kanidm-unixd-clients pulls kanidm-clients). + - kanidm-unixd-clients + +# Firefox is shipped as a per-user Flatpak, not an RPM. +exclude-packages: + - firefox + +add-files: + # Per-user Flatpaks on first login (Firefox). + - ["flatpak-user-firstboot", "/usr/libexec/flatpak-user-firstboot"] + - ["flatpak-user-firstboot.service", "/usr/lib/systemd/user/flatpak-user-firstboot.service"] + - ["60-flatpak-user-firstboot.preset", "/usr/lib/systemd/user-preset/60-flatpak-user-firstboot.preset"] + - ["flatpaks.list", "/usr/share/flatpak/flatpaks.list"] + # Kanidm client config + authselect profile sources. + - ["kanidm-config", "/etc/kanidm/config"] + - ["kanidm-unixd", "/etc/kanidm/unixd"] + - ["kanidm-system-auth", "/usr/share/fedora-remote/authselect/system-auth"] + - ["kanidm-password-auth", "/usr/share/fedora-remote/authselect/password-auth"] + - ["kanidm-nsswitch.conf", "/usr/share/fedora-remote/authselect/nsswitch.conf"] + - ["50-kanidm.preset", "/usr/lib/systemd/system-preset/50-kanidm.preset"] + - ["kanidm-unixd.cil", "/usr/share/fedora-remote/kanidm-unixd.cil"] + - ["10-kanidm.conf", "/etc/ssh/sshd_config.d/10-kanidm.conf"] + # GNOME Remote Desktop first-boot configuration. + - ["grd-firstboot", "/usr/libexec/grd-firstboot"] + - ["grd-firstboot.service", "/usr/lib/systemd/system/grd-firstboot.service"] + - ["50-grd.preset", "/usr/lib/systemd/system-preset/50-grd.preset"] + +postprocess: + # Kanidm PAM/nsswitch via an authselect custom profile (direct-file fallback). + - | + #!/usr/bin/env bash + set -xeuo pipefail + if ! ( + set -euo pipefail + authselect create-profile kanidm -b local + install -m 0644 /usr/share/fedora-remote/authselect/system-auth \ + /etc/authselect/custom/kanidm/system-auth + install -m 0644 /usr/share/fedora-remote/authselect/password-auth \ + /etc/authselect/custom/kanidm/password-auth + install -m 0644 /usr/share/fedora-remote/authselect/nsswitch.conf \ + /etc/authselect/custom/kanidm/nsswitch.conf + authselect select custom/kanidm --force --nobackup + ); then + echo "authselect setup failed, installing PAM/nsswitch directly" >&2 + rm -f /etc/pam.d/system-auth /etc/pam.d/password-auth /etc/nsswitch.conf + install -m 0644 /usr/share/fedora-remote/authselect/system-auth /etc/pam.d/system-auth + install -m 0644 /usr/share/fedora-remote/authselect/password-auth /etc/pam.d/password-auth + install -m 0644 /usr/share/fedora-remote/authselect/nsswitch.conf /etc/nsswitch.conf + fi + + # Kanidm SELinux policy (same approach as fedora-cosmic). + - | + #!/usr/bin/env bash + set -xeuo pipefail + semodule -n -i /usr/share/fedora-remote/kanidm-unixd.cil || true + for domain in sshd_t sshd_session_t sshd_auth_t chkpwd_t \ + systemd_userdbd_t local_login_t xdm_t polkit_t accountsd_t \ + unconfined_service_t; do + semanage permissive -a "${domain}" || true + done + + # Make helper scripts executable. + - | + #!/usr/bin/env bash + set -xeuo pipefail + chmod 0755 /usr/libexec/flatpak-user-firstboot /usr/libexec/grd-firstboot + systemctl --user --global preset-all diff --git a/images/fedora-remote/files/10-kanidm.conf b/images/fedora-remote/files/10-kanidm.conf new file mode 100644 index 0000000..77f85b1 --- /dev/null +++ b/images/fedora-remote/files/10-kanidm.conf @@ -0,0 +1,16 @@ +# Fetch authorized SSH public keys from Kanidm (uploaded to the account). +# Name this 10-* so it is read before systemd-userdbd's AuthorizedKeysCommand +# drop-in, since sshd honours the first directive it sees. +PubkeyAuthentication yes +UsePAM yes +AuthorizedKeysCommand /usr/bin/kanidm_ssh_authorizedkeys %u +AuthorizedKeysCommandUser nobody + +# Hardening: key-only auth through Kanidm. Make sure you have uploaded an SSH +# public key to your account before relying on this, or you can lock yourself +# out of SSH. +PermitRootLogin no +PasswordAuthentication no +PermitEmptyPasswords no +GSSAPIAuthentication no +KerberosAuthentication no diff --git a/images/fedora-remote/files/50-grd.preset b/images/fedora-remote/files/50-grd.preset new file mode 100644 index 0000000..ce40af8 --- /dev/null +++ b/images/fedora-remote/files/50-grd.preset @@ -0,0 +1,4 @@ +# GNOME Remote Desktop headless remote login. +enable gdm.service +enable gnome-remote-desktop.service +enable grd-firstboot.service diff --git a/images/fedora-remote/files/50-kanidm.preset b/images/fedora-remote/files/50-kanidm.preset new file mode 100644 index 0000000..aaa3fb5 --- /dev/null +++ b/images/fedora-remote/files/50-kanidm.preset @@ -0,0 +1,3 @@ +# Kanidm Unix authentication daemons. +enable kanidm-unixd.service +enable kanidm-unixd-tasks.service diff --git a/images/fedora-remote/files/60-flatpak-user-firstboot.preset b/images/fedora-remote/files/60-flatpak-user-firstboot.preset new file mode 100644 index 0000000..0ce2a82 --- /dev/null +++ b/images/fedora-remote/files/60-flatpak-user-firstboot.preset @@ -0,0 +1 @@ +enable flatpak-user-firstboot.service diff --git a/images/fedora-remote/files/flatpak-user-firstboot b/images/fedora-remote/files/flatpak-user-firstboot new file mode 100755 index 0000000..1b39a00 --- /dev/null +++ b/images/fedora-remote/files/flatpak-user-firstboot @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Install the per-user Flatpaks listed in /usr/share/flatpak/flatpaks.list on +# the first login of each user. Run as a systemd --user oneshot unit. +set -euo pipefail + +LIST="/usr/share/flatpak/flatpaks.list" +MARKER="${HOME}/.config/flatpak-user-firstboot.done" + +[[ -f "${LIST}" ]] || exit 0 + +# Make Flathub available for the current user. +flatpak remote-add --user --if-not-exists flathub \ + https://flathub.org/repo/flathub.flatpakrepo + +mapfile -t apps < <( + sed -e 's/#.*//' -e 's/[[:space:]]//g' "${LIST}" | grep -v '^$' || true +) + +if [[ ${#apps[@]} -gt 0 ]]; then + flatpak install --user --noninteractive --assumeyes "${apps[@]}" +fi + +install -dm0755 "$(dirname "${MARKER}")" +touch "${MARKER}" diff --git a/images/fedora-remote/files/flatpak-user-firstboot.service b/images/fedora-remote/files/flatpak-user-firstboot.service new file mode 100644 index 0000000..180157c --- /dev/null +++ b/images/fedora-remote/files/flatpak-user-firstboot.service @@ -0,0 +1,14 @@ +[Unit] +Description=Install per-user Flatpak applications on first login +Documentation=https://docs.flatpak.org/en/latest/flatpak-command-reference.html +ConditionPathExists=!%h/.config/flatpak-user-firstboot.done +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/usr/libexec/flatpak-user-firstboot + +[Install] +WantedBy=default.target diff --git a/images/fedora-remote/files/flatpaks.list b/images/fedora-remote/files/flatpaks.list new file mode 100644 index 0000000..acdccea --- /dev/null +++ b/images/fedora-remote/files/flatpaks.list @@ -0,0 +1,3 @@ +# Flatpaks installed into each user's per-user installation on first login. +# foot is installed as an RPM (no Flathub build), so only Firefox here. +org.mozilla.firefox diff --git a/images/fedora-remote/files/grd-firstboot b/images/fedora-remote/files/grd-firstboot new file mode 100755 index 0000000..349ea54 --- /dev/null +++ b/images/fedora-remote/files/grd-firstboot @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# One-time configuration of GNOME Remote Desktop (system / headless remote +# login). Runs on first boot because grdctl talks to the running daemon. +# +# Optional /etc/gnome-remote-desktop/rdp.env: +# GRD_SYSTEM_USER=rdp +# GRD_SYSTEM_PASSWORD=... +# If unset, remote login is enabled but no greeter credential is configured +# (set one with: printf '%s\n%s\n' USER PASS | grdctl --system rdp set-credentials). +set -euo pipefail + +GRD_USER=gnome-remote-desktop +STATE="/var/lib/${GRD_USER}" +TLS_DIR="${STATE}/.local/share/gnome-remote-desktop" +MARKER="${STATE}/.configured" + +[[ -f "${MARKER}" ]] && exit 0 + +install -d -o "${GRD_USER}" -g "${GRD_USER}" "${TLS_DIR}" + +if [[ ! -f "${TLS_DIR}/tls.key" ]]; then + sudo -u "${GRD_USER}" openssl req -new -newkey rsa:4096 -days 720 -nodes -x509 \ + -subj "/CN=${GRD_CERT_CN:-fedora-remote}" \ + -out "${TLS_DIR}/tls.crt" -keyout "${TLS_DIR}/tls.key" +fi + +grdctl --system rdp set-tls-key "${TLS_DIR}/tls.key" +grdctl --system rdp set-tls-cert "${TLS_DIR}/tls.crt" + +if [[ -n "${GRD_SYSTEM_PASSWORD:-}" ]]; then + printf '%s\n%s\n' "${GRD_SYSTEM_USER:-rdp}" "${GRD_SYSTEM_PASSWORD}" \ + | grdctl --system rdp set-credentials +fi + +grdctl --system rdp enable +systemctl restart gnome-remote-desktop.service || true + +touch "${MARKER}" diff --git a/images/fedora-remote/files/grd-firstboot.service b/images/fedora-remote/files/grd-firstboot.service new file mode 100644 index 0000000..4645a8a --- /dev/null +++ b/images/fedora-remote/files/grd-firstboot.service @@ -0,0 +1,15 @@ +[Unit] +Description=Configure GNOME Remote Desktop on first boot +Documentation=https://github.com/GNOME/gnome-remote-desktop/blob/main/docs/configuration.md +After=network-online.target gnome-remote-desktop.service +Wants=network-online.target +ConditionPathExists=!/var/lib/gnome-remote-desktop/.configured + +[Service] +Type=oneshot +EnvironmentFile=-/etc/gnome-remote-desktop/rdp.env +ExecStart=/usr/libexec/grd-firstboot +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target diff --git a/images/fedora-remote/files/kanidm-config b/images/fedora-remote/files/kanidm-config new file mode 100644 index 0000000..78e1c17 --- /dev/null +++ b/images/fedora-remote/files/kanidm-config @@ -0,0 +1 @@ +uri = "https://auth.plabble.org" diff --git a/images/fedora-remote/files/kanidm-nsswitch.conf b/images/fedora-remote/files/kanidm-nsswitch.conf new file mode 100644 index 0000000..c3857f5 --- /dev/null +++ b/images/fedora-remote/files/kanidm-nsswitch.conf @@ -0,0 +1,14 @@ +passwd: kanidm compat systemd +group: kanidm compat systemd +shadow: files +hosts: files dns myhostname +services: files +netgroup: files +automount: files +aliases: files +ethers: files +gshadow: files +networks: files dns +protocols: files +publickey: files +rpc: files diff --git a/images/fedora-remote/files/kanidm-password-auth b/images/fedora-remote/files/kanidm-password-auth new file mode 100644 index 0000000..ea9d379 --- /dev/null +++ b/images/fedora-remote/files/kanidm-password-auth @@ -0,0 +1,19 @@ +auth required pam_env.so +auth required pam_faildelay.so delay=2000000 +auth sufficient pam_kanidm.so ignore_unknown_user +auth sufficient pam_unix.so nullok +auth required pam_deny.so + +account sufficient pam_kanidm.so +account required pam_unix.so + +password requisite pam_pwquality.so +password sufficient pam_unix.so yescrypt shadow nullok use_authtok +password required pam_deny.so + +session optional pam_keyinit.so revoke +session required pam_limits.so +-session optional pam_systemd.so +session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid +session optional pam_kanidm.so +session required pam_unix.so diff --git a/images/fedora-remote/files/kanidm-system-auth b/images/fedora-remote/files/kanidm-system-auth new file mode 100644 index 0000000..41019d3 --- /dev/null +++ b/images/fedora-remote/files/kanidm-system-auth @@ -0,0 +1,20 @@ +auth required pam_env.so +auth required pam_faildelay.so delay=2000000 +auth sufficient pam_fprintd.so +auth sufficient pam_kanidm.so ignore_unknown_user +auth sufficient pam_unix.so nullok +auth required pam_deny.so + +account sufficient pam_kanidm.so ignore_unknown_user +account required pam_unix.so + +password requisite pam_pwquality.so +password sufficient pam_unix.so yescrypt shadow nullok use_authtok +password required pam_deny.so + +session optional pam_keyinit.so revoke +session required pam_limits.so +-session optional pam_systemd.so +session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid +session optional pam_kanidm.so +session required pam_unix.so diff --git a/images/fedora-remote/files/kanidm-unixd b/images/fedora-remote/files/kanidm-unixd new file mode 100644 index 0000000..fd72ba2 --- /dev/null +++ b/images/fedora-remote/files/kanidm-unixd @@ -0,0 +1,33 @@ +version = "2" + +# Bind cached credentials to the local TPM when one is available. +hsm_type = "tpm_if_possible" + +default_shell = "/bin/bash" +home_prefix = "/home/" +home_attr = "uuid" +home_alias = "name" +use_etc_skel = true +selinux = true + +# Present the short login name ("misthios"), not the SPN, to NSS/PAM. With the +# default (spn) the passwd entry name is "misthios@auth.plabble.org", which +# confuses logins. +uid_attr_map = "name" +gid_attr_map = "name" + +[kanidm] +# Members of this Kanidm POSIX group are allowed to log in via PAM. +pam_allowed_login_groups = ["unix_users"] + +# A host almost always already has a local account at uid 1000. Kanidm ignores +# its own entry when a local account with the same name exists, so logins would +# use the local account (and the Kanidm password would fail). Let Kanidm take +# over these local accounts. Add more names as needed. +allow_local_account_override = ["misthios"] + +# NOTE: kanidm-unixd runs with DynamicUser=yes and cannot read a root-only file, +# so the service account token (when seeded) is passed as a systemd credential +# via the build.sh-generated drop-in +# /usr/lib/systemd/system/kanidm-unixd.service.d/10-token.conf. +# Do not set service_account_token_path here. diff --git a/images/fedora-remote/files/kanidm-unixd.cil b/images/fedora-remote/files/kanidm-unixd.cil new file mode 100644 index 0000000..93b5cf1 --- /dev/null +++ b/images/fedora-remote/files/kanidm-unixd.cil @@ -0,0 +1,21 @@ +; Kanidm ships no SELinux policy. Without these allows, nss/pam consumers +; (sshd, the display manager, systemd-userdbd, ...) are denied write access to +; the kanidm-unixd sockets in /run/kanidm-unixd (labeled var_run_t), so Kanidm +; users cannot be resolved or authenticated and logins fail (sshd reports +; "invalid user"). This is the allow set produced by: +; grep avc: /var/log/audit/audit.log | audit2allow +(allow accountsd_t var_run_t (sock_file (write))) +(allow auditd_t var_run_t (sock_file (write))) +(allow chkpwd_t var_run_t (sock_file (write))) +(allow local_login_t var_run_t (sock_file (write))) +(allow policykit_t var_run_t (sock_file (write))) +(allow ssh_keygen_t var_run_t (sock_file (write))) +(allow sshd_auth_t var_run_t (sock_file (write))) +(allow sshd_keygen_t var_run_t (sock_file (write))) +(allow sshd_session_t var_run_t (sock_file (write))) +(allow sshd_t var_run_t (sock_file (write))) +(allow systemd_bootc_generator_t var_run_t (sock_file (write))) +(allow systemd_selinux_autorelabel_generator_t var_run_t (sock_file (write))) +(allow systemd_userdbd_t var_run_t (sock_file (write))) +(allow xdm_t var_run_t (sock_file (write))) +(allow init_t unconfined_service_t (unix_stream_socket (connectto))) diff --git a/images/fedora-remote/manifest.yaml b/images/fedora-remote/manifest.yaml new file mode 100644 index 0000000..4da049e --- /dev/null +++ b/images/fedora-remote/manifest.yaml @@ -0,0 +1,29 @@ +# Minimal headless remote host: GNOME Remote Desktop (RDP over Wayland) with +# Kanidm Unix authentication. Built on the upstream base-atomic (no desktop) +# manifest, not the full GNOME/silverblue set. + +metadata: + summary: Fedora headless remote host (GNOME Remote Desktop + Kanidm) + +variables: + # Opt out of the upstream fedora.yaml so we don't pull the Firefox RPM. + distro: "fedora-remote" + variant: "remote" + +# Keep it small: no weak/optional dependencies. +recommends: false + +default_target: graphical.target + +ref: fedora-remote/${releasever_ref}/${basearch}/remote + +repos: + # Kanidm packages (OBS network:idm). + - network_idm + +include: + - base-atomic.yaml + - custom.yaml + # Generated by build.sh. + - hardware-exclude.yaml + - kanidm-token.yaml diff --git a/images/fedora-remote/repos/44/network-idm.repo b/images/fedora-remote/repos/44/network-idm.repo new file mode 100644 index 0000000..d252ccb --- /dev/null +++ b/images/fedora-remote/repos/44/network-idm.repo @@ -0,0 +1,9 @@ +[network_idm] +name=Identity Management related tools (Fedora 44) +baseurl=https://download.opensuse.org/repositories/network:/idm/Fedora_44/ +type=rpm-md +skip_if_unavailable=False +gpgcheck=1 +repo_gpgcheck=0 +enabled=1 +gpgkey=https://download.opensuse.org/repositories/network:/idm/Fedora_44/repodata/repomd.xml.key diff --git a/images/fedora-remote/upstream-exclude.txt b/images/fedora-remote/upstream-exclude.txt new file mode 100644 index 0000000..a1a877d --- /dev/null +++ b/images/fedora-remote/upstream-exclude.txt @@ -0,0 +1,49 @@ +# Packages removed from the cloned upstream manifests at build time. +# +# rpm-ostree treats excluding a package that an included manifest declares as a +# fatal error, so build.sh strips these lines from the upstream package +# manifests before composing. +# +# Target machine: GMKtec NucBox M7 +# - AMD Ryzen 7 PRO 6850H (Radeon 680M iGPU) -> amd-gpu-firmware kept +# - 2x Intel I226-V Ethernet -> kernel driver, no fw pkg +# - Intel Wi-Fi 6 AX200 -> iwlwifi-mvm-firmware kept +# - AMD audio (SOF) -> alsa-sof-firmware kept +# No NVIDIA, no Intel GPU/audio, no other wireless vendors, bare metal (no VMs). + +# NVIDIA +nvidia-gpu-firmware + +# Intel GPU / platform / audio (Intel wireless firmware is kept below) +intel-gpu-firmware +intel-audio-firmware +intel-lpmd +intel-vsc-firmware +libva-intel-media-driver + +# Intel-only CPU tooling (AMD uses amd-ucode-firmware) +microcode_ctl +thermald + +# Wireless firmware for hardware that is not present +atheros-firmware +brcmfmac-firmware +libertas-firmware +mt7xxx-firmware +nxpwireless-firmware +qcom-wwan-firmware +realtek-firmware +tiwilink-firmware +iwlwifi-dvm-firmware +iwlegacy-firmware + +# Audio firmware for other vendors +cirrus-audio-firmware + +# Virtual machine guest agents / drivers +hyperv-daemons +open-vm-tools-desktop +qemu-guest-agent +spice-vdagent +spice-webdavd +virtualbox-guest-additions