GitProxy 2.3 Docker Compose 部署指南

在 Ubuntu 22.04 服务器上以 Docker Compose 一键部署 GitProxy 2.3 推送审批网关,配套 Gitea 上游 Git 服务与自签 TLS 证书,完成账号创建、仓库授权与推送-审批-重推全链路验证。

GitProxyGitProxyGitOps2026/10/26

GitProxy 2.3 Docker Compose 部署指南

目标:在一台 Ubuntu 22.04 服务器上用 Docker Compose 部署 GitProxy v2.3.0 推送审批网关,并以 Gitea 1.23.8 作为上游 Git 服务,完成 TLS 证书签发、服务启动验证、账号初始化、仓库授权与「推送 → 审批 → 重推生效」全链路实测。已在 Ubuntu 22.04(x86_64)服务器实测通过。

GitProxy 是 FINOS 开源的 Git 推送审批网关:所有 git push 先被网关截获,经过仓库白名单、提交消息、作者邮箱、diff 内容扫描等检查后进入审批队列,由授权审批人批准(或驳回)后才能落到上游仓库。软件定位、架构与选型背景见《GitProxy 2.3 介绍与选型指南》;本篇只解决「如何把这套网关完整跑起来」。

一、环境信息

项目取值
操作系统Ubuntu 22.04(x86_64)
内核Linux 5.15+
GitProxy 版本v2.3.0(官方镜像 finos/git-proxy:v2.3.0)
上游 Git 服务Gitea 1.23.8(gitea/gitea:1.23.8)
演示工作机ubuntu:22.04(仅装 git,作为 Git 客户端工作台)
Docker / ComposeDocker Engine 27.x + Compose v2(docker compose 插件)
实验目录/opt/gitproxy-2.3/
端口规划8147(Gitea HTTPS)、8149(Git 代理 HTTP)、8150(管理台 HTTP)、8151(Git 代理 HTTPS)、8152(管理台 HTTPS)
内部网络user-defined bridge,网段 10.89.67.0/24
域名规划gitproxy.local(网关)、gitea.local(上游),通过 network alias + 工作机 hosts 解析

💡 说明:gitproxy.local、gitea.local 两个域名由容器内 network alias 提供容器间解析;工作机与浏览器侧需要在服务器的 /etc/hosts 里补两条记录(见 4.4 节)。端口 8149–8152 是本方案的显式规划(避开常用 80/443 冲突),GitProxy 的四个监听端口在配置文件中一一对应。

二、镜像选型说明

本方案涉及三个镜像,均来自官方:

镜像版本压缩大小解压大小用途
finos/git-proxy:v2.3.0v2.3.0444MB1.98GBGitProxy 推送审批网关本体(Node.js)
gitea/gitea:1.23.81.23.875.2MB255MB上游 Git 服务器(模拟企业自托管 Git)
ubuntu:22.0422.0429MB77.8MB演示用 Git 客户端工作台

选型依据与关键差异:

  1. GitProxy 镜像:FINOS 官方 Docker Hub 仓库,v2.3.0 标签对应本系列实测版本;镜像内含 Node.js 运行时与 gitleaks 扫描器调用逻辑(注意:gitleaks 二进制本身不随镜像分发,见 11.5 节)。
  2. Gitea 镜像:GitProxy 只做推送审批,不存储代码,真实落库方是上游 Git 服务器。Gitea 轻量(75MB)、自带 SQLite 且支持 HTTPS 直挂证书,最适合演示「网关 + 自托管上游」的组合;换成 GitLab、Bitbucket 亦可,只需把 proxy.config.json 里 authorisedList 的 origin 地址替换为对应仓库 URL。
  3. ubuntu:22.04 工作台:仅用于承载 git 客户端做推送演示,可选。已有办公机的读者可直接用办公机操作。

💡 技术拓展:GitProxy 的上游并不限定 Git 服务器类型,它工作在 Git Smart HTTP 协议层。常见上游对比——

上游原理/特点适用场景
Gitea/GitLab(自托管)企业内网 Git 服务,GitProxy 直连其 Smart HTTP 端点内部代码合规管控
GitHub.com托管服务,GitProxy 默认 catch-all 路由开源合规审查
Bitbucket/自建裸仓库同为 Smart HTTP 可达仓库任意可 HTTP 推送的仓库

本方案实际采用「Gitea 自托管上游」,理由:闭环可控、无外网依赖,且能演示 HTTPS 证书全链路。

三、前置条件

  1. 服务器已安装 Docker Engine 与 Compose v2 插件,可用 docker compose version 验证;
  2. 服务器已安装 openssl(Ubuntu 22.04 自带)与 curl;
  3. 工作机已安装 git(git --version ≥ 2.30 即可);
  4. 服务器 8147、8149、8150、8151、8152 五个端口空闲(ss -ltn | grep -E '814[7-9]|815[0-2]' 应无输出);
  5. 可访问 Docker Hub 拉取镜像(内网环境可预先导入镜像 tar 包)。

四、部署步骤

4.1 目录结构

本节建立部署根目录与子目录,后续证书、配置、数据全部落在该目录内,便于整体备份与迁移。

