Gitlab pre-receive 对提交进行校验

2025-04-09  本文已影响0人  87d6dc4b11a7

以下是分模块实现的完整代码方案,包含 配置管理日志模块校验逻辑主程序,采用工程化结构设计:


目录结构

/opt/gitlab/custom_hooks/
├── config.sh               # 集中配置
├── pre-receive             # 主入口脚本
├── modules/
│   ├── log.sh              # 日志模块
│   ├── validator.sh        # 校验核心逻辑
│   └── utils.sh            # 工具函数
└── logs/
    └── hook_audit.log      # 审计日志

模块 1: 配置文件 (config.sh)

#!/bin/bash

# 全局配置参数
readonly MAX_FILE_SIZE=104857600              # 100MB
readonly COMMIT_MSG_REGEX='^[A-Z]+-[0-9]+: '  # 提交信息规则
readonly MAX_COMMITS_PROCESS=500              # 单次推送最多处理500个提交(防溢出)

# 文件白名单配置
readonly WHITELIST_PATTERNS=(
  "*.pdf"             # 允许所有PDF文件
  "vendor/*"          # 允许vendor目录下所有文件
  "lib/*.so"          # 允许lib目录下的.so文件
)

# 日志配置
readonly LOG_ENABLED=true
readonly LOG_FILE="/opt/gitlab/custom_hooks/logs/hook_audit.log"

模块 2: 日志模块 (modules/log.sh)

#!/bin/bash

# 导入配置
source /opt/gitlab/custom_hooks/config.sh

# 日志记录函数
log_rejection() {
  local commit_hash="$1"
  local reason="$2"
  local file_path="${3:-N/A}"

  if [ "$LOG_ENABLED" = true ]; then
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] REJECTED - Commit: ${commit_hash} - File: ${file_path} - Reason: ${reason}" >> "$LOG_FILE"
  fi
}

# 错误格式化输出
print_error() {
  local commit_hash="$1"
  local reason="$2"
  local details="$3"

  echo "==================================================================="
  echo "[REJECTED] 提交 ${commit_hash:0:8} 未通过校验"
  echo "原因: ${reason}"
  echo "详情: ${details}"
  echo "==================================================================="
}

模块 3: 校验逻辑 (modules/validator.sh)

#!/bin/bash

# 导入依赖
source /opt/gitlab/custom_hooks/config.sh
source /opt/gitlab/custom_hooks/modules/log.sh
source /opt/gitlab/custom_hooks/modules/utils.sh

# 提交信息校验
validate_commit_message() {
  local commit="$1"
  local msg=$(git log --format=%B -n 1 "$commit")
  
  if ! [[ "$msg" =~ $COMMIT_MSG_REGEX ]]; then
    print_error "$commit" "提交信息格式错误" "要求格式: ${COMMIT_MSG_REGEX}\n实际内容: ${msg}"
    log_rejection "$commit" "Invalid commit message" 
    return 1
  fi
  return 0
}

# 文件白名单检查
is_whitelisted() {
  local file="$1"
  
  for pattern in "${WHITELIST_PATTERNS[@]}"; do
    if [[ "$file" == $pattern ]]; then
      return 0
    fi
  done
  return 1
}

# 文件大小校验
validate_file_size() {
  local commit="$1"
  
  # 使用 null 分隔符处理特殊文件名
  git diff-tree -z --no-commit-id --name-only -r "$commit" | while IFS= read -r -d '' file; do
    [ -z "$file" ] && continue

    # 白名单跳过
    if is_whitelisted "$file"; then
      echo "[INFO] 跳过白名单文件: $file"
      continue
    fi

    # 获取文件大小
    file_size=$(git cat-file -s "$commit:$file" 2>/dev/null)
    [ $? -ne 0 ] && continue  # 忽略已删除文件

    if [ "$file_size" -gt "$MAX_FILE_SIZE" ]; then
      print_error "$commit" "文件大小超限" "文件: ${file}\n限制: $((MAX_FILE_SIZE/1024/1024))MB, 实际: $((file_size/1024/1024))MB"
      log_rejection "$commit" "File size exceeded" "$file"
      return 1
    fi
  done

  return 0
}

