GitProxy 2.3 Docker Compose 部署指南
在 Ubuntu 22.04 服务器上以 Docker Compose 一键部署 GitProxy 2.3 推送审批网关,配套 Gitea 上游 Git 服务与自签 TLS 证书,完成账号创建、仓库授权与推送-审批-重推全链路验证。
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 / Compose | Docker 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.0 | v2.3.0 | 444MB | 1.98GB | GitProxy 推送审批网关本体(Node.js) |
gitea/gitea:1.23.8 | 1.23.8 | 75.2MB | 255MB | 上游 Git 服务器(模拟企业自托管 Git) |
ubuntu:22.04 | 22.04 | 29MB | 77.8MB | 演示用 Git 客户端工作台 |
选型依据与关键差异:
- GitProxy 镜像:FINOS 官方 Docker Hub 仓库,
v2.3.0标签对应本系列实测版本;镜像内含 Node.js 运行时与 gitleaks 扫描器调用逻辑(注意:gitleaks 二进制本身不随镜像分发,见 11.5 节)。 - Gitea 镜像:GitProxy 只做推送审批,不存储代码,真实落库方是上游 Git 服务器。Gitea 轻量(75MB)、自带 SQLite 且支持 HTTPS 直挂证书,最适合演示「网关 + 自托管上游」的组合;换成 GitLab、Bitbucket 亦可,只需把
proxy.config.json里authorisedList的 origin 地址替换为对应仓库 URL。 - 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 证书全链路。
三、前置条件
- 服务器已安装 Docker Engine 与 Compose v2 插件,可用
docker compose version验证; - 服务器已安装 openssl(Ubuntu 22.04 自带)与 curl;
- 工作机已安装 git(
git --version≥ 2.30 即可); - 服务器 8147、8149、8150、8151、8152 五个端口空闲(
ss -ltn | grep -E '814[7-9]|815[0-2]'应无输出); - 可访问 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 / httpsServerPort | Git 代理的 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 链接由此生成) |
scmProviders | SCM 供应商数组(name/type/host,自建 Gitea 加 apiUrl);local-gitea 项供 Web 界面做 SCM 身份(Token)绑定 |
tls | HTTPS 端口使用的证书与私钥路径(容器内路径,来自 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
配置说明(关键项):
gitea:Gitea 自身启用 HTTPS(GITEA__server__PROTOCOL: https),证书来自./certs只读挂载;INSTALL_LOCK=true跳过网页安装向导,账号用命令行创建(4.6 节);DISABLE_REGISTRATION=true关闭开放注册。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。client:演示工作台,把 CA 证书放到系统 CA 目录(update-ca-certificates后生效),工作目录挂载到/root/work。- 三个服务共用
gitproxy-stack-netbridge 网络,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/.data | GitProxy NeDB 数据库与推送临时区 |
/opt/gitproxy-2.3/data/gitea/ | /data | Gitea 全量数据(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_GID | 1000 / 1000 | Gitea 运行身份,需与 data/gitea 属主一致 |
GITEA__server__PROTOCOL | https | Gitea 主站启用 HTTPS |
GITEA__server__CERT_FILE | /data/certs/gitea.crt | Gitea 服务端证书(只读挂载) |
GITEA__server__KEY_FILE | /data/certs/gitea.key | Gitea 私钥(只读挂载) |
GITEA__server__DOMAIN | gitea.local | 对外域名(Cookie、克隆地址生成用) |
GITEA__server__ROOT_URL | https://gitea.local:3000/ | 对外根地址 |
GITEA__database__DB_TYPE | sqlite3 | 内置 SQLite,免独立数据库 |
GITEA__security__INSTALL_LOCK | true | 跳过网页安装向导 |
GITEA__service__DISABLE_REGISTRATION | true | 关闭开放注册,账号仅命令行创建 |
gitproxy 服务:
| 变量 | 取值 | 含义 |
|---|---|---|
CONFIG_FILE | /app/proxy.config.json | 声明式传给入口的配置文件路径(真正生效靠 command 里的 --config,见 11.6 节) |
NODE_EXTRA_CA_CERTS | /app/certs/ca.crt | Node.js 额外信任的自签 CA(网关访问 Gitea HTTPS 用) |
GIT_SSL_CAINFO | /app/certs/ca.crt | git 子进程的 CA 信任文件 |
ALLOWED_ORIGINS | * | Web 运行时允许的跨域来源(管理台 API 调用方) |
七、配置参数说明
proxy.config.json 的完整键位在 4.3 节已给出。从部署视角再补充三类高频参数的语义与默认行为:
- 端口四件套:
serverPort(8149,Git 代理 HTTP)、httpsServerPort(8151,Git 代理 HTTPS)、uiPort(8150,管理台 HTTP)、httpsUiPort(8152,管理台 HTTPS)。工作机推送用 8149/8151,人与 API 用 8150/8152,四端口可独立调整。 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 节)。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 获取)。
十二、参考链接
- GitProxy 官方仓库:https://github.com/finos/git-proxy
- GitProxy v2.3.0 Release:https://github.com/finos/git-proxy/releases/tag/v2.3.0
- GitProxy 官方文档站:https://finos.github.io/git-proxy/
- Gitea 官方文档(环境变量配置):https://docs.gitea.com/category/installation
- 本系列姊妹篇:《GitProxy 2.3 介绍与选型指南》、《GitProxy 2.3 配置参数详解》、《GitProxy 2.3 常用 CLI 命令与 REST API 详解》