ec3aca7fc8
🛡️ Retrieve DNSSEC status of coresecret.dev. / 🛡️ Retrieve DNSSEC status of coresecret.dev. (push) Has been cancelled
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Has been cancelled
💙 Generating a PUBLIC Live ISO. / 💙 Generating a PUBLIC Live ISO. (push) Has been cancelled
🔐 Generating a Private Live ISO TRIXIE. / 🔐 Generating a Private Live ISO TRIXIE. (push) Has been cancelled
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
318 lines
9.9 KiB
Bash
318 lines
9.9 KiB
Bash
#!/bin/bash
|
|
# SPDX-Version: 3.0
|
|
# SPDX-CreationInfo: 2025-10-11; WEIDNER, Marc S.; <msw@coresecret.dev>
|
|
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
|
|
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
|
|
# SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; <msw@coresecret.dev>
|
|
# SPDX-FileType: SOURCE
|
|
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
|
|
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
|
|
# SPDX-PackageName: CISS.debian.live.builder
|
|
# SPDX-Security-Contact: security@coresecret.eu
|
|
set -Ceuo pipefail
|
|
|
|
printf "\e[95m🧪 '%s' starting ... \e[0m\n" "${0}"
|
|
|
|
[[ -r /root/ciss_xdg_tmp.sh ]] && . /root/ciss_xdg_tmp.sh
|
|
export DEBIAN_FRONTEND="noninteractive"
|
|
export INITRD="No"
|
|
|
|
declare SOPS_COSIGN_CERTIFICATE_IDENTITY_REGEXP="https://github.com/getsops"
|
|
declare SOPS_COSIGN_CERTIFICATE_OIDC_ISSUER="https://token.actions.githubusercontent.com"
|
|
|
|
#######################################
|
|
# Print a fatal error and abort the hook.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Message string
|
|
# Returns:
|
|
# None
|
|
#######################################
|
|
die() {
|
|
declare message="$1"
|
|
printf "\e[91m❌ ERROR: %s \e[0m\n" "${message}" >&2
|
|
exit 43
|
|
}
|
|
|
|
#######################################
|
|
# Require an executable tool.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Tool name
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
require_tool() {
|
|
declare tool_name="$1"
|
|
|
|
command -v "${tool_name}" >/dev/null 2>&1 || die "Required tool not found: ${tool_name}"
|
|
|
|
return 0
|
|
}
|
|
|
|
#######################################
|
|
# Validate and normalize a SOPS semantic version.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: SOPS version string
|
|
# Outputs:
|
|
# Normalized bare semantic version
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
normalize_sops_version() {
|
|
declare sops_version="${1#v}"
|
|
|
|
[[ "${sops_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || \
|
|
die "Invalid SOPS version '${1}'. Expected '<MAJOR>.<MINOR>.<PATCH>' without prerelease metadata."
|
|
|
|
printf '%s' "${sops_version}"
|
|
|
|
return 0
|
|
}
|
|
|
|
#######################################
|
|
# Download a mandatory release asset.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Asset URL
|
|
# 2: Target filename
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
download_required_asset() {
|
|
declare asset_url="$1"
|
|
declare target_file="$2"
|
|
|
|
if ! curl -fsSLo "${target_file}" "${asset_url}"; then
|
|
die "Failed to download required SOPS asset '${target_file}' from '${asset_url}'."
|
|
fi
|
|
|
|
[[ -s "${target_file}" ]] || die "Downloaded SOPS asset is empty: ${target_file}"
|
|
|
|
return 0
|
|
}
|
|
|
|
#######################################
|
|
# Download an optional release asset and distinguish absence from download errors.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Asset URL
|
|
# 2: Target filename
|
|
# Returns:
|
|
# 0: asset was downloaded
|
|
# 1: asset is absent upstream
|
|
#######################################
|
|
download_optional_asset() {
|
|
declare asset_url="$1"
|
|
declare target_file="$2"
|
|
declare http_code=""
|
|
|
|
if ! http_code=$(curl -sSLo "${target_file}" -w '%{http_code}' "${asset_url}"); then
|
|
rm -f -- "${target_file}"
|
|
die "Failed to query optional SOPS asset '${target_file}' from '${asset_url}'."
|
|
fi
|
|
|
|
case "${http_code}" in
|
|
200)
|
|
[[ -s "${target_file}" ]] || die "Optional SOPS asset is empty after HTTP 200: ${target_file}"
|
|
return 0
|
|
;;
|
|
404)
|
|
rm -f -- "${target_file}"
|
|
return 1
|
|
;;
|
|
*)
|
|
rm -f -- "${target_file}"
|
|
die "Unexpected HTTP status ${http_code} for optional SOPS asset '${target_file}' from '${asset_url}'."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
#######################################
|
|
# Verify the SOPS checksums file with Cosign.
|
|
# Globals:
|
|
# SOPS_COSIGN_CERTIFICATE_IDENTITY_REGEXP
|
|
# SOPS_COSIGN_CERTIFICATE_OIDC_ISSUER
|
|
# Arguments:
|
|
# 1: Checksums filename
|
|
# 2: Bundle filename
|
|
# 3: Certificate filename
|
|
# 4: Signature filename
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
verify_sops_checksums_signature() {
|
|
declare checksums_file="$1"
|
|
declare bundle_file="$2"
|
|
declare certificate_file="$3"
|
|
declare signature_file="$4"
|
|
|
|
if [[ -f "${bundle_file}" ]]; then
|
|
printf "\e[95m[INFO] Verifying SOPS checksums with Cosign bundle: %s \e[0m\n" "${bundle_file}"
|
|
cosign verify-blob "${checksums_file}" \
|
|
--bundle "${bundle_file}" \
|
|
--certificate-identity-regexp="${SOPS_COSIGN_CERTIFICATE_IDENTITY_REGEXP}" \
|
|
--certificate-oidc-issuer="${SOPS_COSIGN_CERTIFICATE_OIDC_ISSUER}" || \
|
|
die "SOPS checksum signature verification failed in bundle mode for '${checksums_file}' using '${bundle_file}'."
|
|
return 0
|
|
fi
|
|
|
|
if [[ -f "${certificate_file}" && -f "${signature_file}" ]]; then
|
|
printf "\e[95m[INFO] Verifying SOPS checksums with Cosign split certificate/signature: %s %s \e[0m\n" "${certificate_file}" "${signature_file}"
|
|
cosign verify-blob "${checksums_file}" \
|
|
--certificate "${certificate_file}" \
|
|
--signature "${signature_file}" \
|
|
--certificate-identity-regexp="${SOPS_COSIGN_CERTIFICATE_IDENTITY_REGEXP}" \
|
|
--certificate-oidc-issuer="${SOPS_COSIGN_CERTIFICATE_OIDC_ISSUER}" || \
|
|
die "SOPS checksum signature verification failed in legacy split mode for '${checksums_file}' using '${certificate_file}' and '${signature_file}'."
|
|
return 0
|
|
fi
|
|
|
|
if [[ -f "${certificate_file}" || -f "${signature_file}" ]]; then
|
|
die "Incomplete legacy SOPS signature layout for '${checksums_file}'. Expected both '${certificate_file}' and '${signature_file}'."
|
|
fi
|
|
|
|
die "No supported SOPS checksum signature layout found for '${checksums_file}'. Expected bundle or split certificate/signature assets."
|
|
}
|
|
|
|
#######################################
|
|
# Verify the SOPS artifact checksum and ensure the expected artifact was covered.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Checksums filename
|
|
# 2: Artifact filename
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
verify_sops_artifact_checksum() {
|
|
declare checksums_file="$1"
|
|
declare artifact_file="$2"
|
|
declare checksum_output=""
|
|
|
|
if ! checksum_output=$(sha256sum -c "${checksums_file}" --ignore-missing 2>&1); then
|
|
printf '%s\n' "${checksum_output}" >&2
|
|
die "SOPS artifact checksum verification failed for '${artifact_file}' using '${checksums_file}'."
|
|
fi
|
|
|
|
printf '%s\n' "${checksum_output}"
|
|
|
|
if ! grep -Fxq "${artifact_file}: OK" <<< "${checksum_output}" && \
|
|
! grep -Fxq "./${artifact_file}: OK" <<< "${checksum_output}"; then
|
|
die "SOPS checksum verification did not cover expected artifact '${artifact_file}' from '${checksums_file}'."
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
#######################################
|
|
# Install SOPS from an upstream GitHub release after signature and checksum verification.
|
|
# Globals:
|
|
# CISS_SOPS_VERSION
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
main() {
|
|
require_tool curl
|
|
require_tool cosign
|
|
require_tool sha256sum
|
|
|
|
declare sops_env="/root/sops.env"
|
|
[[ -r "${sops_env}" ]] || die "Missing SOPS environment file: ${sops_env}"
|
|
|
|
# shellcheck disable=SC1090
|
|
. "${sops_env}"
|
|
|
|
declare ciss_sops_version
|
|
ciss_sops_version=$(normalize_sops_version "${CISS_SOPS_VERSION:?CISS_SOPS_VERSION is not set}")
|
|
|
|
declare architecture
|
|
architecture="$(dpkg --print-architecture)"
|
|
|
|
declare sops_tag="v${ciss_sops_version}"
|
|
declare sops_file=""
|
|
case "${architecture}" in
|
|
amd64)
|
|
sops_file="sops-${sops_tag}.linux.amd64"
|
|
;;
|
|
arm64)
|
|
sops_file="sops-${sops_tag}.linux.arm64"
|
|
;;
|
|
*)
|
|
die "Unsupported architecture '${architecture}' for SOPS version '${ciss_sops_version}'. Expected amd64 or arm64."
|
|
;;
|
|
esac
|
|
|
|
declare release_base_url="https://github.com/getsops/sops/releases/download/${sops_tag}"
|
|
declare checksums_file="sops-${sops_tag}.checksums.txt"
|
|
declare bundle_file="sops-${sops_tag}.checksums.sigstore.json"
|
|
declare certificate_file="sops-${sops_tag}.checksums.pem"
|
|
declare signature_file="sops-${sops_tag}.checksums.sig"
|
|
declare bundle_available="false"
|
|
declare certificate_available="false"
|
|
declare signature_available="false"
|
|
|
|
cd /tmp
|
|
|
|
printf "\e[95m[INFO] Downloading SOPS %s asset: %s \e[0m\n" "${ciss_sops_version}" "${sops_file}"
|
|
download_required_asset "${release_base_url}/${sops_file}" "${sops_file}"
|
|
download_required_asset "${release_base_url}/${checksums_file}" "${checksums_file}"
|
|
|
|
# shellcheck disable=SC2310
|
|
if download_optional_asset "${release_base_url}/${bundle_file}" "${bundle_file}"; then
|
|
bundle_available="true"
|
|
fi
|
|
|
|
if [[ "${bundle_available}" == "false" ]]; then
|
|
# shellcheck disable=SC2310
|
|
if download_optional_asset "${release_base_url}/${certificate_file}" "${certificate_file}"; then
|
|
certificate_available="true"
|
|
fi
|
|
|
|
# shellcheck disable=SC2310
|
|
if download_optional_asset "${release_base_url}/${signature_file}" "${signature_file}"; then
|
|
signature_available="true"
|
|
fi
|
|
|
|
if [[ "${certificate_available}" != "${signature_available}" ]]; then
|
|
die "Incomplete legacy SOPS signature assets for version '${ciss_sops_version}'. Expected both '${certificate_file}' and '${signature_file}'."
|
|
fi
|
|
fi
|
|
|
|
verify_sops_checksums_signature "${checksums_file}" "${bundle_file}" "${certificate_file}" "${signature_file}"
|
|
verify_sops_artifact_checksum "${checksums_file}" "${sops_file}"
|
|
|
|
install -m 0755 "${sops_file}" /usr/local/bin/sops
|
|
sops --version >| /root/.ciss/cdlb/log/sops.log
|
|
age --version >| /root/.ciss/cdlb/log/age.log
|
|
|
|
rm -f -- "/tmp/${sops_file}"
|
|
rm -f -- "/tmp/${checksums_file}"
|
|
rm -f -- "/tmp/${bundle_file}"
|
|
rm -f -- "/tmp/${certificate_file}"
|
|
rm -f -- "/tmp/${signature_file}"
|
|
|
|
if [[ -f /root/.config/sops/age/keys.txt ]]; then
|
|
chmod 0400 /root/.config/sops/age/keys.txt
|
|
fi
|
|
|
|
printf "\e[92m✅ '%s' applied successfully. \e[0m\n" "${0}"
|
|
|
|
return 0
|
|
}
|
|
|
|
if [[ "${CISS_SOPS_TEST_MODE:-false}" != "true" ]]; then
|
|
main "$@"
|
|
exit 0
|
|
fi
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|