feat: SHA256 integrity verification of downloaded scripts in step 5

The installer downloads awg_common.sh and manage_amneziawg.sh from
GitHub in step 5. Until now these files were fetched and sourced
without any integrity check beyond curl -f (HTTP error detection).

New verify_sha256() function computes sha256sum of each downloaded
file and compares it against a hardcoded expected hash. On mismatch
the installer dies with a clear error pointing the user at a possible
supply-chain issue.

Design decisions:
- SHA256 hashes are stored as COMMON_SCRIPT_SHA256 and
  MANAGE_SCRIPT_SHA256 constants at the top of install_amneziawg.sh.
  They are set to RELEASE_PLACEHOLDER in development and replaced
  with actual hashes in the release commit (when version is bumped).
- Verification is skipped when the hash is RELEASE_PLACEHOLDER
  (pre-release development) or when AWG_BRANCH is overridden by
  the user (testing against a custom branch).
- sha256sum is available on all target platforms (Ubuntu 24.04+,
  Debian 12+) as part of coreutils.

Workflow at release time:
1. Finalize code in awg_common.sh and manage_amneziawg.sh
2. Compute: sha256sum awg_common.sh manage_amneziawg.sh
3. Replace RELEASE_PLACEHOLDER with actual hashes in both
   install_amneziawg.sh and install_amneziawg_en.sh
4. Same for EN variants (awg_common_en.sh, manage_amneziawg_en.sh)
5. Commit, tag, release

Files: install_amneziawg.sh / _en.sh (RU+EN mirror).
Tests: 81/81 bats pass, 0 shellcheck warnings, syntax clean.
This commit is contained in:
Ivan Bondarev 2026-04-10 20:49:28 +03:00
parent f40300bdd4
commit 5cc24ea23c
2 changed files with 78 additions and 4 deletions

View file

