repo权限管理

需求速览

“任意目录级源码读取权限控制”

1
2
3
不给某人 gateway/ 权限
=> 他从 origin 拉代码时,本地完全没有 gateway/ 内容
=> 但 TeamCity(指定的用户) 可以看到全量源码,并替其他人 build binary

那最适合的方向不是 SOPS / git-crypt,也不是 Bitbucket,而是:

使用支持 path-based read/write ACL 的版本控制系统,例如 Perforce Helix Core 或 SVN,再接 TeamCity。

1. 需求文档草案

1.1 背景与问题定义

当前 ariel 项目存在按文件夹控制源码读取权限的需求。以 ariel 为例(假设有这些目录):

1
2
3
4
5
6
7
ariel/
config/
gateway/
strategy/
model/
scripts/
common/

其中某些目录可能包含敏感逻辑、特殊配置、交易网关、内部策略或高价值实现,不适合对所有 repo 成员开放。例如:

1
2
3
4
5
6
7
8
某些开发者可以访问 strategy/
但不能访问 gateway/

某些开发者可以访问 common/
但不能访问 config/

TeamCity 可以访问完整 ariel 源码
并负责统一 build binary

需求的关键不是简单的“谁能改”,而是:

没有权限的人,从 origin 拉代码时,本地就不应该出现对应 folder 的内容。

因此,这不是普通 GitLab 权限配置问题,而是 repository 内部 path-level read permission 问题。

1.2 从版本控制工具的“制度理念”看这个问题

这个需求很有意思,因为它不仅是一个工具选型问题,也反映了不同版本控制系统背后的治理哲学。

Git 的理念更接近自由分布式。

Git 鼓励每个开发者拥有完整仓库副本,在本地自由 commit、branch、merge,然后再与其他人同步。GitLab 文档也明确说明,Git 是分布式版本控制系统,项目成员 clone 后会获得完整 repository 的本地副本;Reporter、Developer、Maintainer、Owner 等角色都可以 clone repository。

这套理念非常适合开源协作、快速迭代、高信任团队和去中心化开发。它的优势是自由、灵活、离线能力强、分支成本低。但它的天然边界也很明显:

1
2
Git 的权限边界主要是 repo
不是 repo 内部的 folder

也就是说,在一个 Git repo 内部,想做到:

1
2
A 能拉 strategy/
A 不能拉 gateway/

这和 Git 的分布式模型是冲突的。因为一旦给了 repo 读取权限,用户通常就能获得完整仓库内容和历史。

SVN / Perforce 的理念更接近集中式治理。

SVN 和 Perforce 这类集中式版本控制系统,把中央服务器作为权威中心。开发者不是天然拥有完整仓库副本,而是从中央服务器 checkout / sync 自己有权限的路径。

这就像一种“中央统一管理 + 局部差异化授权”的制度设计。中央服务器知道完整代码版图,但每个开发者只能进入自己被授权的区域。SVN 官方文档明确支持 path-based authorization,可以按目录授予或拒绝 read / write 权限;某些目录甚至可以只允许少数人读取。

这正好对应当前需求:

1
2
3
代码整体仍然属于 ariel
TeamCity 仍然可以看到完整 ariel
但 gateway/、config/ 这类特殊目录可以有特殊权限制度

1.2.1 一个更形象的类比:代码治理中的“一国两制”

如果把 ariel 看成一个完整的软件共同体,那么普通目录和敏感目录其实不一定要被拆成独立 repo。

拆成独立 repo 的做法,确实可以解决权限边界问题,但也会带来新的麻烦:

1
2
3
4
5
代码和 config 要跨 repo 同步
相关变更要多个 MR / commit 对齐
版本兼容关系要额外维护
TeamCity build 要拉多个 repo
开发者理解成本增加

这有点像一遇到特殊治理问题,就把一块区域切出去另立门户。技术上可行,但治理上会形成割裂。

更优雅的思路是:

保持一个统一的 ariel 代码版图,但对特殊目录实行特殊权限制度。

也就是技术治理里的“一国两制”:

1
2
3
4
5
6
7
8
9
10
11
12
“一国”:
ariel 仍然是一个统一项目
TeamCity 仍然基于完整源码统一 build binary
版本、构建、发布仍然保持整体一致

“两制”:
common/、strategy/ 等普通区域按普通开发流程协作
gateway/、config/ 等敏感区域实行特殊读取和写入权限