# 创建部署根目录与各子目录
mkdir -p /opt/gitproxy-2.3/{config,certs,data/gitea,data/gitproxy,client}
cd /opt/gitproxy-2.3

最终目录结构与用途:

/opt/gitproxy-2.3/
├── config/proxy.config.json   # GitProxy 主配置(挂载进服务,只读)
├── certs/                     # 自签 CA 与两个服务端证书
├── data/gitea/                # Gitea 持久化数据(仓库、账号、SQLite)
├── data/gitproxy/             # GitProxy 持久化数据(NeDB:pushes/repos/users)
└── client/                    # 演示工作机的工作目录(git 仓库)

4.2 生成 TLS 证书

GitProxy 的 HTTPS 端口(8151/8152)与 Gitea 的 HTTPS 端口(8147)都需要证书。本节用 openssl 自签一个演示 CA,并给 gitproxy.local、gitea.local 各签一张服务端证书,保证「工作机 → 网关 → 上游」整条链路 HTTPS 闭环。所有命令已在服务器实测:

# 1) 生成自签 CA(有效期 10 年;genrsa/req 静默成功无输出)
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -subj '/CN=GitProxy Demo CA' -out ca.crt

# 2) 签发 gitproxy.local 服务端证书(SAN 必须含域名,否则 git 客户端报证书不匹配)
printf 'subjectAltName = DNS:gitproxy.local\nextendedKeyUsage = serverAuth\n' > san-gitproxy.cnf
openssl genrsa -out gitproxy.key 2048
openssl req -new -key gitproxy.key -subj '/CN=gitproxy.local' -out gitproxy.csr
openssl x509 -req -in gitproxy.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out gitproxy.crt -days 825 -sha256 -extfile san-gitproxy.cnf

第 2) 步 x509 签发回显(实测):

Certificate request self-signature ok
subject=CN = gitproxy.local
# 3) 签发 gitea.local 服务端证书
printf 'subjectAltName = DNS:gitea.local\nextendedKeyUsage = serverAuth\n' > san-gitea.cnf
openssl genrsa -out gitea.key 2048
openssl req -new -key gitea.key -subj '/CN=gitea.local' -out gitea.csr
openssl x509 -req -in gitea.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out gitea.crt -days 825 -sha256 -extfile san-gitea.cnf

第 3) 步 x509 签发回显(实测):

Certificate request self-signature ok
subject=CN = gitea.local
# 4) 验证证书链
openssl verify -CAfile ca.crt gitproxy.crt gitea.crt

第 4) 步验证回显(两证书均受 CA 信任):

gitproxy.crt: OK
gitea.crt: OK

⚠️ 注意:ca.key、gitproxy.key、gitea.key 是私钥,生产环境务必收紧权限(chmod 600)并纳入密钥管理;本节私钥仅用于演示环境。

4.3 编写 proxy.config.json

在 /opt/gitproxy-2.3/config/proxy.config.json 写入以下内容(实测部署初始配置,逐项含义见 7 节):

{
  "cookieSecret": "gitproxy-2.3-demo-cookie-secret-2026",
  "sessionMaxAgeHours": 12,
  "serverPort": 8149,
  "httpsServerPort": 8151,
  "uiHost": "http://gitproxy.local:8150",
  "uiPort": 8150,
  "httpsUiPort": 8152,
  "rateLimit": {
    "windowMs": 60000,
    "limit": 1000
  },
  "tempPassword": {
    "sendEmail": false,
    "emailConfig": {}
  },
  "authorisedList": [
    {
      "project": "admin",
      "name": "hello-gitproxy",
      "url": "https://gitea.local:3000/admin/hello-gitproxy.git",
      "proxyURL": "http://gitproxy.local:8149"
    }
  ],
  "scmProviders": [
    { "name": "github", "type": "github", "host": "github.com" },
    { "name": "gitlab", "type": "gitlab", "host": "gitlab.com" },
    { "name": "codeberg", "type": "forgejo", "host": "codeberg.org" },
    { "name": "gitea", "type": "forgejo", "host": "gitea.com" },
    {
      "name": "local-gitea",
      "type": "forgejo",
      "host": "gitea.local",
      "apiUrl": "https://gitea.local:3000/api/v1"
    }
  ],
  "sink": [
    { "type": "fs", "enabled": true },
    {
      "type": "mongo",
      "connectionString": "mongodb://localhost:27017/gitproxy",
      "options": { "tlsAllowInvalidCertificates": false },
      "enabled": false
    },
    {
      "type": "postgres",
      "connectionString": "postgresql://localhost:5432/gitproxy",
      "enabled": false
    }
  ],
  "authentication": [
    { "type": "local", "enabled": true },
    {
      "type": "ActiveDirectory",
      "enabled": false,
      "adminGroup": "",
      "userGroup": "",
      "domain": "",
      "adConfig": { "url": "", "baseDN": "", "searchBase": "", "username": "", "password": "" }
    },
    {
      "type": "openidconnect",
      "enabled": false,
      "oidcConfig": { "issuer": "", "clientID": "", "clientSecret": "", "callbackURL": "", "scope": "" }
    }
  ],
  "api": {},
  "commitConfig": {
    "author": {
      "email": {
        "local": { "block": "" },
        "domain": { "allow": ".*" }
      }
    },
    "message": { "block": { "literals": [], "patterns": [] } },
    "diff": { "block": { "literals": [], "patterns": [], "providers": {} } }
  },
  "attestationConfig": {
    "questions": [
      {
        "label": "I have checked every commit in this push",
        "tooltip": {
          "text": "Please confirm you have checked every commit in this push",
          "links": []
        }
      },
      {
        "label": "I am authorised to make this push",
        "tooltip": {
          "text": "Please confirm you are authorised to make this push",
          "links": []
        }
      }
    ]
  },
  "domains": {
    "proxy": "http://gitproxy.local:8149",
    "service": "http://gitproxy.local:8150"
  },
  "privateOrganizations": [],
  "urlShortener": "",
  "contactEmail": "admin@example.com",
  "csrfProtection": true,
  "sidebandProgress": true,
  "plugins": [],
  "apiAuthentication": [
    {
      "type": "jwt",
      "enabled": false,
      "jwtConfig": {
        "authorityURL": "",
        "clientID": "",
        "expectedAudience": "",
        "roleMapping": { "admin": { "": "" } }
      }
    }
  ],
  "upstreamProxy": { "enabled": false, "url": "http://localhost:8081", "noProxy": [] },
  "tls": {
    "enabled": true,
    "key": "/app/certs/gitproxy.key",
    "cert": "/app/certs/gitproxy.crt"
  },
  "uiRouteAuth": {
    "enabled": false,
    "rules": [
      { "pattern": "/dashboard/*", "adminOnly": false, "loginRequired": true },
      { "pattern": "/admin/*", "adminOnly": true, "loginRequired": true }
    ]
  },
  "ssh": {
    "enabled": false,
    "port": 2222,
    "knownHosts": {
      "github.com": "SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU",
      "gitlab.com": "SHA256:eUXGGm1YGsMAS7vkcx6JOJdOGHPem5gQp4taiCfCLB8"
    }
  }
}

