本文目录导读:

Git 钩子脚本可以通过多种机制实现防篡改策略检查,主要目的是确保提交的代码符合安全规范、未被恶意修改或绕过审核流程,以下是几种常见的实现方案:
签名验证检查(最严格)
在 pre-receive 或 update 钩子中验证 GPG 签名
#!/bin/bash
# .git/hooks/pre-receive
# 读取标准输入中的引用更新
while read oldrev newrev refname; do
# 只检查分支更新(非删除)
if [ "$newrev" != "0000000000000000000000000000000000000000" ]; then
# 获取提交的 GPG 签名信息
gpg_info=$(git log --format="%GG" -1 "$newrev")
# 检查签名状态
if ! git verify-commit "$newrev" 2>/dev/null; then
echo "ERROR: Commit $newrev 没有有效的 GPG 签名或签名验证失败"
exit 1
fi
# 检查签名者是否在授权列表中
signer=$(git log --format="%GS" -1 "$newrev")
authorized_signers=("developer@company.com" "admin@company.com")
if [[ ! " ${authorized_signers[@]} " =~ " ${signer} " ]]; then
echo "ERROR: 提交 $newrev 的签名者 $signer 未授权"
exit 1
fi
fi
done
文件完整性检查
在 pre-commit 钩子中检查关键文件是否被篡改
#!/bin/bash
# .git/hooks/pre-commit
# 配置需要保护的文件列表
PROTECTED_FILES=(
"package.json"
"package-lock.json"
"yarn.lock"
"Dockerfile"
"docker-compose.yml"
)
# 计算文件的哈希值(基线)
CHECKSUM_FILE=".git/checksum.baseline"
# 如果基线文件存在,检查是否有未授权的修改
if [ -f "$CHECKSUM_FILE" ]; then
while IFS=' ' read -r expected_hash file_path; do
if [ -f "$file_path" ]; then
# 获取当前暂存区中的文件哈希
# staged_hash=$(git hash-object "$file_path")
current_hash=$(sha256sum "$file_path" | awk '{print $1}')
if [ "$current_hash" != "$expected_hash" ]; then
echo "ERROR: 文件 $file_path 已被修改!"
echo "预期哈希: $expected_hash"
echo "当前哈希: $current_hash"
echo "请检查修改是否经过批准"
exit 1
fi
fi
done < "$CHECKSUM_FILE"
fi
创建基线文件的脚本
#!/bin/bash
# generate-baseline.sh
PROTECTED_FILES=(
"package.json"
"package-lock.json"
"Dockerfile"
)
for file in "${PROTECTED_FILES[@]}"; do
if [ -f "$file" ]; then
hash=$(sha256sum "$file" | awk '{print $1}')
echo "$hash $file"
fi
done > .git/checksum.baseline
echo "Baseline generated in .git/checksum.baseline"
完整性检查
在 commit-msg 钩子中检查提交信息
#!/bin/bash
# .git/hooks/commit-msg
commit_msg_file="$1"
commit_msg=$(cat "$commit_msg_file")
# 强制要求提交信息格式
# 格式: [类型]: 描述 (#issue_number)
pattern="^\[(feat|fix|docs|chore|refactor|test|ci)\]: .+ \(#[0-9]+\)$"
if ! [[ "$commit_msg" =~ $pattern ]]; then
echo "ERROR: 提交信息格式无效"
echo "正确格式: [类型]: 描述 (#issue_number)"
echo "类型: feat|fix|docs|chore|refactor|test|ci"
echo "示例: [feat]: 添加用户注册功能 (#123)"
exit 1
fi
# 检查是否包含敏感信息
sensitive_patterns=(
"password"
"secret"
"api[_-]?key"
"token"
"credential"
)
for pattern in "${sensitive_patterns[@]}"; do
if echo "$commit_msg" | grep -qiE "$pattern"; then
echo "ERROR: 提交信息中可能包含敏感信息"
echo "请移除与敏感信息相关的内容"
exit 1
fi
done
完整性校验实现
使用哈希链验证提交完整性
#!/bin/bash
# .git/hooks/post-receive
# 检查所有新提交是否构成一个完整的哈希链
check_commit_chain() {
local start_commit="$1"
local current_commit="$start_commit"
while [ "$current_commit" != "0000000000000000000000000000000000000000" ]; do
# 获取当前提交的父提交
parent_hash=$(git log --format="%P" -1 "$current_commit" | awk '{print $1}')
# 检查提交的完整性(签名、时间戳等)
if ! git cat-file -e "$current_commit" 2>/dev/null; then
echo "ERROR: 提交 $current_commit 对象损坏"
exit 1
fi
# 检查提交是否在合理的时间范围内
commit_time=$(git log --format="%ci" -1 "$current_commit")
current_time=$(date +"%Y-%m-%d %H:%M:%S %z")
# 如果只有一个父提交,继续检查
if [ -n "$parent_hash" ] && [ "$parent_hash" != "0000000000000000000000000000000000000000" ]; then
current_commit="$parent_hash"
else
break
fi
done
}
while read oldrev newrev refname; do
if [ "$newrev" != "0000000000000000000000000000000000000000" ]; then
check_commit_chain "$newrev"
fi
done
集中式签名验证系统
使用密钥服务器验证
#!/bin/bash
# .git/hooks/pre-receive - 带密钥验证
# 配置密钥服务器
KEY_SERVER="https://keys.example.com"
ALLOWED_EMAILS="@company.com$"
verify_signing_key() {
local commit_hash="$1"
# 获取签名的 key ID
key_id=$(git log --format="%GK" -1 "$commit_hash")
if [ -z "$key_id" ]; then
echo "ERROR: 提交 $commit_hash 没有签名"
exit 1
fi
# 从密钥服务器获取公钥并验证
response=$(curl -s "$KEY_SERVER/api/verify/$key_id")
if echo "$response" | grep -q '"valid": false'; then
echo "ERROR: 签名密钥 $key_id 已过期或已吊销"
exit 1
fi
# 验证签名者邮箱域名
signer_email=$(git log --format="%ae" -1 "$commit_hash")
if [[ ! "$signer_email" =~ $ALLOWED_EMAILS ]]; then
echo "ERROR: 提交者 $signer_email 不允许直接推送"
exit 1
fi
}
while read oldrev newrev refname; do
if [ "$newrev" != "0000000000000000000000000000000000000000" ]; then
verify_signing_key "$newrev"
fi
done
部署前完整性检查
在 post-receive 钩子中验证部署包
#!/bin/bash
# .git/hooks/post-receive
# 创建检查和验证
verify_deployment_integrity() {
local commit_hash="$1"
local temp_dir=$(mktemp -d)
# 将工作树导出到临时目录
git archive --format=tar "$commit_hash" | tar -x -C "$temp_dir"
# 验证关键文件的完整性
cd "$temp_dir"
# 检查 package.json 是否被正确签名
if [ -f "package.json" ]; then
if [ -f "package.json.sig" ]; then
if ! gpg --verify package.json.sig package.json 2>/dev/null; then
echo "ERROR: package.json 签名验证失败"
rm -rf "$temp_dir"
exit 1
fi
fi
fi
# 检查 Dockerfile 的完整性
if [ -f "Dockerfile" ]; then
# 使用自定义哈希算法验证
expected_hash=$(cat Dockerfile | sha256sum | awk '{print $1}')
stored_hash=$(cat Dockerfile.sha256 2>/dev/null)
if [ -n "$stored_hash" ] && [ "$expected_hash" != "$stored_hash" ]; then
echo "ERROR: Dockerfile 已被修改"
rm -rf "$temp_dir"
exit 1
fi
fi
rm -rf "$temp_dir"
}
while read oldrev newrev refname; do
if [ "$newrev" != "0000000000000000000000000000000000000000" ]; then
# 只验证 master/main 分支
if [[ "$refname" == "refs/heads/master" ]] || [[ "$refname" == "refs/heads/main" ]]; then
verify_deployment_integrity "$newrev"
fi
fi
done
使用 Git Hooks 管理工具
创建一个完整的钩子管理框架
#!/bin/bash
# .git/hooks/pre-receive - 组合多种检查
# 引入配置
source "$(dirname "$0")/hook_config.sh"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$HOOK_LOG_FILE"
}
# 主检查函数
perform_integrity_check() {
local commit_hash="$1"
local check_passed=0
# 1. 检查 GPG 签名
if $ENFORCE_GPG_SIGNING; then
if ! git verify-commit "$commit_hash" 2>/dev/null; then
log "FAIL: GPG 签名验证失败 - $commit_hash"
echo "ERROR: GPG 签名验证失败"
check_passed=1
fi
fi
# 2. 检查提交者权限
if $ENFORCE_COMMITTER_CHECK; then
local author=$(git log --format="%ae" -1 "$commit_hash")
if [[ "$author" != *"$ALLOWED_DOMAIN" ]]; then
log "FAIL: 未授权提交者 $author"
echo "ERROR: 提交者 $author 未被授权"
check_passed=1
fi
fi
# 3. 检查文件完整性
if $ENFORCE_FILE_INTEGRITY; then
for file in "${PROTECTED_FILES[@]}"; do
if git ls-tree -r "$commit_hash" | grep -q "$file"; then
local file_hash=$(git show "$commit_hash:$file" | sha256sum | awk '{print $1}')
if [ -f ".git/allowed-hashes/$file" ]; then
local allowed_hash=$(cat ".git/allowed-hashes/$file")
if [ "$file_hash" != "$allowed_hash" ]; then
log "FAIL: 文件完整性检查失败 - $file"
echo "ERROR: 文件 $file 哈希不匹配"
check_passed=1
fi
fi
fi
done
fi
return $check_passed
}
# 主循环
while read oldrev newrev refname; do
if [ "$newrev" != "0000000000000000000000000000000000000000" ]; then
if ! perform_integrity_check "$newrev"; then
log "REJECT: 提交 $newrev 被拒绝"
exit 1
else
log "ACCEPT: 提交 $newrev 通过检查"
fi
fi
done
最佳实践建议
- 分层防御:结合多种检查机制,如签名 + 文件完整性 + 权限控制
- 性能考虑:在 pre-receive 钩子中检查,避免在客户端 pre-commit 钩子中影响开发体验
- 安全存储:将基线哈希存储在安全位置,如加密的配置文件或外部密钥管理系统
- 审计日志:记录所有通过/拒绝的提交操作
- 容错机制:在检查失败时提供清晰的错误信息,并建议正确的操作方式
- 定期更新:定期更新授权签名列表和文件哈希基线
通过以上这些机制,可以有效防止代码篡改和未经授权的修改,确保 Git 仓库的完整性和安全性。