Inspirer

从 gitolite 实现原理拓展 gitlab 及其他 git 服务的实现原理

通过本文了解 git server 和 SSH 授权实现原理,如何使用数据库而不是 authorized_keys 来管理用户,并实现更精细的自定义访问权限管理。本文不会实现完整的 Git 服务,但可以通过本文了解实现原理。

ssh 协议下的 clone、push

clone 和 push 过程

git clone ---> ssh -> | SERVER | -> sshd ---> git-upload-pack ; git fetch ---> ssh -> | ORIGIN SERVER | -> sshd ---> git-upload-pack

git push ---> ssh -> | SERVER | -> sshd ---> git-receive-pack

而 gitlab、gitolite 是通过在 sshd 调用具体命令(git-upload-pack 或 git-receive-pack)之间加了一个中间命令,该命令会进行额外的校验操作(权限验证),最终决定是否执行下一步命令。

由于 git ssh 大多时候都是通过 git 用户(例如 git@github.com,可以看到用户名为 git),但实际情况是每个仓库都有自己的权限,仅通过 git 用户是无法区分的。

因此实际过程应该如下(以 git push 为例):

git push ---> ssh -> | SERVER | -> sshd ---> {custom-shell} ---> git-receive-pack

其实,custom shell 就是一个脚本或可执行文件,问题在于,如何让 sshd 去主动调用这个执行文件,并传递一些我们需要用于判定的参数呢?

sshd 通过 authorized_keys 来设置客户端公钥,客户端通过使用私钥连接时,sshd 会读取该文件遍历其中条目来获取对应设置信息,authorized_keys 设置如下:

command="[path]/custom-shell sitaram",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA18S2t...
command="[path]/custom-shell usertwo",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...

和我们常见的设置不太一样的是,每一行的开头多了一项设置,即当指定私钥连接时,执行命令并传递指定参数,我们就可以利用其判断具体登录上来的用户到底是谁。另外,sshd 在调用指定 command 之前,会修改环境变量 SSH_ORIGINAL_COMMAND,该值是原本实际 ssh 连接上来希望执行的命令,我们就可以利用这个,在判定完权限后知道该调用什么命令了。

不过这个不适用于对分支进行权限限制,若要限制某用户操作指定分支的权限,则需要通过 git hook 实现,这个可参考相关文档(本文开头已给出)。

更高级的公钥认证

通过 authorized_keys 管理公钥很不灵活,我们更希望通过数据库或其他服务来进行,这便于拆分业务和未来横向扩展。实际上仅需要通过改动 sshd 的配置文件即可。

通过修改 /etc/ssh/sshd_config 中 AuthorizedKeysCommand 即可,且同时要求设定 AuthorizedKeysCommandUser。

在进行下一步前,需要保证系统上的 OpenSSH 版本高于 6.9,否则 AuthorizedKeysCommand 无法接收诸如指纹信息等参数。

设置参考如下:

AuthorizedKeysCommand /server/bin/command.sh %u %t %f
AuthorizedKeysCommandUser root

其中可以看到,附带的参数中有几个占位符:%u%t%f,这几个占位符会替换成传递给 AuthorizedKeysCommand 的具体参数,通过 man sshd_config 可查阅到关于这部分的信息:

Arguments to some keywords can make use of tokens, which are expanded at runtime:

      %%    A literal `%'.
      %F    The fingerprint of the CA key.
      %f    The fingerprint of the key or certificate.
      %h    The home directory of the user.
      %i    The key ID in the certificate.
      %K    The base64-encoded CA key.
      %k    The base64-encoded key or certificate for authentication.
      %s    The serial number of the certificate.
      %T    The type of the CA key.
      %t    The key or certificate type.
      %u    The username.

AuthorizedKeysCommand accepts the tokens %%, %f, %h, %t, and %u.

如上所言,有这么多占位符,但是 AuthorizedKeysCommand 仅支持有限的参数,需要注意。

AuthorizedKeysCommand 命令执行返回结果格式和 authorized_keys 的单条数据结果格式一致即可生效,若返回错误、结束状态非 0,则 SSHD 仍然会去查询 authorized_keys,若 authorized_keys 不存在,则提示输入密码。

需要注意的权限问题

AuthorizedKeysCommand 这个地方如果设置了一个自定义的命令,这个命令不是在 / 目录下,则会出现无法执行,查看日志提示:

May 27 23:50:54 bogon sshd[4445]: error: Could not stat AuthorizedKeysCommand "<your command path>": Permission denied

往往疑惑在于,该命令 mode 为 0700,哪怕是 777 也仍然报错,用户、用户组都正确为什么提示权限问题呢?出现这个问题的原因是 SElinux 导致的,SElinux 权限限制更加精细,通过 stat 命令查看能够正确执行的文件是这样的:

[root@localhost ~]# stat /authkey
  File: ‘/authkey’
  Size: 889         Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 143414      Links: 1
Access: (0700/-rwx------)  Uid: (    0/    root)   Gid: (    0/    root)
Context: unconfined_u:object_r:etc_runtime_t:s0
Access: 2018-05-27 23:33:38.977963479 -0400
Modify: 2018-05-27 23:33:12.481929023 -0400
Change: 2018-05-27 23:33:12.485179139 -0400
 Birth: -

而不正确的是这样的:

[root@localhost ~]# stat ~/gitserver-authorize
  File: ‘/root/gitserver-authorize’
  Size: 621         Blocks: 8          IO Block: 4096   regular file
Device: fd00h/64768d    Inode: 8455139     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Context: unconfined_u:object_r:admin_home_t:s0
Access: 2018-05-28 00:03:55.153955969 -0400
Modify: 2018-05-28 00:03:55.156122714 -0400
Change: 2018-05-28 00:03:55.156122714 -0400
 Birth: -

对比发现在于 Context 字段中第三栏有差异,可执行的值为:etc_runtime_t,相反,不能执行的为:admin_home_t,这里需要补充关于这个 Context 的意义,其实所有文件、目录都有该值(废话),可以通过 ls -lZ 查看,对单个文件则可用 stat 查看,可以看到通过 ls 列出的列表中,Context 被列出。

Context 字段结构为:

user:role:type:level

需要注意,user、role 等都是指的 SELinux 的 user 和 role。关于 SELinux 用户和 Linux 用户的关联,则需要通过一系列工具查看。相关资料文章首部已给出,可参考查阅。下面直接给出之前问题的解决办法。

问题原因清楚了,解决办法

  1. 关闭 SELinux
  2. 设置文件目录 Context,使用命令 chcon 修改:

    chcon -t etc_runtime_t -R /gitserver

汇总的相关资料来源