配置说明(关键项逐条,完整键位详解见《GitProxy 2.3 配置参数详解》):

键值与含义
serverPort / httpsServerPortGit 代理的 HTTP/HTTPS 监听端口(8149/8151),工作机的 git push 指向这两个端口
uiPort / httpsUiPort / uiHost管理台(审批 UI + REST API)的 HTTP/HTTPS 监听端口;uiHost 为管理台对外地址(完整 URL 形式,审批分享链接由它生成)
cookieSecret管理台会话 Cookie 签名密钥,本文用演示值 gitproxy-2.3-demo-cookie-secret-2026,生产必须换成独立随机串(openssl rand -hex 64 生成)
sessionMaxAgeHours会话有效期(小时),超时需重新登录
rateLimit管理台限流:60 秒窗口 1000 次,超出返回 429
contactEmail推送被拦时的提示邮箱,会出现在拒绝回显里
csrfProtection管理台写操作强制 CSRF Token 校验(true 时所有 POST 必须先取 Token,见 11.4 节)
authentication登录方式数组,local(内置账号密码)启用;ActiveDirectory、openidconnect 项预留且关闭
commitConfig推送检查规则(消息/作者邮箱/diff 三类),参数含义与修改验证见配置参数详解篇
authorisedList允许经网关推送的仓库白名单;运行期可通过 REST API 增删
sink推送记录持久化后端数组(fs/mongo/postgres 三项按 enabled 切换),fs 即本地 NeDB 文件(五节验证落盘文件)
plugins自定义检查插件目录,空数组表示未配置(启动日志 Found 0 plugin modules)
domains对外地址覆盖:proxy 为网关对外地址、service 为管理台对外地址(审批分享链接与被拦回显的 gitproxy.local 链接由此生成)
scmProvidersSCM 供应商数组(name/type/host,自建 Gitea 加 apiUrl);local-gitea 项供 Web 界面做 SCM 身份(Token)绑定
tlsHTTPS 端口使用的证书与私钥路径(容器内路径,来自 4.4 节挂载)

4.4 编写 docker-compose.yml

在 /opt/gitproxy-2.3/docker-compose.yml 写入以下内容(实测原文件):

name: gitproxy-2.3-deploy

