如何限制Git推送仅接受签名且匹配指定邮箱的提交
Git仓库限制:仅允许指定邮箱+对应GPG签名推送
问题背景
使用Git托管网站代码,已实现邮箱白名单推送限制,但扩展GPG签名验证时持续触发以下错误:
Commit $commit is not signed or has an invalid signature.Commit $commit by $author_email is not signed or signed with an unauthorized key.
核心问题分析
- 服务器GPG信任缺失:
git verify-commit依赖服务器导入并信任用户的GPG公钥,未导入的话会直接判定签名无效 - 指纹提取逻辑缺陷:原脚本的正则匹配无法精准捕获指纹,且未处理大小写差异
- 指纹匹配逻辑错误:16位短指纹需匹配长指纹的最后16位,原脚本直接全量对比导致不匹配
解决方案
步骤1:服务器导入并信任用户GPG公钥
要求用户导出自己的GPG公钥(执行gpg --armor --export myemail@example.dev),在服务器上执行以下操作:
# 导入用户公钥 gpg --import user_public_key.asc # 标记为信任(避免验证时提示未信任) gpg --edit-key 0E3DFE091F345CC152237C5502E78387B88700FE # 输入 trust → 选择5(ultimate trust)→ 输入 save 退出
步骤2:修复后的Git钩子脚本(pre-receive)
# Allowed users and their corresponding GPG fingerprints (full 40-char or last 16-char) declare -A ALLOWED_USERS ALLOWED_USERS["myemail@example.dev"]="0E3DFE091F345CC152237C5502E78387B88700FE" ALLOWED_USERS["otheremail@icontrol.dev"]="A787E5542209332D" # Check if user is in allowed list is_allowed_user() { local user_email=$1 [[ -n "${ALLOWED_USERS[$user_email]}" ]] } # Verify commit signature and key authorization is_signed_and_allowed() { local commit=$1 local user_email=$2 local allowed_fingerprint="${ALLOWED_USERS[$user_email]}" local extracted_fingerprint # Quick check for valid signature (suppress verbose output) if ! git verify-commit --quiet "$commit"; then echo "Commit $commit: unsigned or invalid/untrusted signature." return 1 fi # Extract full 40-char fingerprint from GPG output extracted_fingerprint=$(git verify-commit "$commit" 2>&1 | grep -i "fingerprint:" | awk '{print $NF}' | tr -d ' ') if [[ -z "$extracted_fingerprint" ]]; then echo "Commit $commit: failed to extract GPG fingerprint." return 1 fi # Normalize to uppercase to avoid case mismatch extracted_fingerprint=$(echo "$extracted_fingerprint" | tr '[:lower:]' '[:upper:]') allowed_fingerprint=$(echo "$allowed_fingerprint" | tr '[:lower:]' '[:upper:]') # Match full fingerprint or last 16 chars (for short fingerprint entries) if [[ "$extracted_fingerprint" != "$allowed_fingerprint" && "${extracted_fingerprint: -16}" != "$allowed_fingerprint" ]]; then echo "Commit $commit: unauthorized GPG key $extracted_fingerprint for user $user_email. Allowed key: $allowed_fingerprint." return 1 fi return 0 } # Process push requests while read oldrev newrev refname; do # Skip branch deletion if [[ "$newrev" == "0000000000000000000000000000000000000000" ]]; then continue fi # Validate every new commit in the push for commit in $(git rev-list "$oldrev".."$newrev"); do author_email=$(git log -1 --format="%ae" "$commit") if ! is_allowed_user "$author_email"; then echo "Unauthorized user: $author_email" exit 1 fi if ! is_signed_and_allowed "$commit" "$author_email"; then exit 1 fi done done exit 0
关键修复说明
- GPG信任处理:必须在服务器端导入并信任用户公钥,否则签名验证会直接失败
- 精准指纹提取:通过
grep -i "fingerprint:"定位指纹行,避免正则匹配遗漏 - 大小写兼容:统一转换为大写,解决Windows终端复制指纹可能的大小写差异
- 灵活指纹匹配:支持全40位指纹和16位短指纹(匹配长指纹最后16位)
- 优化验证流程:先用
--quiet快速验证签名有效性,减少冗余输出
内容的提问来源于stack exchange,提问作者Paul Williams
相关产品推荐
相关产品推荐