模块 4: 工具函数 (modules/utils.sh)

#!/bin/bash

# 安全获取提交列表
get_safe_commit_list() {
  local oldrev="$1"
  local newrev="$2"
  
  # 限制处理的提交数量防止内存溢出
  git rev-list --max-count=$MAX_COMMITS_PROCESS "$oldrev".."$newrev"
}

# 检查是否为新分支创建
is_new_branch() {
  [ "$oldrev" = "0000000000000000000000000000000000000000" ]
}

主入口脚本 (pre-receive)

#!/bin/bash

# 初始化环境
HOOKS_DIR="$(dirname "$0")"
source "$HOOKS_DIR/config.sh"
source "$HOOKS_DIR/modules/utils.sh"
source "$HOOKS_DIR/modules/validator.sh"
source "$HOOKS_DIR/modules/log.sh"

# 主处理流程
while read -r oldrev newrev refname; do
  # 跳过空新修订(删除分支)
  [ -z "$newrev" ] && continue

  # 处理新分支创建场景
  is_new_branch && oldrev="${newrev}^"

  # 获取安全提交列表
  commits=$(get_safe_commit_list "$oldrev" "$newrev")

  # 遍历每个提交
  for commit in $commits; do
    validate_commit_message "$commit" || exit 1
    validate_file_size "$commit" || exit 1
  done
done

exit 0

部署步骤

  1. 创建目录结构

    sudo mkdir -p /opt/gitlab/custom_hooks/{modules,logs}
    sudo chown -R git:git /opt/gitlab/custom_hooks
    
  2. 设置权限

    sudo chmod -R 755 /opt/gitlab/custom_hooks
    sudo chmod 644 /opt/gitlab/custom_hooks/logs/hook_audit.log
    
  3. 链接到仓库

    # 进入目标仓库的 hooks 目录
    cd /var/opt/gitlab/git-data/repositories/<group>/<project>.git/custom_hooks/
    
    # 创建软链接
    ln -s /opt/gitlab/custom_hooks/pre-receive .
    

验证测试

测试用例 1:提交信息违规

git commit --allow-empty -m "invalid message"
git push origin main

# 预期输出:
# [REJECTED] 提交 xxxxxxxx 未通过校验
# 原因: 提交信息格式错误

测试用例 2:文件大小超限

dd if=/dev/zero of=oversize_file bs=1M count=101
git add oversize_file
git commit -m "TASK-001: Add large file"
git push origin main

# 预期输出:
# [REJECTED] 提交 xxxxxxxx 未通过校验
# 原因: 文件大小超限
remote: -------------------------------------------------------------------
remote: [REJECTED] 提交 7e624af8 包含超大文件: oversize_file
remote: 大小限制: 100MB, 实际大小: 110MB
remote: -------------------------------------------------------------------
To ssh://<gitlab-url>/test/test.git
 ! [remote rejected] main -> main (pre-receive hook declined)
error: failed to push some refs to 'ssh://<gitlab-url>/test/test.git'

检查日志

tail -f /opt/gitlab/custom_hooks/logs/hook_audit.log
# 输出示例:
# [2023-10-05 14:30:00] REJECTED - Commit: a1b2c3d4 - File: oversize_file - Reason: File size exceeded

此设计实现了以下关键特性:

  1. 模块解耦:各功能独立成模块,修改校验规则无需改动主流程
  2. 安全防护
    • 限制处理的最大提交数量 (MAX_COMMITS_PROCESS)
    • 使用 null 分隔符处理特殊文件名
  3. 审计追踪:详细日志记录所有拒绝操作
  4. 灵活配置:白名单机制和阈值参数集中管理

如需扩展功能(如添加新的校验规则),只需创建新模块并在主流程中调用即可。这种架构特别适合需要统一管理多个 GitLab 仓库的企业环境。

上一篇 下一篇

猜你喜欢

热点阅读