services:
  gitea:
    image: gitea/gitea:1.23.8
    container_name: gitproxy-2.3-gitea
    hostname: gitea.local
    restart: unless-stopped
    environment:
      USER_UID: "1000"
      USER_GID: "1000"
      GITEA__server__PROTOCOL: https
      GITEA__server__CERT_FILE: /data/certs/gitea.crt
      GITEA__server__KEY_FILE: /data/certs/gitea.key
      GITEA__server__DOMAIN: gitea.local
      GITEA__server__ROOT_URL: https://gitea.local:3000/
      GITEA__database__DB_TYPE: sqlite3
      GITEA__security__INSTALL_LOCK: "true"
      GITEA__service__DISABLE_REGISTRATION: "true"
    ports:
      - "8147:3000"
    volumes:
      - ./data/gitea:/data
      - ./certs:/data/certs:ro
    networks:
      default:
        aliases:
          - gitea.local

  gitproxy:
    image: finos/git-proxy:v2.3.0
    container_name: gitproxy-2.3-server
    hostname: gitproxy.local
    restart: unless-stopped
    command: ["node", "--enable-source-maps", "dist/index.js", "--config", "/app/proxy.config.json"]
    depends_on:
      - gitea
    environment:
      ALLOWED_ORIGINS: "*"
      NODE_EXTRA_CA_CERTS: /app/certs/ca.crt
      GIT_SSL_CAINFO: /app/certs/ca.crt
      CONFIG_FILE: /app/proxy.config.json
    ports:
      - "8149:8149"
      - "8150:8150"
      - "8151:8151"
      - "8152:8152"
    volumes:
      - ./config/proxy.config.json:/app/proxy.config.json:ro
      - ./certs:/app/certs:ro
      - ./data/gitproxy:/app/.data
    networks:
      default:
        aliases:
          - gitproxy.local

  client:
    image: ubuntu:22.04
    container_name: gitproxy-2.3-client
    hostname: client.local
    restart: unless-stopped
    command: ["sleep", "infinity"]
    volumes:
      - ./certs/ca.crt:/usr/local/share/ca-certificates/gitproxy-ca.crt:ro
      - ./client:/root/work

networks:
  default:
    name: gitproxy-stack-net
    ipam:
      config:
        - subnet: 10.89.67.0/24

配置说明(关键项):

  1. gitea:Gitea 自身启用 HTTPS(GITEA__server__PROTOCOL: https),证书来自 ./certs 只读挂载;INSTALL_LOCK=true 跳过网页安装向导,账号用命令行创建(4.6 节);DISABLE_REGISTRATION=true 关闭开放注册。
  2. gitproxy:显式 command 覆盖默认入口,让网关加载挂载进来的 proxy.config.json(否则镜像默认读容器内 /app/dist/proxy.config.json,日志显示 Setting config file to: /app/dist/proxy.config.json,修改外部文件不会生效);NODE_EXTRA_CA_CERTS 与 GIT_SSL_CAINFO 把自签 CA 注入 Node 与 git 信任库,网关才能以 HTTPS 校验方式访问 Gitea。
  3. client:演示工作台,把 CA 证书放到系统 CA 目录(update-ca-certificates 后生效),工作目录挂载到 /root/work。
  4. 三个服务共用 gitproxy-stack-net bridge 网络,gitea.local、gitproxy.local 由 network alias 提供容器间解析。

在服务器 /etc/hosts 追加两条记录(供浏览器与宿主机侧访问域名用):

# 服务器本机解析两个演示域名
echo "10.89.67.2 gitproxy.local gitea.local" >> /etc/hosts

⚠️ 注意:10.89.67.2 取自 compose 子网内的网关容器地址;若按 11.1 节排查到其他地址,请以实际为准替换。

4.5 启动并验证服务

# 启动整套栈(首次启动自动拉取镜像)
docker compose up -d

查看服务状态(实测回显):

docker compose ps
NAME                  IMAGE                    COMMAND                  SERVICE    CREATED             STATUS             PORTS
gitproxy-2.3-client   ubuntu:22.04             "sleep infinity"         client     About an hour ago   Up About an hour   
gitproxy-2.3-gitea    gitea/gitea:1.23.8       "/usr/bin/entrypoint…"   gitea      About an hour ago   Up About an hour   22/tcp, 0.0.0.0:8147->3000/tcp, [::]:8147->3000/tcp
gitproxy-2.3-server   finos/git-proxy:v2.3.0   "tini -- /docker-ent…"   gitproxy   About an hour ago   Up 3 minutes       8000/tcp, 8080/tcp, 8444/tcp, 0.0.0.0:8149-8152->8149-8152/tcp, [::]:8149-8152->8149-8152/tcp

查看 GitProxy 启动日志(实测回显,docker compose logs gitproxy | head -30):

Created runtime configuration with:
  API URL: auto-detect
  Allowed Origins: *
  Environment: production
Configuration sources are disabled
Configuration loaded successfully
Setting config file to: /app/proxy.config.json
Validating config
Setting up the proxy and Service
Cleaning up the existing .remote dir...
Creating the .remote dir...
Loading neDB database adaptor
No plugins configured
Found 0 plugin modules
Service Listening on 8150
HTTPS Service Listening on 8152
Initializing proxy router for origins: '["gitea.local:3000"]'
	setting up origin: 'gitea.local:3000'
	setting up catch-all route (github.com) for backwards compatibility
proxy keys registered:  ["/gitea.local:3000/"]
HTTP Proxy Listening on 8149
HTTPS Proxy Listening on 8151

日志确认四个端口全部监听、配置文件路径为挂载的 /app/proxy.config.json、白名单 origin 已注册。

服务健康验证(实测回显):

# 1) GitProxy 管理台健康检查(HTTP 8150)
curl -s http://localhost:8150/api/v1/healthcheck
{"message":"ok"}
# 2) 管理台 HTTPS(8152)与 Gitea HTTPS(8147)状态码
curl -sk -o /dev/null -w '%{http_code}\n' https://localhost:8152/
curl -sk -o /dev/null -w '%{http_code}\n' https://localhost:8147/
200
200

三个端口均返回预期结果,服务本体启动完成。

4.6 初始化 Gitea 管理员

