From 8652f92aae7b55768a1d7307595438a6df514d4c Mon Sep 17 00:00:00 2001 From: Gabriel Brown Date: Thu, 27 Aug 2026 06:46:36 -0400 Subject: [PATCH] Fix: Validate existing package repositories --- setup/scripts/install-packages | 200 +++++++++++++++++++--- tests/setup/package-provenance-contract | 217 +++++++++++++++++++++++- 2 files changed, 388 insertions(+), 29 deletions(-) diff --git a/setup/scripts/install-packages b/setup/scripts/install-packages index 3d4b375..31c0816 100755 --- a/setup/scripts/install-packages +++ b/setup/scripts/install-packages @@ -70,6 +70,7 @@ PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}" # Kept as a named path so the hermetic contract can redirect reads after # sourcing this file. Normal installer execution always resets it to /etc. PANAMA_SYSTEM_ETC=/etc +PANAMA_SYSTEM_FLATPAK_REPO=/var/lib/flatpak/repo # Reviewed installer data and verification primitives. The config parser treats # every value as inert data and rejects unknown, duplicate, or missing fields. @@ -268,10 +269,129 @@ _ini_value() { } } ' "$file") - [[ ${#values[@]} -eq 1 && -n "${values[0]}" ]] || return 1 + (( ${#values[@]} > 0 )) || return 1 + [[ ${#values[@]} -eq 1 && -n "${values[0]}" ]] || return 2 printf '%s\n' "${values[0]}" } +_ini_section_count() { + local file="$1" wanted_section="$2" + awk -v wanted_section="$wanted_section" ' + function trim(value) { + sub(/^[[:space:]]+/, "", value) + sub(/[[:space:]]+$/, "", value) + return value + } + { + sub(/\r$/, "") + line = trim($0) + if (line ~ /^\[[^]]+\]$/) { + section = substr(line, 2, length(line) - 2) + if (tolower(section) == tolower(wanted_section)) count++ + } + } + END { print count + 0 } + ' "$file" +} + +_restore_repository_file() { + local existed="$1" backup="$2" mode="$3" destination="$4" + if (( existed )); then + sudo install -m "$mode" "$backup" "$destination" + else + sudo rm -f -- "$destination" + fi +} + +# A key and its repository file form one trust root. If either activation +# write fails after touching its target, restore both prior files or return both +# targets to absence before reporting failure. +_publish_repository_pair() { + local staged_key="$1" key_destination="$2" staged_repo="$3" repo_destination="$4" + local backup_dir key_backup repo_backup key_mode=0644 repo_mode=0644 + local key_current repo_current + local key_existed=0 repo_existed=0 status=0 rollback_status=0 + + [[ "$key_destination" == /etc/* && "$repo_destination" == /etc/* ]] || return 1 + key_current="$PANAMA_SYSTEM_ETC${key_destination#/etc}" + repo_current="$PANAMA_SYSTEM_ETC${repo_destination#/etc}" + [[ ! -L "$key_current" && ! -L "$repo_current" ]] || return 1 + backup_dir="$(dirname -- "$staged_key")" + key_backup="$backup_dir/prior-key" + repo_backup="$backup_dir/prior-repo" + if [[ -e "$key_current" ]]; then + [[ -f "$key_current" ]] || return 1 + cp -- "$key_current" "$key_backup" || return 1 + key_mode="$(stat -c %a "$key_current")" || return 1 + key_existed=1 + fi + if [[ -e "$repo_current" ]]; then + [[ -f "$repo_current" ]] || return 1 + cp -- "$repo_current" "$repo_backup" || return 1 + repo_mode="$(stat -c %a "$repo_current")" || return 1 + repo_existed=1 + fi + + sudo install -m 0644 "$staged_key" "$key_destination" || status=$? + if (( status == 0 )); then + sudo install -m 0644 "$staged_repo" "$repo_destination" || status=$? + fi + (( status == 0 )) && return 0 + + _restore_repository_file "$repo_existed" "$repo_backup" "$repo_mode" "$repo_destination" \ + || rollback_status=$? + _restore_repository_file "$key_existed" "$key_backup" "$key_mode" "$key_destination" \ + || rollback_status=$? + (( rollback_status == 0 )) || log "Repository activation rollback did not complete" + return "$status" +} + +_terra_repository_is_trusted() { + local repo_file baseurl enabled gpgcheck repo_gpgcheck gpgkey local_key + repo_file="$PANAMA_SYSTEM_ETC/yum.repos.d/terra.repo" + [[ -f "$repo_file" ]] || return 1 + baseurl="$(_ini_value "$repo_file" terra baseurl)" || return 1 + enabled="$(_ini_value "$repo_file" terra enabled)" || return 1 + gpgcheck="$(_ini_value "$repo_file" terra gpgcheck)" || return 1 + repo_gpgcheck="$(_ini_value "$repo_file" terra repo_gpgcheck)" || return 1 + gpgkey="$(_ini_value "$repo_file" terra gpgkey)" || return 1 + [[ "$baseurl" == "${INSTALLER_PROVENANCE[TERRA_BASEURL]}" \ + && "$enabled" == 1 && "$gpgcheck" == 1 && "$repo_gpgcheck" == 1 \ + && "$gpgkey" == 'file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama' ]] || return 1 + local_key="$PANAMA_SYSTEM_ETC/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" + [[ -f "$local_key" ]] || return 1 + key_fingerprint_matches "$PANAMA_PATH/setup/provenance/keys/terra44.asc" \ + "${INSTALLER_PROVENANCE[TERRA_FINGERPRINT]}" \ + && key_fingerprint_matches "$local_key" \ + "${INSTALLER_PROVENANCE[TERRA_FINGERPRINT]}" +} + +# Status 0 is trusted, 1 is absent, and 2 is present but untrusted or malformed. +_flathub_remote_status() { + local config section_count url gpg_verify summary_verify disabled disabled_status + config="$PANAMA_SYSTEM_FLATPAK_REPO/config" + [[ -f "$config" ]] || return 1 + section_count="$(_ini_section_count "$config" 'remote "flathub"')" || return 2 + (( section_count > 0 )) || return 1 + (( section_count == 1 )) || return 2 + url="$(_ini_value "$config" 'remote "flathub"' url)" || return 2 + gpg_verify="$(_ini_value "$config" 'remote "flathub"' gpg-verify)" || return 2 + summary_verify="$(_ini_value "$config" 'remote "flathub"' gpg-verify-summary)" || return 2 + [[ "$url" == 'https://dl.flathub.org/repo/' ]] || return 2 + case "${gpg_verify,,}" in true|yes|1) ;; *) return 2 ;; esac + case "${summary_verify,,}" in true|yes|1) ;; *) return 2 ;; esac + disabled_status=0 + disabled="$(_ini_value "$config" 'remote "flathub"' xa.disable)" || disabled_status=$? + if (( disabled_status == 0 )); then + case "${disabled,,}" in true|yes|1) return 2 ;; esac + elif (( disabled_status != 1 )); then + return 2 + fi + [[ -f "$PANAMA_SYSTEM_FLATPAK_REPO/flathub.trustedkeys.gpg" ]] || return 2 + key_fingerprint_matches "$PANAMA_SYSTEM_FLATPAK_REPO/flathub.trustedkeys.gpg" \ + "${INSTALLER_PROVENANCE[FLATHUB_FINGERPRINT]}" || return 2 +} + install_rpmfusion_repositories() { local work free_rpm nonfree_rpm require_reviewed_fedora_release || return 1 @@ -308,16 +428,22 @@ install_rpmfusion_repositories() { } install_terra_repository() { - local work staged_key status + local work staged_key staged_repo status require_reviewed_fedora_release || return 1 - if rpm -q terra-release >/dev/null 2>&1; then - log "Terra repository already installed" - return 0 - fi _require_policy_value TERRA_BASEURL 'https://repos.fyralabs.com/terra44' || return 1 + _require_policy_value TERRA_FINGERPRINT AE09157A4DE88B497EA1D5D300CDAB43DE226D6F || return 1 + if rpm -q terra-release >/dev/null 2>&1; then + if _terra_repository_is_trusted; then + log "Terra repository already installed and verified" + return 0 + fi + log "Installed Terra repository does not match Panama's reviewed trust policy" + return 1 + fi work="$(mktemp -d)" || return 1 chmod 0700 "$work" staged_key="$work/terra44.asc" + staged_repo="$work/terra.repo" if ! _stage_reviewed_key "$PANAMA_PATH/setup/provenance/keys/terra44.asc" "$staged_key" \ TERRA_FINGERPRINT AE09157A4DE88B497EA1D5D300CDAB43DE226D6F; then rm -rf -- "$work" @@ -327,6 +453,15 @@ install_terra_repository() { rm -rf -- "$work" return 1 } + printf '%s\n' \ + '[terra]' \ + 'name=Panama reviewed Terra 44' \ + "baseurl=${INSTALLER_PROVENANCE[TERRA_BASEURL]}" \ + 'enabled=1' \ + 'gpgcheck=1' \ + 'repo_gpgcheck=1' \ + 'gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama' > "$staged_repo" + chmod 0600 "$staged_repo" status=0 sudo dnf install -y \ --repofrompath "terra,${INSTALLER_PROVENANCE[TERRA_BASEURL]}" \ @@ -334,6 +469,12 @@ install_terra_repository() { --setopt=terra.repo_gpgcheck=1 \ --setopt=terra.gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama \ terra-release || status=$? + if (( status == 0 )); then + sudo install -m 0644 "$staged_repo" /etc/yum.repos.d/terra.repo || status=$? + fi + if (( status == 0 )) && ! _terra_repository_is_trusted; then + status=1 + fi rm -rf -- "$work" return "$status" } @@ -363,22 +504,28 @@ configure_hyprland_repository() { 'gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland' > "$staged_repo" chmod 0600 "$staged_repo" status=0 - sudo install -m 0644 "$staged_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland \ - || status=$? - if (( status == 0 )); then - sudo install -m 0644 "$staged_repo" /etc/yum.repos.d/panama-hyprland.repo \ - || status=$? - fi + _publish_repository_pair \ + "$staged_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland \ + "$staged_repo" /etc/yum.repos.d/panama-hyprland.repo || status=$? rm -rf -- "$work" return "$status" } ensure_flathub_remote() { - local work descriptor encoded key_file url no_gpg_verify gpg_verify status + local work descriptor encoded key_file url no_gpg_verify gpg_verify + local status remote_status no_gpg_status gpg_status require_reviewed_fedora_release || return 1 _require_policy_value FLATHUB_DESCRIPTOR_URL 'https://flathub.org/repo/flathub.flatpakrepo' || return 1 _require_policy_value FLATHUB_DESCRIPTOR_MAX_BYTES 1048576 || return 1 _require_policy_value FLATHUB_FINGERPRINT 6E5C05D979C76DAF93C081354184DD4D907A7CAE || return 1 + remote_status=0 + _flathub_remote_status || remote_status=$? + if (( remote_status == 0 )); then + return 0 + elif (( remote_status != 1 )); then + log "Existing Flathub remote does not match Panama's reviewed trust policy" + return 1 + fi work="$(mktemp -d)" || return 1 chmod 0700 "$work" descriptor="$work/flathub.flatpakrepo" @@ -392,11 +539,22 @@ ensure_flathub_remote() { rm -rf -- "$work" return 1 fi - if no_gpg_verify="$(_ini_value "$descriptor" 'Flatpak Repo' NoGPGVerify)"; then + no_gpg_status=0 + no_gpg_verify="$(_ini_value "$descriptor" 'Flatpak Repo' NoGPGVerify)" \ + || no_gpg_status=$? + if (( no_gpg_status == 0 )); then case "${no_gpg_verify,,}" in true|yes|1) rm -rf -- "$work"; return 1 ;; esac + elif (( no_gpg_status != 1 )); then + rm -rf -- "$work" + return 1 fi - if gpg_verify="$(_ini_value "$descriptor" 'Flatpak Repo' GPGVerify)"; then + gpg_status=0 + gpg_verify="$(_ini_value "$descriptor" 'Flatpak Repo' GPGVerify)" || gpg_status=$? + if (( gpg_status == 0 )); then case "${gpg_verify,,}" in false|no|0) rm -rf -- "$work"; return 1 ;; esac + elif (( gpg_status != 1 )); then + rm -rf -- "$work" + return 1 fi if ! key_fingerprint_matches "$key_file" "${INSTALLER_PROVENANCE[FLATHUB_FINGERPRINT]}"; then rm -rf -- "$work" @@ -405,6 +563,9 @@ ensure_flathub_remote() { status=0 sudo flatpak remote-add --if-not-exists --gpg-import="$key_file" flathub "$url" \ || status=$? + if (( status == 0 )); then + _flathub_remote_status || status=$? + fi rm -rf -- "$work" return "$status" } @@ -436,12 +597,9 @@ install_claude_code() { 'gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama' > "$staged_repo" chmod 0600 "$staged_repo" status=0 - sudo install -m 0644 "$staged_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama \ - || status=$? - if (( status == 0 )); then - sudo install -m 0644 "$staged_repo" /etc/yum.repos.d/claude-code.repo \ - || status=$? - fi + _publish_repository_pair \ + "$staged_key" /etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama \ + "$staged_repo" /etc/yum.repos.d/claude-code.repo || status=$? if (( status == 0 )); then sudo dnf install -y claude-code || status=$? fi diff --git a/tests/setup/package-provenance-contract b/tests/setup/package-provenance-contract index eaf823a..39fcbe3 100755 --- a/tests/setup/package-provenance-contract +++ b/tests/setup/package-provenance-contract @@ -102,6 +102,7 @@ before_rpmdb="$(snapshot "$host_rpmdb")" before_repo_files="$(snapshot_gpg_state /etc/yum.repos.d)" before_rpm_key_files="$(snapshot_gpg_state /etc/pki/rpm-gpg)" before_system_flatpak="$(snapshot_file_state /var/lib/flatpak/repo/config)" +before_system_flathub_key="$(snapshot_file_state /var/lib/flatpak/repo/flathub.trustedkeys.gpg)" before_user_flatpak="$(snapshot_file_state "$HOME/.local/share/flatpak/repo/config")" before_bashrc="$(snapshot_file_state "$HOME/.bashrc")" @@ -288,7 +289,7 @@ sed '/^# --- The server path/,$d' "$repo_dir/setup/scripts/install-packages" \ make_stub_commands() { local case_root="$1" mkdir -p "$case_root/bin" "$case_root/home" "$case_root/tmp" "$case_root/etc/yum.repos.d" \ - "$case_root/etc/pki/rpm-gpg" + "$case_root/etc/pki/rpm-gpg" "$case_root/flatpak-repo" cat > "$case_root/bin/rpm" <<'STUB' #!/usr/bin/env bash @@ -385,13 +386,26 @@ STUB set -euo pipefail if [[ "${1:-}" == install ]]; then shift - [[ "${1:-}" == -m && "${2:-}" == 0644 ]] || exit 67 + [[ "${1:-}" == -m && ( "${2:-}" == 0644 || "${2:-}" == 644 ) ]] || exit 67 source_file="$3" destination="$4" printf 'sudo:install:%s:%s\n' "$(basename "$source_file")" "$destination" >> "$COMMAND_LOG" mapped="$STUB_ETC${destination#/etc}" mkdir -p "$(dirname "$mapped")" /usr/bin/install -m 0644 "$source_file" "$mapped" + count=0 + [[ ! -f "$STUB_INSTALL_COUNTER" ]] || read -r count < "$STUB_INSTALL_COUNTER" + count=$((count + 1)) + printf '%s\n' "$count" > "$STUB_INSTALL_COUNTER" + if [[ -n "${STUB_INSTALL_FAIL_AT:-}" && "$count" == "$STUB_INSTALL_FAIL_AT" ]]; then + exit 67 + fi + exit 0 +fi +if [[ "${1:-}" == rm && "${2:-}" == -f && "${3:-}" == -- ]]; then + destination="$4" + printf 'sudo:rm:%s\n' "$destination" >> "$COMMAND_LOG" + rm -f -- "$STUB_ETC${destination#/etc}" exit 0 fi original="$*" @@ -413,16 +427,31 @@ if [[ -n "${STUB_DNF_FAIL_MATCH:-}" && "$original" == *"$STUB_DNF_FAIL_MATCH"* ] exit 68 fi if [[ "${1:-}" == flatpak && "${2:-}" == remote-add ]]; then - printf 'mutated\n' > "$STUB_FLATPAK_STATE" + if [[ ! -f "$STUB_FLATPAK_REPO/config" ]] \ + || ! grep -q '^\[remote "flathub"\]$' "$STUB_FLATPAK_REPO/config"; then + key='' + url="${!#}" + for argument in "$@"; do + [[ "$argument" != --gpg-import=* ]] || key="${argument#--gpg-import=}" + done + [[ -n "$key" ]] || exit 69 + printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=%s\ngpg-verify=true\ngpg-verify-summary=true\n' \ + "$url" > "$STUB_FLATPAK_REPO/config" + cp "$key" "$STUB_FLATPAK_REPO/flathub.trustedkeys.gpg" + printf 'mutated\n' > "$STUB_FLATPAK_STATE" + fi fi STUB - for command in dnf flatpak; do - cat > "$case_root/bin/$command" <<'STUB' + cat > "$case_root/bin/dnf" <<'STUB' +#!/usr/bin/env bash +exit 69 +STUB + + cat > "$case_root/bin/flatpak" <<'STUB' #!/usr/bin/env bash exit 69 STUB - done chmod +x "$case_root/bin"/* } @@ -445,14 +474,70 @@ run_installer_function() { rm -rf -- "$case_root" make_stub_commands "$case_root" : > "$case_root/commands.log" + printf '0\n' > "$case_root/install-counter" printf 'preserved\n' > "$case_root/flatpak-state" write_flathub_descriptor "$case_root/flathub.flatpakrepo" \ "${STUB_FLATHUB_KEY_FILE:-$installer_fixture/setup/provenance/keys/flathub.asc}" \ "${STUB_FLATHUB_VERIFY_LINE:-}" "${STUB_FLATHUB_URL:-https://dl.flathub.org/repo/}" - if [[ "${STUB_EXISTING_REPOSITORY:-}" == hyprland ]]; then + if [[ "${STUB_EXISTING_REPOSITORY:-}" == hyprland \ + || "${STUB_PAIR_PRIOR:-}" == present && "${STUB_PAIR_NAME:-}" == hyprland ]]; then printf 'known key\n' > "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" printf 'known repo\n' > "$case_root/etc/yum.repos.d/panama-hyprland.repo" fi + if [[ "${STUB_PAIR_PRIOR:-}" == present && "${STUB_PAIR_NAME:-}" == claude-code ]]; then + printf 'known key\n' > "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" + printf 'known repo\n' > "$case_root/etc/yum.repos.d/claude-code.repo" + fi + case "${STUB_TERRA_REPO_MODE:-absent}" in + trusted) + cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ + "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" + printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ + > "$case_root/etc/yum.repos.d/terra.repo" + ;; + nogpg) + printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=0\nrepo_gpgcheck=0\ngpgkey=https://repos.fyralabs.com/terra44.key\n' \ + > "$case_root/etc/yum.repos.d/terra.repo" + ;; + wrong-url) + cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ + "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" + printf '[terra]\nbaseurl=https://evil.invalid/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ + > "$case_root/etc/yum.repos.d/terra.repo" + ;; + wrong-key) + cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ + "$case_root/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" + printf '[terra]\nbaseurl=https://repos.fyralabs.com/terra44\nenabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama\n' \ + > "$case_root/etc/yum.repos.d/terra.repo" + ;; + esac + case "${STUB_FLATPAK_REMOTE_MODE:-absent}" in + trusted) + printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ + > "$case_root/flatpak-repo/config" + cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ + "$case_root/flatpak-repo/flathub.trustedkeys.gpg" + ;; + wrong-url) + printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://evil.invalid/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ + > "$case_root/flatpak-repo/config" + cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ + "$case_root/flatpak-repo/flathub.trustedkeys.gpg" + ;; + wrong-key) + printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=true\ngpg-verify-summary=true\n' \ + > "$case_root/flatpak-repo/config" + cp "$installer_fixture/setup/provenance/keys/terra44.asc" \ + "$case_root/flatpak-repo/flathub.trustedkeys.gpg" + ;; + no-gpg) + printf '[core]\nrepo_version=1\n\n[remote "flathub"]\nurl=https://dl.flathub.org/repo/\ngpg-verify=false\ngpg-verify-summary=false\n' \ + > "$case_root/flatpak-repo/config" + cp "$installer_fixture/setup/provenance/keys/flathub.asc" \ + "$case_root/flatpak-repo/flathub.trustedkeys.gpg" + ;; + esac case "${STUB_CLAUDE_DESKTOP_REPO_MODE:-absent}" in trusted) cp "$installer_fixture/setup/provenance/keys/claude-desktop.asc" \ @@ -477,11 +562,13 @@ run_installer_function() { FLATHUB_DESCRIPTOR="$case_root/flathub.flatpakrepo" \ STUB_ETC="$case_root/etc" \ STUB_FLATPAK_STATE="$case_root/flatpak-state" \ + STUB_FLATPAK_REPO="$case_root/flatpak-repo" \ + STUB_INSTALL_COUNTER="$case_root/install-counter" \ HOME="$case_root/home" \ TMPDIR="$case_root/tmp" \ PANAMA_PATH="$installer_fixture" \ PATH="$case_root/bin:/usr/bin:/bin" \ - bash -c 'source "$PANAMA_PATH/setup/scripts/install-packages"; PANAMA_SYSTEM_ETC="$STUB_ETC"; declare -F "$1" >/dev/null; "$1"' \ + bash -c 'source "$PANAMA_PATH/setup/scripts/install-packages"; PANAMA_SYSTEM_ETC="$STUB_ETC"; PANAMA_SYSTEM_FLATPAK_REPO="$STUB_FLATPAK_REPO"; declare -F "$1" >/dev/null; "$1"' \ bash "$function_name" > "$case_root/output" 2>&1 } @@ -519,11 +606,24 @@ rpm:query:terra-release gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F sudo:install:terra44.asc:/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama sudo:dnf install -y --repofrompath terra,https://repos.fyralabs.com/terra44 --setopt=terra.pkg_gpgcheck=1 --setopt=terra.repo_gpgcheck=1 --setopt=terra.gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama terra-release +sudo:install:terra.repo:/etc/yum.repos.d/terra.repo +gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F +gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F EXPECTED )" cmp -s "$installer_fixture/setup/provenance/keys/terra44.asc" \ "$test_tmp/cases/terra/etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama" \ || fail 'Terra privileged install did not preserve the fully staged reviewed key' +assert_file_bytes "$test_tmp/cases/terra/etc/yum.repos.d/terra.repo" "$(cat <<'EXPECTED' +[terra] +name=Panama reviewed Terra 44 +baseurl=https://repos.fyralabs.com/terra44 +enabled=1 +gpgcheck=1 +repo_gpgcheck=1 +gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-terra44-panama +EXPECTED +)" reset_installer_fixture expect_success run_installer_function hyprland configure_hyprland_repository @@ -555,6 +655,7 @@ rpm:release curl:https://flathub.org/repo/flathub.flatpakrepo:max=1048576:output=flathub.flatpakrepo gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE sudo:flatpak remote-add --if-not-exists --gpg-import=FLATHUB_KEY flathub https://dl.flathub.org/repo/ +gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE EXPECTED )" @@ -600,6 +701,104 @@ sudo:dnf install -y claude-desktop-extra EXPECTED )" +# Existing repository state is part of the trust boundary. Idempotency is only +# success when the already-active repository matches the reviewed policy. +reset_installer_fixture +STUB_FLATPAK_REMOTE_MODE=trusted \ + expect_success run_installer_function flathub-existing-trusted ensure_flathub_remote +assert_log flathub-existing-trusted "$(cat <<'EXPECTED' +rpm:release +gpg:fingerprint:6E5C05D979C76DAF93C081354184DD4D907A7CAE +EXPECTED +)" +assert_file_bytes "$test_tmp/cases/flathub-existing-trusted/flatpak-state" 'preserved' + +for mode in wrong-url wrong-key no-gpg; do + reset_installer_fixture + name="flathub-existing-$mode" + STUB_FLATPAK_REMOTE_MODE="$mode" \ + expect_failure run_installer_function "$name" ensure_flathub_remote + assert_file_bytes "$test_tmp/cases/$name/flatpak-state" 'preserved' + [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'curl:'* \ + && "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ + || fail "untrusted existing Flathub $mode state was changed" +done + +reset_installer_fixture +STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE=trusted \ + expect_success run_installer_function terra-existing-trusted install_terra_repository +assert_log terra-existing-trusted "$(cat <<'EXPECTED' +rpm:release +rpm:query:terra-release +gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F +gpg:fingerprint:AE09157A4DE88B497EA1D5D300CDAB43DE226D6F +EXPECTED +)" + +for mode in nogpg wrong-url wrong-key absent; do + reset_installer_fixture + name="terra-existing-$mode" + STUB_TERRA_INSTALLED=1 STUB_TERRA_REPO_MODE="$mode" \ + expect_failure run_installer_function "$name" install_terra_repository + [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ + || fail "untrusted existing Terra $mode state reached a mutation" +done + +# An optional security field may be absent, but duplicates are malformed even +# when one copy looks safe. These cases catch the absent/duplicate conflation. +for duplicate_case in \ + $'duplicate-no-gpg NoGPGVerify=false\nNoGPGVerify=true' \ + $'duplicate-gpg-verify GPGVerify=true\nGPGVerify=false'; do + name="${duplicate_case%% *}" + lines="${duplicate_case#* }" + reset_installer_fixture + STUB_FLATHUB_VERIFY_LINE="$lines" \ + expect_failure run_installer_function "$name" ensure_flathub_remote + assert_file_bytes "$test_tmp/cases/$name/flatpak-state" 'preserved' + [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:'* ]] \ + || fail "$name descriptor reached remote activation" +done + +assert_pair_rollback() { + local name="$1" pair="$2" prior="$3" key repo + case "$pair" in + hyprland) + key="$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-panama-hyprland" + repo="$test_tmp/cases/$name/etc/yum.repos.d/panama-hyprland.repo" + ;; + claude-code) + key="$test_tmp/cases/$name/etc/pki/rpm-gpg/RPM-GPG-KEY-claude-code-panama" + repo="$test_tmp/cases/$name/etc/yum.repos.d/claude-code.repo" + ;; + esac + if [[ "$prior" == present ]]; then + assert_file_bytes "$key" 'known key' + assert_file_bytes "$repo" 'known repo' + else + [[ ! -e "$key" && ! -e "$repo" ]] \ + || fail "$pair activation failure left part of an absent pair" + fi + [[ "$(<"$test_tmp/cases/$name/commands.log")" != *'sudo:dnf'* ]] \ + || fail "$pair activation failure reached DNF" +} + +# Both activation writes can fail after changing their target. Each repository +# must restore known-good pairs and return prior-absent pairs to full absence. +for pair_spec in \ + 'hyprland configure_hyprland_repository' \ + 'claude-code install_claude_code'; do + read -r pair function_name <<<"$pair_spec" + for prior in absent present; do + for fail_at in 1 2; do + reset_installer_fixture + name="$pair-$prior-activation-$fail_at" + STUB_PAIR_NAME="$pair" STUB_PAIR_PRIOR="$prior" STUB_INSTALL_FAIL_AT="$fail_at" \ + expect_failure run_installer_function "$name" "$function_name" + assert_pair_rollback "$name" "$pair" "$prior" + done + done +done + # A Fedora version outside the reviewed policy stops every public transaction # before curl, sudo, Flatpak, or repository inspection can act. for function_name in install_rpmfusion_repositories install_terra_repository \ @@ -710,6 +909,8 @@ STUB_DNF_FAIL_MATCH=terra-release expect_failure run_installer_function terra-dn || fail 'repository cases changed host RPM key files' [[ "$before_system_flatpak" == "$(snapshot_file_state /var/lib/flatpak/repo/config)" ]] \ || fail 'repository cases changed the system Flatpak remote' +[[ "$before_system_flathub_key" == "$(snapshot_file_state /var/lib/flatpak/repo/flathub.trustedkeys.gpg)" ]] \ + || fail 'repository cases changed the system Flathub trusted key' [[ "$before_user_flatpak" == "$(snapshot_file_state "$HOME/.local/share/flatpak/repo/config")" ]] \ || fail 'repository cases changed the user Flatpak remote' [[ "$before_bashrc" == "$(snapshot_file_state "$HOME/.bashrc")" ]] \