“特别行政区”:
gateway/、config/ 不是独立出去的 repo
而是在统一 repo/depot 内部,被赋予特殊访问规则

这个类比能帮助我们理解:
软件工具不是中性的,它们背后都有自己的组织哲学。

Git 更像自由分布式协作:每个开发者拿到完整副本,强调个人本地自治。
SVN / Perforce 更像集中式治理:中央服务器掌握完整版图,根据路径发放访问权限。
而当前 ariel 的需求,本质上不是“自由复制完整仓库”,而是“统一项目下的分区授权”。

所以,这个问题不能简单地问:

1
GitLab 有没有 folder permission?

更应该问:

1
我们的项目治理模型,到底是自由分布式,还是集中式分区治理?

当前答案更接近后者。

1.3 目标

目标 1:目录级读取权限

系统需要支持按目录配置读取权限:

1
2
3
4
5
6
7
user A 可以读取:
ariel/strategy/
ariel/common/

user A 不可以读取:
ariel/gateway/
ariel/config/

无权限用户从 origin checkout / pull / sync 时,本地不应出现无权限目录的源码内容。

目标 2:目录级写入权限

除读取权限外,也需要支持写入权限:

1
2
3
4
5
6
7
user A 可以改 strategy/
但不能改 gateway/

user B 可以读 gateway/
但不能改 gateway/

gateway owner 可以读写 gateway/

也就是说,权限最好区分:

1
2
3
4
no access
read only
read/write
admin/owner

目标 3:TeamCity 拥有全量读取权限

TeamCity 使用独立 service account,例如:

1
svc_teamcity_ariel

该账号可以读取完整 repo:

1
2
3
4
5
ariel/config/
ariel/gateway/
ariel/strategy/
ariel/model/
ariel/common/

TeamCity 用全量源码 build binary。

目标 4:低权限开发者通过 CI/CD 获取 binary

没有完整源码权限的人,本地不能 build 完整 ariel binary。流程改为:

1
2
3
4
5
6
7
1. 开发者修改自己有权限的目录
2. commit / push / submit 到 origin
3. 触发 TeamCity workflow
4. TeamCity 用完整源码 build binary
5. TeamCity 发布 binary 到 artifact storage 或网盘
6. 开发者下载 binary
7. 开发者用 binary 跑回测

目标 5:权限不能通过历史、分支、tag、网页、API 绕过

无权限用户不应通过以下方式拿到受限目录内容:

1
2
3
4
5
6
7
8
9
10
11
checkout / pull / sync
查看历史版本
查看 branch / tag
网页浏览 repo
搜索代码
diff / patch
API 下载 raw file
TeamCity log
TeamCity artifact
网盘 artifact
debug symbol / source map

这个点很关键。不能只限制“当前目录”,还要避免历史版本泄露。

1.4 非目标

当前阶段可以先不支持:

1
2
低权限开发者本地直接用完整源码 build ariel
低权限开发者本地直接访问受限 config 跑回测

也就是说,当前可以接受:

1
2
3
本地源码不完整
本地不能 build 完整 binary
完整 build 交给 TeamCity

1.5 关键安全风险

如果 TeamCity 能看到完整源码,而低权限开发者能让 TeamCity 执行他提交的任意脚本,那么他可能通过 CI/CD 把受限目录内容打印到 log 或打包进 artifact。

例如低权限开发者没有 gateway/ 权限,但如果他能改 build script,他可能提交:

1
cat gateway/secret_file.cpp

然后 TeamCity build log 里就泄露了 gateway/。

即使他不能改 build script,如果 TeamCity 会运行他提交的测试代码、代码生成脚本、pre-build hook、post-build hook,也可能泄露源码。

所以 CI/CD 方案必须加限制:

1
2
3
4
5
6
低权限用户触发的 TeamCity build,不能执行任意用户提交的脚本
TeamCity build 配置必须由管理员维护,不从低权限 branch 读取可执行 CI 配置
build log 和 artifact 要做泄露检查
受限目录不能被打包进 artifact
debug symbol / source map / source archive 要谨慎发布
production/full-source build 最好在 trusted review 后运行

3. 方案详细评估

方案 A:GitLab 单 repo + CODEOWNERS

GitLab CODEOWNERS 可以指定某些文件或目录的 owner,并要求相关 owner approve 后才能合并。这个方案适合控制:

1
2
3
谁能改 gateway/
谁能 approve config/
谁能 merge 到 main

但它不能控制:

1
2
谁能读取 gateway/  
谁能 clone config/

所以 CODEOWNERS 只能解决“写入审批”,不能解决“读取隔离”。

结论:不满足当前需求。


方案 B:Git sparse-checkout / partial clone

Git sparse-checkout 可以让本地 working tree 只显示部分目录。它适合大仓库性能优化,或者让开发者本地工作区更清爽。

但它不是权限系统。

用户如果本身拥有 Git repo 读取权限,sparse-checkout 不能被当作安全边界。它不能保证用户无法主动拉取其他目录内容。

结论:不满足当前需求。


方案 C:SOPS / git-crypt 加密敏感内容

SOPS / git-crypt 适合保护配置文件、secret、token 等内容。

效果是:

1
2
无权限用户可以拉到文件  
但看到的是密文

这适合 config/ 里有密码、token、证书的场景。

但当前的需求是:

1
无权限 folder 本地就不应该拉下来

而不是:

1
拉下来但看不懂

并且如果 gateway/ 这类目录是源码逻辑,不可能把整个源码目录都当成配置文件长期加密维护。

结论:适合保护 secret/config,不适合任意 folder 权限管理。


方案 D:拆 repo / Git submodule

可以把敏感目录拆成独立 repo:

1
2
3
4
5
ariel-main/
common/
strategy/
gateway/ -> submodule: ariel-gateway.git
config/ -> submodule: ariel-config.git

这个方案符合 Git 的权限模型,因为 Git 的权限边界是 repo。
用户没有 ariel-gateway.git 权限,就无法拉取 gateway 内容。

但缺点也明显:

1
2
3
4
5
相关代码要跨 repo 一起更新  
多个 MR / branch / commit 要对齐
TeamCity build 要管理多个 repo
权限目录多了以后 repo 数量膨胀
开发体验变复杂

这就是说“相关的要一起更新的时候会麻烦”。

从治理类比上看,这更像把特殊区域拆出去单独建制。能解决权限,但破坏了 ariel 作为统一工程体的完整性。

结论:如果必须继续使用 Git,这是现实方案;但不符合当前“不希望拆 repo”的方向。


方案 E:SVN path-based authorization + TeamCity

SVN 的理念天然符合这个需求。

SVN 是集中式模型,中央服务器可以按路径授权。官方文档明确说明 SVN 可以定义细粒度访问规则,例如一组用户可以写某个目录,另一个目录只有少数人可读。

可以把 ariel 放在一个 SVN repository 里:

1
2
3
4
5
6
7
/ariel/trunk/  
common/
strategy/
model/
gateway/
config/
build/

然后配置权限:

1
2
3
4
5
6
7
8
9
10
11
12
strategy_devs:
read/write /ariel/trunk/strategy
read /ariel/trunk/common
no access /ariel/trunk/gateway
no access /ariel/trunk/config

gateway_devs:
read/write /ariel/trunk/gateway
read /ariel/trunk/common