Gitea 关闭了网页注册(DISABLE_REGISTRATION),管理员账号用 Gitea 命令行创建。创建 admin 账号(密码用演示口令 q1w2e3r4,邮箱用 admin@example.com):

# 在 Gitea 服务内创建管理员(实测命令)
docker exec gitproxy-2.3-gitea gitea admin user create \
  --username admin --password q1w2e3r4 \
  --email admin@example.com --admin

实测回显:

New user 'admin' has been successfully created!

登录 https://gitea.local:8147(用刚创建的 admin)创建演示仓库 hello-gitproxy(网页操作:New Repository,属主 admin,Private 可选)。该仓库地址即 4.3 节白名单里的 https://gitea.local:3000/admin/hello-gitproxy.git。

同时为网关生成一个 Gitea Personal Access Token(网页操作:Settings → Applications → Generate New Token,勾选 repository 读写与 user 读取),Token 在 4.8 节推送时作为 Git 密码使用。

4.7 创建 GitProxy 账号与仓库授权

GitProxy 首次启动会在持久化目录里创建两个默认账号 admin / user(初始密码均为 admin)。安全起见,先用管理台 API 修改 admin 密码为演示口令 q1w2e3r4,再创建一个只读审批人 reviewer(用于演示四眼原则:作者与审批人不能是同一人)。

