如何基于SSH密钥限制Git SSH访问

4
我有一个私人的 git 服务器,只有一个用户 git 和 SSH 密钥认证。目前,只有我在使用它,但我想添加更多人,并且希望每个人都使用用户 git 连接到服务器并执行 git clonegit push 等操作。如果所有存储库都是“公共的”,那么我知道如何解决这个问题。但是例如我想有一个私有存储库,我仍然会使用我的 SSH 密钥通过 git clone git@server:repo 克隆它,但是我不希望其他用户能够使用他们的 SSH 密钥克隆它。
我查阅了 git-scm 文档关于设置服务器(对于公共存储库很有帮助),以及这篇文章,但是这篇文章似乎只解决了私人存储库的问题。
TLDR:当你在 GitHub 上克隆存储库时,你说 git clone git@github.com:user/repo,你的 git 用户名和 ssh 密钥被发送过去。现在,取决于你是否有此存储库的权限,你可以克隆它,否则不能。因此,基本上每个人都在表面上使用 git 用户,但在幕后会进行某些授权。GitHub 是如何处理这个问题的呢?
4个回答

3

gitolite似乎恰好符合您管理基于ssh密钥的存储库/分支访问权限的需求。

但如果您想从头开始构建类似的东西,您需要研究authorized_keys文件中的选项,特别是commandenvironment。通过这些选项,您可以强制执行特定的命令/脚本或添加/覆盖基于使用的ssh密钥的环境变量。

例如,您可以编写一个读取用户允许的存储库作为其参数并强制选择某些ssh密钥运行该脚本的脚本:

# file ~/.ssh/authorized_keys
command="/home/git/bin/git-only-shell /home/git/repos/repo1" ssh-rsa AAAAB2...
command="/home/git/bin/git-only-shell /home/git/repos/repo1 /home/git/repos/repo2" ssh-rsa AAAAB3...

现在脚本可以从$SSH_ORIGINAL_COMMAND读取请求的repo,并检查它是否包含在传递的列表中。一个完整但是简陋的例子实现这个脚本git-only-shell可能像这样:

#!/bin/bash

# verify that $SSH_ORIGINAL_COMMAND starts with git-upload-pack, git-upload-archive or
# git-receive-pack, followed by a space
if ! [[ "$SSH_ORIGINAL_COMMAND" == git-upload-pack\ * || "$SSH_ORIGINAL_COMMAND" == git-upload-archive\ * || "$SSH_ORIGINAL_COMMAND" == git-receive-pack\ * ]]; then
    echo "unsupported command" >&2
    exit 1
fi

# remove first word (git command)
ARGUMENTS="${SSH_ORIGINAL_COMMAND#git-* }"

# use eval to un-quote repo path (it is passed in single-quotes)
REPO_PATH="$(eval "echo $ARGUMENTS")"

# allowed repos are passed as arguments to this script
ALLOWED_REPOS=("$@")

# check if repo was whitelisted
IS_ALLOWED=false
for repo in "${ALLOWED_REPOS[@]}"; do
    if [[ "$REPO_PATH" == "$repo" ]]; then
        IS_ALLOWED=true
    fi
done

if [[ $IS_ALLOWED == "false" ]]; then
    echo "access to this repo not allowed" >&2
    exit 1
fi

# execute the original command
eval "$SSH_ORIGINAL_COMMAND"

这似乎正是我正在寻找的,谢谢!我会测试一下,如果它能满足我的需求,我会接受你的答案。 - campovski
1
"eval“语句在命令注入和CRE(评估代码执行)漏洞中存在危险。 - e-info128
最好[将本地变量转换为小写以避免名称冲突。](https://dev59.com/anRB5IYBdhLWcg3wSVYI) - tripleee

1

gitolite这样的工具过于复杂和难以使用,或者没有提供所需的功能。我最终采用了以下解决方案:

  • 一个包含userrepo表以及多对多表permissions的PostgreSQL数据库。
  • 一个用C语言编写的程序,检查用户是否有读/写权限来访问请求的存储库,该存储库从$SSH_ORIGINAL_COMMAND中提取。
  • .ssh/authorized_keys中,每个密钥都有自己的command,当该密钥用于git操作时,它会调用一个带有与该密钥关联的用户名和从SSH命令中提取的存储库的C程序。

因此,当用户向他的帐户添加SSH密钥时,会在.ssh/authorized_keys中添加一行,例如:

command="/home/git/check_perms username \"${SSH_ORIGINAL_COMMAND}\"" no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ...

然后每次使用该密钥进行Git操作时,都会运行command。如果用户没有足够的权限,则Git操作将被终止。


1
要实现类似的功能,您可以依赖于专用工具,例如 gitolite
否则,您可以安装一个完整的GitLab服务器,它还将为存储库提供细粒度访问控制,使用“Git URL”,如git@your-gitlab-domain.com:user/repo.git (顺便说一下,似乎GitLab在5.0.0版本之前曾经依赖于gitolite

我不想依赖GitLab,因为我想从头开始构建GitHub / GitLab。我已经看过gitolite,但还没有找到解决方案。我想我需要更深入地研究它。 - campovski

0
我通过使用git-shell和一点点bash解决了这个问题。基本想法是检查钥匙用户是否可以访问给定的仓库,如果是,则使用git-shell运行他们的命令。
脚本如下:
#!/bin/bash

SSH_ORIGINAL_COMMAND=${SSH_ORIGINAL_COMMAND#git-}
a=( ${SSH_ORIGINAL_COMMAND//\'/} ) # strip single qoutes [^1]

cmd=${a[0]} # git * command
path=${a[-1]} # targeted path
unset a[0] a[-1]

# optional arguments
if (( ${#a[@]} )); then
    args="#{a[*]} "
fi

# check if the target path is whitelisted
for a_path in $@; do
    f_path="${a_path}${path#@(a_path/|../)}/" # f(ull)_path
    if [ -d "$f_path" ]; then
        /path/to/git-shell -c "git $cmd $args'$f_path'"
    fi
done


我们接着修改git用户的.ssh/authorized_keys文件;
command="/path/to/script /foo/ /bar/ /biz/" ssh-...

攻击

通过专门使用git-shell,可以防止任意命令执行,因此我们只需要担心未经授权的存储库访问——这是通过以下两种方式解决的:

  • 在组合f_path时删除../实例。
  • 在检查目录存在性时,限定目标路径范围。

e.g.;

 - foo     [Authorised]
 |_ biz
 - bar
 |_ boo

====
- `git clone git@server.dom:foo/../bar/boo` fails
- `git clone git@server.dom:bar` fails
- `git clone git@server.dom:foo/biz` passes

参考资料

[1]: 这里形成a的方式可能与通配符不兼容;有关更多上下文信息,请参见此SO答案


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接