@ -29,6 +29,13 @@ COMMON_SCRIPT_PATH="$AWG_DIR/awg_common.sh"
MANAGE_SCRIPT_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/${AWG_BRANCH}/manage_amneziawg.sh"
MANAGE_SCRIPT_PATH="$AWG_DIR/manage_amneziawg.sh"
# SHA256 checksums скачиваемых скриптов. Обновляются при каждом релизе.
# Проверяются в step5_download_scripts() после curl.
# Если AWG_BRANCH переопределён (не v$SCRIPT_VERSION), проверка пропускается.
# Формат: sha256sum output (hex, 64 chars).
COMMON_SCRIPT_SHA256="RELEASE_PLACEHOLDER"
MANAGE_SCRIPT_SHA256="RELEASE_PLACEHOLDER"
# Флаги CLI
UNINSTALL=0; HELP=0; DIAGNOSTIC=0; VERBOSE=0; NO_COLOR=0; AUTO_YES=0; NO_TWEAKS=0
_APT_UPDATED=0
@ -1702,6 +1709,32 @@ step4_setup_firewall() {
# ШАГ 5: Скачивание скриптов (БЕЗ Python!)
# ==============================================================================
verify_sha256() {
local file="$1" expected="$2" label="$3"
# Пропускаем проверку если:
# - SHA не установлен (RELEASE_PLACEHOLDER — ещё не выпущен release)
# - AWG_BRANCH переопределён (тестовая ветка)
if [[ "$expected" == "RELEASE_PLACEHOLDER" ]]; then
log_debug "SHA256 для $label: пропуск (placeholder, до release)."
return 0
fi
if [[ "${AWG_BRANCH}" != "v${SCRIPT_VERSION}" ]]; then
log_debug "SHA256 для $label: пропуск (AWG_BRANCH переопределён: ${AWG_BRANCH})."
return 0
fi
local actual
actual=$(sha256sum "$file" 2>/dev/null | awk '{print $1}')
if [[ "$actual" != "$expected" ]]; then
log_error "SHA256 $label НЕ совпадает!"
log_error " Ожидался: $expected"
log_error " Получен: $actual"
log_error " Файл мог быть подменён. Скачайте installer заново с GitHub."
return 1
fi
log_debug "SHA256 $label: OK ($actual)"
return 0
}
step5_download_scripts() {
update_state 5
log "### ШАГ 5: Скачивание скриптов управления ###"
@ -1711,7 +1744,9 @@ step5_download_scripts() {
log "Скачивание $COMMON_SCRIPT_PATH..."
if curl -fLso "$COMMON_SCRIPT_PATH" --max-time 60 --retry 2 "$COMMON_SCRIPT_URL"; then
chmod 700 "$COMMON_SCRIPT_PATH" || die "Ошибка chmod awg_common.sh"
log "awg_common.sh скачан."
verify_sha256 "$COMMON_SCRIPT_PATH" "$COMMON_SCRIPT_SHA256" "awg_common.sh" || \
die "Целостность awg_common.sh не подтверждена (SHA256 mismatch). Установка прервана."
log "awg_common.sh скачан и верифицирован."
else
die "Ошибка скачивания awg_common.sh"
fi
@ -1720,7 +1755,9 @@ step5_download_scripts() {
log "Скачивание $MANAGE_SCRIPT_PATH..."
if curl -fLso "$MANAGE_SCRIPT_PATH" --max-time 60 --retry 2 "$MANAGE_SCRIPT_URL"; then
chmod 700 "$MANAGE_SCRIPT_PATH" || die "Ошибка chmod manage_amneziawg.sh"
log "manage_amneziawg.sh скачан."
verify_sha256 "$MANAGE_SCRIPT_PATH" "$MANAGE_SCRIPT_SHA256" "manage_amneziawg.sh" || \
die "Целостность manage_amneziawg.sh не подтверждена (SHA256 mismatch). Установка прервана."
log "manage_amneziawg.sh скачан и верифицирован."
else
die "Ошибка скачивания manage_amneziawg.sh"
fi

View file

@ -29,6 +29,13 @@ COMMON_SCRIPT_PATH="$AWG_DIR/awg_common.sh"
MANAGE_SCRIPT_URL="https://raw.githubusercontent.com/bivlked/amneziawg-installer/${AWG_BRANCH}/manage_amneziawg_en.sh"
MANAGE_SCRIPT_PATH="$AWG_DIR/manage_amneziawg.sh"
# SHA256 checksums of downloaded scripts. Updated at each release.
# Verified in step5_download_scripts() after curl.
# Verification is skipped when AWG_BRANCH is overridden (test branch).
# Format: sha256sum output (hex, 64 chars).
COMMON_SCRIPT_SHA256="RELEASE_PLACEHOLDER"
MANAGE_SCRIPT_SHA256="RELEASE_PLACEHOLDER"
# CLI flags
UNINSTALL=0; HELP=0; DIAGNOSTIC=0; VERBOSE=0; NO_COLOR=0; AUTO_YES=0; NO_TWEAKS=0
_APT_UPDATED=0
@ -1703,6 +1710,32 @@ step4_setup_firewall() {
# STEP 5: Downloading scripts (NO Python!)
# ==============================================================================
verify_sha256() {
local file="$1" expected="$2" label="$3"
# Skip verification when:
# - SHA is not set (RELEASE_PLACEHOLDER — release not yet published)
# - AWG_BRANCH is overridden (test branch)
if [[ "$expected" == "RELEASE_PLACEHOLDER" ]]; then
log_debug "SHA256 for $label: skipped (placeholder, pre-release)."
return 0
fi
if [[ "${AWG_BRANCH}" != "v${SCRIPT_VERSION}" ]]; then
log_debug "SHA256 for $label: skipped (AWG_BRANCH overridden: ${AWG_BRANCH})."
return 0
fi
local actual
actual=$(sha256sum "$file" 2>/dev/null | awk '{print $1}')
if [[ "$actual" != "$expected" ]]; then
log_error "SHA256 mismatch for $label!"
log_error " Expected: $expected"
log_error " Got: $actual"
log_error " File may have been tampered with. Re-download the installer from GitHub."
return 1
fi
log_debug "SHA256 $label: OK ($actual)"
return 0
}
step5_download_scripts() {
update_state 5
log "### STEP 5: Downloading management scripts ###"
@ -1712,7 +1745,9 @@ step5_download_scripts() {
log "Downloading $COMMON_SCRIPT_PATH..."
if curl -fLso "$COMMON_SCRIPT_PATH" --max-time 60 --retry 2 "$COMMON_SCRIPT_URL"; then
chmod 700 "$COMMON_SCRIPT_PATH" || die "chmod awg_common.sh error"
log "awg_common.sh downloaded."
verify_sha256 "$COMMON_SCRIPT_PATH" "$COMMON_SCRIPT_SHA256" "awg_common.sh" || \
die "awg_common.sh integrity check failed (SHA256 mismatch). Installation aborted."
log "awg_common.sh downloaded and verified."
else
die "awg_common.sh download error"
fi
@ -1721,7 +1756,9 @@ step5_download_scripts() {
log "Downloading $MANAGE_SCRIPT_PATH..."
if curl -fLso "$MANAGE_SCRIPT_PATH" --max-time 60 --retry 2 "$MANAGE_SCRIPT_URL"; then
chmod 700 "$MANAGE_SCRIPT_PATH" || die "chmod manage_amneziawg.sh error"
log "manage_amneziawg.sh downloaded."
verify_sha256 "$MANAGE_SCRIPT_PATH" "$MANAGE_SCRIPT_SHA256" "manage_amneziawg.sh" || \
die "manage_amneziawg.sh integrity check failed (SHA256 mismatch). Installation aborted."
log "manage_amneziawg.sh downloaded and verified."
else
die "manage_amneziawg.sh download error"
fi