Files
codex-app-server/chv-guest/git-credential-chv-authd
T
host a90cf78d84 feat(chv): 对齐 Muse P3 收尾——authd 替身凭证 + Sentinel 出口审批
- 新测试 cloud/tests/test_chv_muse_p3.py 30 项:替身过期、真 token
  泄露面、SSRF 挡截、deny/ask 路径、authd-seed 拆分、wake 预审、
  客内 authd 守护进程、git credential helper 端到端(真 git fill)
- 8 个旧测试按新 authd 行为改写(未删测试、未放宽断言):
  GITEA_TOKEN 改走 authd-seed,build_ssh_command 不再 source 进
  codex 环境;wake 测试补 Sentinel 白名单 + 假解析器
- 7 处超 50 行函数拆到 50 行内(不改行为);补 authd/sentinel
  公共 API 注解,mypy 在改动文件上干净
- 真问题修复:git 默认不把 path 发给 helper,chv-sync boot 加
  credential.useHttpPath=true,否则 authd 路径每次都拒绝
- 全量 cloud 套件 1897 项 OK(skipped=8)

真机待验证(未做,等第三棒让出 WSL 测试机):见
docs/chv-muse-p3-gap.md §3.6 清单
2026-11-06 21:21:55 +00:00

108 lines
3.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""git credential helper:向 chv-authd 按次取凭证(feat-chv-muse-p3)。
git credential 协议:git 把 `protocol/host/path`(每行 `key=value`,
空行结束)写进本进程 stdin;本进程向 authd 的 Unix socket 发
一次 JSON 请求,拿到用户名/口令后按协议回给 git(属性 + 空行)。
口令只进 git 子进程内存:本进程不写文件、不打日志(拒绝时只
报 kind,不含值);`password_expiry_utc` 按释放的 expires_at
回给 git,git 在该时间后重新问 helper(不缓存过期凭证)。
authd 拒绝 / socket 不通时本进程 exit 1——git 操作随之失败
(fail-closed,绝不回落成“无凭证也照推”)。
用法(chv-sync boot 配好后 git 自动调,不用手工跑):
git config --global credential.helper /usr/local/bin/git-credential-chv-authd
git config --global credential.useHttpPath true
(后者不可少:git 默认不把 path 发给 helper,没有 path 本进程
无法推断仓库与操作,会直接拒绝。)
socket 路径可用 CHV_AUTHD_SOCKET 覆盖(测试用 /tmp 下的假 socket)。
"""
import json
import os
import socket
import sys
DEFAULT_SOCKET_PATH = "/run/chv-authd/authd.sock"
SOCKET_TIMEOUT_SECONDS = 5
def _read_credential_request():
"""读 git credential 协议的请求属性(空行结束)→ dict。"""
attrs = {}
for line in sys.stdin:
line = line.rstrip("\n")
if not line:
break
if "=" in line:
key, value = line.split("=", 1)
attrs[key.strip()] = value.strip()
return attrs
def _ask_authd(socket_path, host, path):
"""向 authd 要一次凭证:返回 (username, password, expires_at)。
拒绝/通信失败即 RuntimeError(只含 kind,不含值)。"""
req = {"connector": "gitea", "host": host, "path": path}
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(SOCKET_TIMEOUT_SECONDS)
try:
sock.connect(socket_path)
sock.sendall((json.dumps(req) + "\n").encode("utf-8"))
chunks = []
while True:
data = sock.recv(65536)
if not data:
break
chunks.append(data)
if b"\n" in data:
break
except OSError as exc:
raise RuntimeError("authd_unreachable:%s" % exc)
finally:
sock.close()
try:
resp = json.loads(b"".join(chunks).decode("utf-8"))
except ValueError:
raise RuntimeError("authd_bad_response")
if not resp.get("ok"):
raise RuntimeError("authd_refused:%s"
% resp.get("error", "refused"))
return (resp["username"], resp["password"],
resp.get("expires_at"))
def main(argv=None):
"""helper 入口:读协议 → 问 authd → 回属性。拒绝即 exit 1。"""
attrs = _read_credential_request()
host = attrs.get("host", "")
path = attrs.get("path", "")
if attrs.get("protocol") != "https" or not host or not path:
print("git-credential-chv-authd: 请求不完整(须 protocol=https "
"+ host + path),拒绝", file=sys.stderr)
return 1
if not path.startswith("/"):
path = "/" + path
# git 的 info/refs 路径带 ?service=... 查询串:Sentinel 的
# L7 规则按干净路径匹配,这里先去掉查询串再问 authd。
path = path.split("?", 1)[0]
socket_path = os.environ.get("CHV_AUTHD_SOCKET") \
or DEFAULT_SOCKET_PATH
try:
username, password, expires_at = _ask_authd(
socket_path, host, path)
except RuntimeError as exc:
print("git-credential-chv-authd: %s" % exc, file=sys.stderr)
return 1
print("username=%s" % username)
print("password=%s" % password)
if expires_at:
print("password_expiry_utc=%d" % int(expires_at))
print()
return 0
if __name__ == "__main__":
sys.exit(main())