teamcity:
read /ariel/trunk/*

TeamCity 已经支持 Subversion VCS root,并且 TeamCity bundled SVNKit,不需要额外在 TeamCity server 或 agent 上安装 SVN client。

这套方案非常符合“一国两制”的技术类比:

1
2
3
4
5
ariel 仍然是一个统一项目
特殊目录不需要拆出去
中央 SVN server 按 path 管理权限
TeamCity 作为中央 build 账号拥有全量视图
普通开发者只能 checkout 自己有权限的区域

结论:非常适合当前需求,尤其适合作为第一阶段落地方案或 POC。


方案 F:Perforce Helix Core + TeamCity

Perforce 也是集中式版本控制系统,并且在大型 monorepo、游戏开发、芯片、金融工程等需要复杂权限和大规模源码管理的场景中比较常见。

Perforce 支持通过 protections table 按路径配置访问权限;官方文档中也说明可以按 Users、Groups、Depot Tree 查看用户或组对文件/文件夹的权限,没有授权的路径会显示 no access。

TeamCity 也支持 Perforce VCS root。

Perforce 的治理模型也非常符合当前需求:

1
2
3
4
统一 depot
路径级权限
TeamCity 全量 sync
普通开发者只 sync 被授权路径

相比 SVN,Perforce 更适合长期维护复杂权限、大型代码库、大文件和多团队工程。

缺点是:

1
2
3
4
采购成本更高
学习成本更高
迁移成本更高
需要专门管理员维护权限表

结论:适合长期正式方案;如果当前只是快速验证,SVN 更轻。


方案 G:Bitbucket

Bitbucket 本质上仍是 Git-based code hosting。Bitbucket Cloud 官方文档中的 branch permissions 主要用于控制谁可以 write / merge 到某些 branch 或 branch pattern。

这和当前需求不同。

当前需求是:

1
控制 repo 内某个 folder 是否可读

而 Bitbucket 的主能力是:

1
控制 branch workflow

所以 Bitbucket 不应该作为这个需求的核心解决方案。

结论:不建议为了 folder-level read permission 采购 Bitbucket。

4. 方案对比表

方案 无权限 folder 是否拉不下来 是否支持任意目录读权限 是否保留统一工程体 是否保留单源码树 TeamCity 全量 build 迁移成本 结论
GitLab CODEOWNERS 否 否 是 是 是 低 只能控写,不能控读
Git sparse-checkout 否 否 是 是 是 低 性能工具,不是权限工具
SOPS / git-crypt 否,能拉到密文 部分 是 是 是 中 适合 secret/config,不适合源码目录
Git submodule 拆 repo 是 是,按 repo 边界 否 否 是 中 保留 Git 的现实方案,但 repo 会变多,工程割裂
SVN path ACL 是 是 是 是 是 中 适合低成本验证,当前最适合落地
Perforce Helix Core 是 是 是 是 是 高 最适合长期正式方案,但成本更高
Azure TFVC / Plastic 是 是 是 可集成 中到高 可行,看公司生态
Bitbucket 否 否 是 是 是 中 不推荐

推荐结论

1
2
3
4
5
6
7
8
如果要低成本快速验证:
先做 SVN path-based authorization + TeamCity POC

如果要长期正式满足当前需求:
选 Perforce Helix Core + TeamCity

如果公司强制继续用 GitLab/Git:
只能拆 repo/submodule,不能在单 Git repo 内实现任意 folder read ACL

5. SVN path-based auth + TeamCity build 目标架构

1
2
3
4
5
6
7
8
9
SVN Repository: ariel

/ariel/trunk/
common/
strategy/
model/
gateway/
config/
build/

权限组:

1
2
3
4
5
6
ariel_common_readers
ariel_strategy_devs
ariel_gateway_devs
ariel_config_owners
ariel_build_admins
ariel_teamcity_service

权限示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ariel_strategy_devs:
read/write /ariel/trunk/strategy
read /ariel/trunk/common
no access /ariel/trunk/gateway
no access /ariel/trunk/config

ariel_gateway_devs:
read/write /ariel/trunk/gateway
read /ariel/trunk/common

ariel_config_owners:
read/write /ariel/trunk/config

svc_teamcity_ariel:
read /ariel/trunk/*

开发者流程:

1
2
3
4
5
6
7
1. 只 checkout 自己有权限的目录
2. 修改并提交代码
3. 触发 TeamCity workflow
4. TeamCity 用完整权限 checkout ariel
5. TeamCity build binary
6. TeamCity 发布 artifact / 上传网盘
7. 开发者下载 binary 跑回测

6. TeamCity 安全要求

6.1 低权限用户不能控制 TeamCity 执行的任意脚本

不要让低权限用户通过修改 repo 里的脚本来控制 TeamCity 做什么。

危险例子:

1
2
cat gateway/secret.cpp
zip artifact.zip gateway/

如果 TeamCity workspace 有完整源码,这类脚本会泄露受限目录。


6.2 Build 配置应由 TeamCity admin 固定

建议:

1
2
3
4
TeamCity build steps 固定在 TeamCity 配置里
普通开发者不能改 build configuration
普通开发者不能改 production build script
低权限 branch 的 build 不执行任意 test hook / post-build hook

如果 build 必须使用 repo 里的脚本,例如:

1
build/build_ariel.sh

那这个目录也要被视为敏感控制面:

1
2
3
build/
ci/
scripts/

这些目录的写权限必须只给 trusted build owner。


6.3 区分两类 build

建议至少分两类:

1
2
3
4
5
6
7
8
9
10
11
普通验证 build:
面向低权限提交
不暴露 full-source log
不上传 source archive
不上传 debug symbol
不执行危险脚本

受信任 build:
需要 owner review 后触发
可以访问完整源码
可以用于正式 binary

6.4 Artifact 也要做权限控制

TeamCity 产出的 binary 可能包含敏感信息,例如:

1
2
3
4
5
6
7
8
debug symbols
source path
source map
embedded config
generated code
build logs
coverage reports
test reports

所以发布 artifact 时要检查:

1
2
3
4
5
不要发布源码包
不要发布包含 gateway 源码片段的 log
不要发布过度详细的 debug symbol
不要把 config 明文打进 binary
网盘目录也要按权限控制

7. 阶段性落地建议

Phase 1:SVN POC

先做一个最小验证:

1
2
3
4
/ariel/trunk/common/
/ariel/trunk/strategy/
/ariel/trunk/gateway/
/ariel/trunk/config/

设置三个用户:

1
2
3
4
5
6
7
8
9
10
11
alice:  
能读写 strategy/
能读 common/
不能读 gateway/
不能读 config/

bob:
能读写 gateway/

svc_teamcity:
能读完整 ariel

验收标准:

1
2
3
4
5
6
7
8
alice checkout 后看不到 gateway/  
alice 无法通过历史版本拿到 gateway/
alice 无法 commit 到 gateway/
bob 可以 checkout / 修改 gateway/
TeamCity 可以 checkout 完整 ariel
TeamCity 可以 build binary
TeamCity artifact 不包含 gateway 源码
alice 可以下载 binary

Phase 2:接入真实 TeamCity workflow

验证:

1
2
3
4
5
提交代码后触发 TeamCity  
TeamCity 使用 service account 拉全量源码
TeamCity build binary
binary 上传 artifact storage / 网盘
低权限开发者可以下载 binary

Phase 3:长期评估 Perforce

如果后续出现这些情况:

1
2
3
4
5
目录权限规则越来越多  
开发组越来越多
binary 和大文件越来越多
monorepo 越来越复杂
SVN workflow 不够用了

再评估 Perforce Helix Core 作为长期治理平台。

8. 总结

现在这个需求已经不是单纯的 config 加密,而是任意 folder 的 read permission。

如果要求是“不给某人 gateway 权限,他从 origin 拉代码时本地就完全没有 gateway 内容”,那么 GitLab / Bitbucket / Git sparse-checkout / CODEOWNERS 都不能严格满足。Git 的权限边界是 repo,不是 repo 内 folder。CODEOWNERS 只能控制谁 approve / merge,不能控制谁读取。

SOPS / git-crypt 适合保护敏感 config 或 secret,但它的效果是“拉下来的是密文”,不是“folder 拉不下来”,所以也不满足当前需求。

真正匹配这个需求的是支持 path-based read/write ACL 的集中式版本控制系统,比如 SVN 或 Perforce。TeamCity 用 service account 读取完整源码并 build binary,低权限开发者只 sync 自己有权限的目录,需要完整 binary 时通过 TeamCity workflow 生成并从 artifact storage / 网盘获取。

建议长期方案评估 Perforce Helix Core,因为它更适合长期维护多个目录、多组权限、monorepo 和 binary build 场景;如果我们想先低成本验证,可以先用 SVN path-based authz + TeamCity 做一个小 POC。

另外要注意 TeamCity 安全:如果 TeamCity 能看到完整源码,不能让低权限开发者通过修改 build script 或 CI 配置把受限目录内容打到 log/artifact 里。TeamCity build 配置、build script、artifact 发布都要受控。

结论

这次需求本质上不是“GitLab 权限没配好”,而是版本控制系统治理理念的选择。

Git 的哲学是自由分布式:每个人拿完整副本,本地自治,最后同步。
SVN / Perforce 的哲学是集中式治理:中央服务器掌握完整版图,按路径授权访问。

而 ariel 当前需要的是:

1
2
3
4
5
统一项目  
统一构建
特殊目录特殊治理
无权限人员本地拉不到敏感 folder
TeamCity 作为中央 build 系统统一产出 binary

所以最贴切的方案不是拆 repo,而是:

在统一 ariel 代码版图内,对 gateway/config 等特殊目录实行“特别行政区式”的 path-level 权限治理。

当前建议先用:

1
SVN path-based authorization + TeamCity

作为落地方案;后续如果规模扩大,再评估:

1
Perforce Helix Core + TeamCity

这样既能避免 Git 单 repo 无法做目录级读取权限的问题,也能避免拆 repo 带来的工程割裂。