👉 1、注册GitLab Runner
GitLab Runner 注册执行器为docker,是因为gitlab内置的代码扫描工具Semgrep需要使用它,可以在同一个节点上注册多个runner,方法和结果如下图👉 2、安装镜像扫描工具(gitlab内置的镜像扫描工具就是基于trivy命令封装的)
# 下载 trivy 命令并安装curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/binsu - gitlab-runner -c "trivy --version"# 拉取最新的漏洞库trivy image mirror.gcr.io/aquasec/trivy-db:2
# 提前拉取镜像,避免第一次构建拉取时间过长docker pull registry.gitlab.com/security-products/semgrep:6docker pull registry.gitlab.com/security-products/container-scanning:8docker images #查看拉取情况
from flask import Flask# 新增:导入request用于获取用户输入(为XSS漏洞做准备)from flask import requestapp = Flask(__name__)# Bug 1:硬编码敏感API密钥(Semgrep高风险漏洞,关键字「secret」「key」触发扫描)# 符合Semgrep内置规则 python.security.secrets.hardcoded-api-keyHARDCODED_SECRET_KEY = "flask_secret_123456_unsafe" # 硬编码敏感数据app.config['SECRET_KEY'] = HARDCODED_SECRET_KEY # 有效使用,确保Semgrep能识别@app.route('/')def hello(): # 后续测试可修改此内容,验证流程闭环 return "Hello Flask! 容器部署成功~ v2 "@app.route('/greet')def greet(): user_name = request.args.get('name', 'Guest') return f"Hello, {user_name}! 欢迎访问~" if __name__ == '__main__': # Bug 2:Flask开启调试模式(Semgrep中风险漏洞) # 符合Semgrep内置规则 python.flask.security.debug-mode.enabled app.run(host='0.0.0.0', port=5000, debug=True)
# ======================================================# GitLab CI/CD 配置文件# 用途:Python Flask应用的安全扫描与部署流水线# 包含阶段:代码扫描 → 依赖安装 → 镜像构建 → 镜像扫描 → 部署# ======================================================# ==================== 模板引入 ====================# 引入GitLab官方安全扫描模板,简化配置include: # SAST:静态应用安全测试,自动扫描代码中的安全漏洞 - template: Security/SAST.gitlab-ci.yml # 容器扫描:扫描Docker镜像中的安全漏洞 - template: Security/Container-Scanning.gitlab-ci.yml# ==================== 流水线阶段定义 ====================# 定义全流程五个阶段,按顺序执行stages: - code-scan # 阶段1:代码安全扫描(使用Semgrep) - install # 阶段2:安装Python/Flask依赖,验证依赖可用性 - build # 阶段3:验证应用语法,构建Docker镜像 - image-scan # 阶段4:扫描Docker镜像安全漏洞 - deploy # 阶段5:本地停止旧容器,启动新构建的Docker镜像# ==================== 阶段1:代码安全扫描 ====================# 使用GitLab SAST功能进行静态代码安全分析sast: stage: code-scan # 绑定到code-scan阶段 tags: - scanning # 使用带有scanning标签的Runner执行 # SAST扫描配置变量 variables: # 指定使用Semgrep作为默认分析器 SAST_DEFAULT_ANALYZERS: "semgrep" # 使用官方安全规则集:p/security(通用安全)+ p/python(Python专用) SAST_SEMGREP_RULESETS: "p/security,p/python" # 排除不需要扫描的目录 SAST_EXCLUDED_PATHS: "vendor/, node_modules/, .git/" # 引入自定义Semgrep规则文件(关键:增强扫描能力) SAST_SEMGREP_ADDITIONAL_RULES: "./semgrep-rules/custom-python-rules.yml" # 扫描前执行的脚本,主要用于调试和验证 before_script: # 查看项目目录结构,确认文件存在 - echo "=== 列出Docker容器内项目目录文件 ===" - ls -la /builds/root/app-demo-src/ # 确认自定义规则文件存在 - echo "=== 列出semgrep-rules文件夹内文件 ===" - ls -la /builds/root/app-demo-src/semgrep-rules/ # 验证自定义规则文件是否存在 - echo "=== 验证自定义规则文件是否存在 ===" - test -f /builds/root/app-demo-src/semgrep-rules/custom-python-rules.yml && echo "自定义规则文件存在" || echo "自定义规则文件不存在" # 新增:手动执行Semgrep扫描,用于调试和详细日志输出 - echo "=== 手动执行Semgrep原生扫描 ===" - semgrep scan --config /builds/root/app-demo-src/semgrep-rules/custom-python-rules.yml --verbose /builds/root/app-demo-src/app.py# ==================== 阶段2:安装依赖 ====================# 安装Python和Flask项目所需的依赖包install_flask_deps: stage: install # 绑定到install阶段 tags: - docker # 使用带有docker标签的Runner执行 # 缓存配置:缓存pip下载的包,提升后续运行效率 cache: paths: - ~/.cache/pip/ # 主要执行脚本 script: # 升级pip到最新版本 - echo "===== 开始升级pip =====" - pip3 install --upgrade pip --user # 安装requirements.txt中指定的所有依赖 - echo "===== 开始安装Flask项目依赖 =====" - pip3 install --no-cache-dir -r requirements.txt --user # 验证Flask依赖是否安装成功 - echo "===== 验证依赖安装结果 =====" - python3 -c "import flask; print('Flask依赖安装成功,版本:', flask.__version__)" # 工件(artifacts)配置:保存安装成功的依赖信息 artifacts: paths: - requirements.txt # 保存依赖列表文件 when: on_success # 仅当任务成功时保存 expire_in: 1 hour # 工件保存1小时后过期# ==================== 阶段3:构建镜像 ====================# 构建应用的Docker镜像build_flask_docker: stage: build # 绑定到build阶段 tags: [docker] # 使用带有docker标签的Runner执行 dependencies: [install_flask_deps] # 依赖install_flask_deps阶段成功完成 script: # 验证Python代码语法正确性 - echo "===== 验证 app.py 语法正确性 =====" - python3 -m py_compile app.py # 语法检查,报错则终止CI # 构建Docker镜像并标记为latest - echo "===== 构建本地 Docker 镜像(flask-app:latest) =====" - docker build -t flask-app:latest . # 查看构建成功的镜像信息 - echo "===== 本地镜像构建成功,查看镜像信息 =====" - docker images | grep flask-app # 打印本地镜像信息 # 不允许失败:如果构建失败,整个流水线应该停止 allow_failure: false# ==================== 阶段4:镜像安全扫描 ====================# 使用Trivy扫描Docker镜像中的安全漏洞container_scanning: stage: image-scan # 绑定到image-scan阶段 tags: [docker] # 使用带有docker标签的Runner执行 dependencies: [build_flask_docker] # 依赖build_flask_docker阶段成功完成 # 不允许失败:如果扫描失败,整个流水线应该停止 allow_failure: false script: # 验证环境工具版本 - echo "===== 1. 验证环境与 Docker 守护进程 =====" - docker --version # 验证Docker客户端可用 - trivy --version # 验证Trivy工具可用 # 执行容器安全扫描,生成GitLab标准格式的JSON报告 - echo "===== 2. 生成漏洞扫描报告(GitLab 标准格式) =====" - trivy image --skip-version-check --skip-db-update --severity CRITICAL,HIGH --format json -o gl-container-scanning-report.json flask-app:latest # 生成软件物料清单(SBOM),CycloneDX格式 - echo "===== 3. 生成 SBOM 报告(CycloneDX 格式) =====" - trivy image --skip-version-check --skip-db-update --format cyclonedx -o gl-sbom-flask-app.cdx flask-app:latest # 验证报告文件已正确生成 - echo "===== 4. 验证报告文件已生成 =====" - ls -l# ==================== 阶段5:部署应用 ====================# 在本地部署Flask应用deploy_flask_local: stage: deploy # 绑定到deploy阶段 tags: [docker] # 使用带有docker标签的Runner执行 dependencies: [build_flask_docker] # 依赖build_flask_docker阶段成功完成 # 限制部署条件:仅在master或main分支执行 only: - main script: # 定义容器配置变量 - echo "===== 开始本地部署Flask应用 =====" - CONTAINER_NAME="flask-container" # 容器名称 - PORT_MAPPING="5000:5000" # 端口映射:主机端口:容器端口 # 停止并删除已存在的同名容器(避免端口冲突) - echo "===== 停止并删除已存在的同名容器(避免端口占用/冲突) =====" - docker stop ${CONTAINER_NAME} || true # 如果容器不存在,忽略错误 - docker rm ${CONTAINER_NAME} || true # 如果容器不存在,忽略错误 # 使用构建的镜像启动新容器 - echo "===== 本地启动Docker容器 =====" - docker run -d --name ${CONTAINER_NAME} -p ${PORT_MAPPING} flask-app:latest # 验证容器运行状态 - echo "===== 验证容器运行状态 =====" - docker ps | grep ${CONTAINER_NAME} # 查找运行中的容器 # 打印访问地址信息 - echo "===== 打印应用访问地址 =====" - LOCAL_IP=$(hostname -I | awk '{print $1}') # 获取本机局域网IP - echo "======================================" - echo "Flask应用本地部署成功!" - echo "本地访问地址:http://localhost:5000" - echo "局域网访问地址:http://${LOCAL_IP}:5000" - echo "容器名称:${CONTAINER_NAME}" - echo "镜像名称:flask-app:latest"
代码扫描规则的文件 semgrep-rules/custom-python-rules.yml(可根据实际情况自定义)rules:- id: insecure-eval-use patterns: - pattern: eval($VAR) - pattern-not: eval("...") fix: secure_eval($VAR) message: Calling 'eval' with user input languages: [python] severity: MEDIUM- id: custom-flask-hardcoded-secret pattern: HARDCODED_SECRET_KEY = "flask_secret_123456_unsafe" message: "自定义规则:检测到Flask硬编码敏感密钥,存在信息泄露风险(对应Bandit B105)" severity: LOW languages: [python] metadata: category: security cwe: "CWE-798: Use of Hard-coded Credentials" bandit_id: B105# 规则3:自定义Flask绑定所有接口扫描(已包含pattern,符合schema规范)- id: custom-flask-bind-all-interfaces pattern: app.run(host='0.0.0.0', port=5000, debug=True) message: "自定义规则:检测到Flask绑定所有网络接口,可能导致未授权访问(对应Bandit B104)" severity: MEDIUM languages: [python] metadata: category: security cwe: "CWE-605: Multiple Binds to the Same Port" bandit_id: B104