# 1) 取 CSRF Token 并用默认账号登录(csrfProtection: true 时所有写请求都要带 Token;
#    Token 每次写操作前重新获取,会话 Cookie 存入 admin.jar 贯穿后续命令)
CT=$(curl -s -c admin.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -c admin.jar -X POST http://localhost:8150/api/auth/login \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"admin","password":"admin"}'
{"message":"success","user":{"username":"admin","displayName":"","email":"admin@place.com","title":"","scmIdentities":{},"admin":true}}

💡 不带 X-CSRF-Token 直接 POST 会被 403 拒绝(Error: CSRF token missing),该机制由 csrfProtection: true 开启,REST API 的完整调用约定见《GitProxy 2.3 常用 CLI 命令与 REST API 详解》。

# 2) 修改 admin 密码为演示口令 q1w2e3r4(POST /api/auth/change-password)
CT=$(curl -s -b admin.jar -c admin.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X POST http://localhost:8150/api/auth/change-password \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"currentPassword":"admin","newPassword":"q1w2e3r4"}'
{"message":"Password updated successfully"}

创建只读审批人 reviewer(演示口令同为 q1w2e3r4),再把 admin 设为 hello-gitproxy 的推送人(canPush)、reviewer 设为审批人(canAuthorise):

# 3) 创建审批人 reviewer
CT=$(curl -s -b admin.jar -c admin.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X POST http://localhost:8150/api/auth/create-user \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"reviewer","password":"q1w2e3r4","email":"reviewer@example.com"}'
{"message":"User created successfully","username":"reviewer"}
# 4) 查询 hello-gitproxy 的仓库记录(拿到 _id,Cookie 沿用 admin.jar)
curl -s -b admin.jar http://localhost:8150/api/v1/repo/

仓库记录返回(实测):

{"project":"admin","name":"hello-gitproxy","url":"https://gitea.local:3000/admin/hello-gitproxy.git","users":{"canPush":["admin"],"canAuthorise":["reviewer"]},"dateCreated":"2026-10-26T01:31:36.372Z","lastModified":"2026-10-26T01:36:39.921Z","_id":"JgcqA5axqbi0LcWa","proxyURL":"http://gitproxy.local:8149"}
# 5) 配置授权矩阵(_id 用上一步返回值;两步各带一个新取的 CSRF Token)
CT=$(curl -s -b admin.jar -c admin.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X PATCH http://localhost:8150/api/v1/repo/JgcqA5axqbi0LcWa/user/push \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" -d '{"username":"admin"}'
CT=$(curl -s -b admin.jar -c admin.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b admin.jar -X PATCH http://localhost:8150/api/v1/repo/JgcqA5axqbi0LcWa/user/authorise \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" -d '{"username":"reviewer"}'
{"message":"created"}
{"message":"created"}

canPush(允许推送)、canAuthorise(允许审批)两个角色分开配置,正是四眼原则的实现基础:同一仓库里,推送人不能审批自己发起的推送。

4.8 工作机推送与审批最小闭环

在演示工作台内配置 git 并做一次完整推送,验证「网关截获 → 审批 → 重推生效」:

# 1) 工作台内信任自签 CA 并配置 git
update-ca-certificates
git config --global user.name  admin
git config --global user.email admin@example.com

# 2) 导出 Gitea Personal Access Token(网页生成后交互式写入变量,避免明文留在命令历史)
read -s -p "Gitea Token: " GITEA_TOKEN && echo

# 3) 克隆上游仓库(走 Gitea HTTPS 8147)
cd /root/work
git clone "https://admin:q1w2e3r4@gitea.local:3000/admin/hello-gitproxy.git"
cd hello-gitproxy

# 4) 提交并推送(推送地址指向 GitProxy 网关 8149,凭据为 GitProxy 账号 + Gitea Token)
echo "deployment verification" > verify.txt
git add verify.txt && git commit -m "docs: deployment verification"
git push "http://admin:${GITEA_TOKEN}@gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git" main

第 4) 步实测回显(推送被网关截获并进入审批队列,返回「approval required」与分享链接;输出节选,实测首尾,中部多个空行略):

remote: Resolving user from token...        
remote: Checking for empty branch...        
remote: Checking repository is authorised...        
remote: Checking commit messages...        
remote: Checking author emails...        
remote: Checking push permissions...        
remote: Fetching remote repository...        
remote: Writing pack data...        
remote: Checking for hidden commits...        
remote: Checking approval status...        
remote: Running pre-receive hook...        
remote: Computing diff...        
remote: Scanning for secrets...        
remote: Scanning diff contents...        
remote: Requesting approval...        
remote: 	        
remote: [32mGitProxy has received your push ✅[0m        
remote: 🔗 Shareable Link        
remote: [34mhttp://gitproxy.local:8150/dashboard/push/1918f67cad308399c981a46feeabdc08b17b8121e64e93b213f1d72f190f10b2[0m        
To http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git
 ! [remote rejected] main -> main (approval required)
error: failed to push some refs to 'http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git'

推送记录已进入管理台审批队列(http://gitproxy.local:8150/dashboard/push/1918f67c…)。用审批人 reviewer 通过 REST API 批准(带两项 attestation 确认):

# 4) reviewer 登录(会话存 reviewer.jar,取 Token 流程同 4.7 节第 1) 步)后调用审批 API
CT=$(curl -s -c reviewer.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b reviewer.jar -c reviewer.jar -X POST http://localhost:8150/api/auth/login \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"username":"reviewer","password":"q1w2e3r4"}'
CT=$(curl -s -b reviewer.jar -c reviewer.jar http://localhost:8150/api/auth/csrf-token | python3 -c 'import json,sys;print(json.load(sys.stdin)["csrfToken"])')
curl -s -b reviewer.jar -X POST http://localhost:8150/api/v1/push/1918f67cad308399c981a46feeabdc08b17b8121e64e93b213f1d72f190f10b2/authorise \
  -H 'Content-Type: application/json' -H "X-CSRF-Token: $CT" \
  -d '{"params":{"attestation":[{"label":"I have checked every commit in this push","checked":true},{"label":"I am authorised to make this push","checked":true}]}}'
{"message":"authorised 1918f67cad308399c981a46feeabdc08b17b8121e64e93b213f1d72f190f10b2"}

⚠️ 注意:若此处改用推送人 admin 自己批准,会返回 {"message":"User admin not authorised to approve pushes on this project"}——四眼原则拦截,属预期行为。

批准后原推送即可重复执行,本次真正落到上游仓库(实测回显):

# 5) 批准后重推同一分支
git push "http://admin:${GITEA_TOKEN}@gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git" main
remote: Checking approval status...        
remote: . Processing 1 references        
remote: Processed 1 references in total        
To http://gitproxy.local:8149/gitea.local:3000/admin/hello-gitproxy.git
   bea3f4f..757f8c7  main -> main

bea3f4f..757f8c7 main -> main 表示提交已通过网关落到 Gitea 上游。至此部署、账号、授权、推送审批全链路验证完成。

五、数据持久化说明

两个数据目录都挂载在部署根目录下,销毁服务不影响数据:

目录(服务器侧)挂载点(服务内)内容
/opt/gitproxy-2.3/data/gitproxy//app/.dataGitProxy NeDB 数据库与推送临时区
/opt/gitproxy-2.3/data/gitea//dataGitea 全量数据(SQLite、仓库、附件)

验证 GitProxy 落盘文件(实测回显):

ls -la /opt/gitproxy-2.3/data/gitproxy/db/
total 124
drwxr-xr-x 2 root root   4096 Oct 26 10:52 .
drwxr-xr-x 4 root root   4096 Oct 26 09:29 ..
-rw-r--r-- 1 root root  20480 Oct 26 10:52 migrations.db
-rw-r--r-- 1 root root  36864 Oct 26 10:52 pushes.db
-rw-r--r-- 1 root root  12288 Oct 26 10:52 repos.db
-rw-r--r-- 1 root root  36864 Oct 26 10:52 users.db

pushes.db(推送审批记录)、repos.db(仓库白名单与授权矩阵)、users.db(账号)、migrations.db(迁移记录)四个 NeDB 文件即 GitProxy 的全部状态。备份与迁移时把 data/ 整目录拷走即可;config/、certs/ 建议一并备份。

💡 技术拓展:GitProxy 的 sink 支持 fs/MongoDB/PostgreSQL 三种持久化后端。默认 fs(NeDB 文件)适合单机与小团队;多实例部署或大流量审计需求应切换 MongoDB/PostgreSQL(改 sink 配置即可,表结构由服务自动初始化)。本方案保持 fs,理由:单节点部署、无外部依赖。

六、环境变量表

compose 中两个服务的环境变量(全部实测生效):

gitea 服务:

变量取值含义
USER_UID / USER_GID1000 / 1000Gitea 运行身份,需与 data/gitea 属主一致
GITEA__server__PROTOCOLhttpsGitea 主站启用 HTTPS
GITEA__server__CERT_FILE/data/certs/gitea.crtGitea 服务端证书(只读挂载)
GITEA__server__KEY_FILE/data/certs/gitea.keyGitea 私钥(只读挂载)
GITEA__server__DOMAINgitea.local对外域名(Cookie、克隆地址生成用)
GITEA__server__ROOT_URLhttps://gitea.local:3000/对外根地址
GITEA__database__DB_TYPEsqlite3内置 SQLite,免独立数据库
GITEA__security__INSTALL_LOCKtrue跳过网页安装向导
GITEA__service__DISABLE_REGISTRATIONtrue关闭开放注册,账号仅命令行创建

gitproxy 服务:

变量取值含义
CONFIG_FILE/app/proxy.config.json声明式传给入口的配置文件路径(真正生效靠 command 里的 --config,见 11.6 节)
NODE_EXTRA_CA_CERTS/app/certs/ca.crtNode.js 额外信任的自签 CA(网关访问 Gitea HTTPS 用)
GIT_SSL_CAINFO/app/certs/ca.crtgit 子进程的 CA 信任文件
ALLOWED_ORIGINS*Web 运行时允许的跨域来源(管理台 API 调用方)

七、配置参数说明

proxy.config.json 的完整键位在 4.3 节已给出。从部署视角再补充三类高频参数的语义与默认行为:

  1. 端口四件套:serverPort(8149,Git 代理 HTTP)、httpsServerPort(8151,Git 代理 HTTPS)、uiPort(8150,管理台 HTTP)、httpsUiPort(8152,管理台 HTTPS)。工作机推送用 8149/8151,人与 API 用 8150/8152,四端口可独立调整。
  2. domains:对外地址覆盖,proxy 为网关对外地址(http://gitproxy.local:8149)、service 为管理台对外地址(http://gitproxy.local:8150),推送被拦回显与审批分享链接里的 gitproxy.local 地址由它们生成。注意它不是路由放行表:启动日志 Initializing proxy router for origins: '["gitea.local:3000"]' 与 proxy keys registered: ["/gitea.local:3000/"] 的路由来源是 authorisedList 里各仓库 URL 的主机部分,推送 URL 未命中注册路由时触发 catch-all(github.com)回退并被 checkRepoInAuthorisedList 拦截(实测见 11.2 节)。
  3. commitConfig 三类推送策略:message(提交/标签消息黑名单:字面量、正则)、author(作者邮箱域名 allow / 本地部分 block)、diff(diff 内容字面量/正则黑名单)。三类规则是「修改 → 重启 → 推送验证」的最典型闭环,逐项实测见《GitProxy 2.3 配置参数详解》。

⚠️ 注意:cookieSecret 用的是本文演示值,生产部署必须替换为 openssl rand -hex 64 生成的独立随机串。

八、配置文件挂载与热更新边界

proxy.config.json 以只读方式(:ro)挂载进服务。GitProxy v2.3.0 对主配置文件没有热更新端点:修改文件后必须重启服务进程才能生效。实测记录:

# 修改配置后尝试热加载端点(v2.3.0 无此路由)
curl -s -X POST http://localhost:8150/api/v1/admin/reload-config
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot POST /api/v1/admin/reload-config</pre>
</body>
</html>

因此 v2.3.0 的正确更新姿势是「改文件 → 重启 → 验证」:

# 修改配置后重启 gitproxy 服务并验证健康
docker compose restart gitproxy
curl -s http://localhost:8150/api/v1/healthcheck
{"message":"ok"}

⚠️ 注意:authorisedList 的运行期增删是个例外——通过 POST /api/v1/repo/ 添加的仓库写入 repos.db 数据库而非配置文件,即时生效无需重启(实测见 D 篇 API 域详解)。配置文件里的 authorisedList 只影响首次初始化。

九、等价 docker run 命令

不使用 compose 时,以下两条 docker run 可复现同等的双服务部署(先建网络,再依次启动上游与网关;演示工作机可直接在服务器本机执行 git 命令替代 client 服务):

# 1) 创建 bridge 网络并注册域名解析
docker network create -d bridge --subnet 10.89.67.0/24 gitproxy-stack-net

# 2) 启动 Gitea 上游(HTTPS + 证书挂载 + 数据持久化)
docker run -d --name gitproxy-2.3-gitea \
  --hostname gitea.local \
  --network gitproxy-stack-net \
  --network-alias gitea.local \
  -p 8147:3000 \
  -e USER_UID=1000 -e USER_GID=1000 \
  -e GITEA__server__PROTOCOL=https \
  -e GITEA__server__CERT_FILE=/data/certs/gitea.crt \
  -e GITEA__server__KEY_FILE=/data/certs/gitea.key \
  -e GITEA__server__DOMAIN=gitea.local \
  -e GITEA__server__ROOT_URL=https://gitea.local:3000/ \
  -e GITEA__database__DB_TYPE=sqlite3 \
  -e GITEA__security__INSTALL_LOCK=true \
  -e GITEA__service__DISABLE_REGISTRATION=true \
  -v /opt/gitproxy-2.3/data/gitea:/data \
  -v /opt/gitproxy-2.3/certs:/data/certs:ro \
  --restart unless-stopped \
  gitea/gitea:1.23.8

# 3) 启动 GitProxy 网关(显式 --config 加载挂载配置 + CA 信任注入)
docker run -d --name gitproxy-2.3-server \
  --hostname gitproxy.local \
  --network gitproxy-stack-net \
  --network-alias gitproxy.local \
  -p 8149:8149 -p 8150:8150 -p 8151:8151 -p 8152:8152 \
  -e ALLOWED_ORIGINS='*' \
  -e NODE_EXTRA_CA_CERTS=/app/certs/ca.crt \
  -e GIT_SSL_CAINFO=/app/certs/ca.crt \
  -v /opt/gitproxy-2.3/config/proxy.config.json:/app/proxy.config.json:ro \
  -v /opt/gitproxy-2.3/certs:/app/certs:ro \
  -v /opt/gitproxy-2.3/data/gitproxy:/app/.data \
  --restart unless-stopped \
  finos/git-proxy:v2.3.0 node --enable-source-maps dist/index.js --config /app/proxy.config.json

验证方式与 4.5 节相同(healthcheck + 日志确认四个端口监听)。

十、常用运维命令

# 查看三个服务状态
docker compose ps

# 跟踪网关实时日志(推送处理链全在这里)
docker compose logs -f gitproxy

# 重启单个服务(改配置后用)
docker compose restart gitproxy

# 停止/启动整套栈(数据保留)
docker compose stop
docker compose start

# 彻底销毁栈(数据目录仍在磁盘上)
docker compose down

# 连数据一起清空(⚠️ 会删除推送审批记录与账号,慎用)
docker compose down -v

推送审批记录查询建议走 REST API(GET /api/v1/push/)而非翻 NeDB 文件,账号与仓库管理同理;API 逐域实测见《GitProxy 2.3 常用 CLI 命令与 REST API 详解》。

十一、常见问题(FAQ)

Q1:docker compose up -d 后网关启动失败/端口占用? 查看日志定位:docker compose logs gitproxy | head -30。若报 EADDRINUSE,说明宿主 8149–8152 有端口被占(ss -ltnp | grep 8149 找到进程),改 proxy.config.json 四端口与 compose 映射即可。

Q2:git push 报 fatal: .../info/refs not valid: is this a git repository? 这是网关在仓库白名单检查阶段拒绝后,git 客户端的统一表现(实测回显):

fatal: http://gitproxy.local:8149/gitea.local:3000/admin/second-repo.git/info/refs not valid: is this a git repository?

服务端日志(docker compose logs gitproxy)同时出现(实测节选):

Parse action calculated repo URL: https://gitea.local:3000/admin/second-repo.git for inbound URL path: /gitea.local:3000/admin/second-repo.git/info/refs?service=git-receive-pack
Parse action fallback calculated repo URL: https://github.com/gitea.local:3000/admin/second-repo.git for inbound URL path: /gitea.local:3000/admin/second-repo.git/info/refs?service=git-receive-pack
checkRepoInAuthorisedList - repo https://github.com/gitea.local:3000/admin/second-repo.git is not in the authorised whitelist, ending
checkRepoInAuthorisedList - Rejecting repo https://github.com/gitea.local:3000/admin/second-repo.git not in the authorised whitelist
Action processed: Error

解决:把该仓库加入授权(POST /api/v1/repo/,即时生效),或将推送 URL 修正为白名单仓库的真实 URL。

Q3:推送被拦 Your push has been blocked (admin is not allowed to push on repo ...) 账号通过了认证但不在该仓库 canPush 列表。用仓库 _id 调 PATCH /api/v1/repo/<id>/user/push 加授权即可(实测):

{"message":"created"}

Q4:审批 API 返回 Attestation is not complete authorise 请求体必须带与 attestationConfig.questions label 逐字一致的确认项(见 4.8 节第 4 步请求体),漏带或 label 不匹配都会 400。

Q5:日志出现 gitleaks - Failed to spawn gitleaks: spawn gitleaks ENOENT api.gitleaks.enabled 设为 true 后,网关会调用外部 gitleaks 二进制做密钥扫描,但官方镜像未内置该二进制。v2.3.0 部署保持 api.gitleaks 缺省(关闭)即可走内置的 diff 字面量扫描(scanDiff 步骤正常执行);确需 gitleaks 时需自行在运行环境安装并重试。

Q6:CLI 登录报 Error: Login 'admin': '403' csrfProtection: true 时管理台所有 POST(含登录)都要求 CSRF Token,而 v2.3.0 的 GitProxy CLI 未实现 CSRF 获取流程,实测被 403 拒绝。使用 CLI 需将 csrfProtection 临时设为 false 并重启(改完记得恢复),或直接用 REST API(curl 流程自带 Token 获取)。

十二、参考链接

  1. GitProxy 官方仓库:https://github.com/finos/git-proxy
  2. GitProxy v2.3.0 Release:https://github.com/finos/git-proxy/releases/tag/v2.3.0
  3. GitProxy 官方文档站:https://finos.github.io/git-proxy/
  4. Gitea 官方文档(环境变量配置):https://docs.gitea.com/category/installation
  5. 本系列姊妹篇:《GitProxy 2.3 介绍与选型指南》、《GitProxy 2.3 配置参数详解》、《GitProxy 2.3 常用 CLI 命令与 REST API 详解》