112 lines
3.0 KiB
Bash
Executable File
112 lines
3.0 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
|
|
set -eu
|
|
|
|
for cmd in git sops openssl; do
|
|
if ! command -v "${cmd}" >/dev/null 2>&1; then
|
|
echo "pre-commit: required command missing: ${cmd}" >&2
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
if [ -z "${SOPS_SYNC_HMAC_KEY:-}" ]; then
|
|
echo "pre-commit: SOPS_SYNC_HMAC_KEY is required" >&2
|
|
exit 1
|
|
fi
|
|
|
|
repo_root=$(git rev-parse --show-toplevel)
|
|
regex_script="${repo_root}/scripts/lib/sops-path-regexes"
|
|
lib_script="${repo_root}/scripts/lib/sops-sync-lib"
|
|
|
|
if [ ! -x "${regex_script}" ]; then
|
|
echo "pre-commit: missing helper ${regex_script}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f "${lib_script}" ]; then
|
|
echo "pre-commit: missing helper ${lib_script}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
. "${lib_script}"
|
|
|
|
regexes=("${(@f)$("${regex_script}")}")
|
|
typeset -A candidates
|
|
|
|
while IFS= read -r staged_path; do
|
|
[ -n "${staged_path}" ] || continue
|
|
|
|
candidate_enc=""
|
|
if [[ "${staged_path}" == *.enc.yaml ]]; then
|
|
candidate_enc="${staged_path}"
|
|
elif [[ "${staged_path}" == *.yaml ]]; then
|
|
if git ls-files --error-unmatch -- "${staged_path}" >/dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
candidate_enc=$(plain_to_enc "${staged_path}") || continue
|
|
fi
|
|
|
|
[ -n "${candidate_enc}" ] || continue
|
|
if matches_any_rule "${candidate_enc}" "${regexes[@]}"; then
|
|
candidates["${candidate_enc}"]=1
|
|
fi
|
|
done < <(git diff --cached --name-only --diff-filter=ACMR)
|
|
|
|
for enc_path in "${(@k)candidates}"; do
|
|
plain_path=$(enc_to_plain "${enc_path}") || continue
|
|
sidecar_path=$(hmac_sidecar_for_enc "${enc_path}")
|
|
|
|
if [ ! -f "${plain_path}" ]; then
|
|
continue
|
|
fi
|
|
|
|
if has_unstaged_changes "${plain_path}"; then
|
|
echo "pre-commit: plaintext file has unstaged changes: ${plain_path}" >&2
|
|
echo "pre-commit: stage or revert the plaintext change before committing" >&2
|
|
exit 1
|
|
fi
|
|
|
|
current_hmac=$(compute_hmac_for_file "${plain_path}")
|
|
tracked_hmac=""
|
|
if [ -f "${sidecar_path}" ]; then
|
|
tracked_hmac=$(read_sidecar "${sidecar_path}" || true)
|
|
fi
|
|
|
|
if [ "${current_hmac}" = "${tracked_hmac}" ] && [ -f "${enc_path}" ]; then
|
|
continue
|
|
fi
|
|
|
|
encrypt_plain_to_enc "${plain_path}" "${enc_path}"
|
|
write_sidecar "${sidecar_path}" "${current_hmac}"
|
|
git add "${enc_path}" "${sidecar_path}"
|
|
done
|
|
|
|
for enc_path in "${(@k)candidates}"; do
|
|
sidecar_path=$(hmac_sidecar_for_enc "${enc_path}")
|
|
|
|
if ! git cat-file -e ":${enc_path}" 2>/dev/null; then
|
|
echo "pre-commit: staged encrypted file missing: ${enc_path}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! git cat-file -e ":${sidecar_path}" 2>/dev/null; then
|
|
echo "pre-commit: staged sync sidecar missing: ${sidecar_path}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
tmp_enc=$(mktemp)
|
|
trap 'rm -f "${tmp_enc}"' EXIT INT TERM
|
|
git show ":${enc_path}" > "${tmp_enc}"
|
|
|
|
staged_hmac=$(git show ":${sidecar_path}" | tr -d '\r\n')
|
|
computed_hmac=$(decrypt_enc_to_plain "${tmp_enc}" | compute_hmac_for_stdin)
|
|
|
|
rm -f "${tmp_enc}"
|
|
trap - EXIT INT TERM
|
|
|
|
if [ "${computed_hmac}" != "${staged_hmac}" ]; then
|
|
echo "pre-commit: staged encrypted file and sidecar are out of sync: ${enc_path}" >&2
|
|
exit 1
|
|
fi
|
|
done
|