GitLab Shell如何通过SSH工作
转自:https://wayjam.me/post/how-gitlab-shell-works-with-ssh.md
GitLab访问Git仓库
首先回顾GitLab的Git仓库四种访问方式:
- git pull over http -> gitlab-rails (Authorization) -> accept or decline -> execute git command
- git push over http -> gitlab-rails (git command is not executed yet) -> execute git command -> gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
- git pull over ssh -> gitlab-shell -> API call to gitlab-rails (Authorization) -> accept or decline -> execute git command
- git push over ssh -> gitlab-shell (git command is not executed yet) -> execute git command -> gitlab-shell pre-receive hook -> API call to gitlab-rails (authorization) -> accept or decline push
四种方式都有GitLab Shell的参与,但不同过程GitLab Shell发挥了不同的作用,并且它并不是一个整体的服务,而是由一些子命令组合而成。HTTP方式的Git操作,经gitlab workhorse直接交由Rails应用处理,然后通过HTTP协议交换数据,对于git的操作有三条路径:Gem包Rugged、Raw Git命令或者Gitaly,push/pull一般只跟后两种有关,GitLab Shell充当的作用仅仅是git hook的作用。
SSH方式的push/pull是GitLab Shell的主场景,而Rails在这其中充当了权限再验证的角色。
Git SSH 传输协议
首先,简要说明Git是如何通过SSH协议与服务端的Git交互数据。
ssh git@example.com "the-command"
在客户端执行这样的命令时候,服务端SSHD验证身份通过后,默认将启动一个Shell解析执行the-command的命令。普通使用中,大多都不加自定义命令,这将启动一个Shell交互式命令解析器。
要了解GitLab Shell的原理,不能不说它的“前任”——Gitolite。Gitolite是一个Git的授权层前端,同样提供了HTTP(httpd)和SSH的方式访问Git仓库。而Gitlab在v5.0完全用GitLab Shell替代了Gitolite,前者完全依赖Rails层的权限认证(项目、分支、用户等的权限),后者则由于需要完全保持一份冗余数据在自身的配置文件,主要由于速度和数据不同步被Gitlab官方放弃。但是,二者仍然采用了authorized_keys的command magic方案。
GitLab CE 10.4之后加入了`AuthorizedKeysCommand`的使用(_require_ OpenSSH 6.9+),使用自定义程序匹配Key而不是文件文本匹配。
SSH command magic
SSHD服务端收到客户端的连接请求后,会在authorized_keys进行匹配,认证失败则拒绝连接。查看~/.ssh/authorized_keys文件内容如下:
# Gitolite
command="[path]/gitolite-shell user-one",[more options] ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
# GitLab Shell
command="/home/git/gitlab-shell/bin/gitlab-shell key-1",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEArXtCT...
可以发现保存的每条公钥前面都有command=,Gitolite和GitLab Shell后面的参数代表着用户的识别信息,Gitolite保存在配置文件,而gitlab保存在数据库。当用户的服务端校验成功后,则会执行command=后的命令,而不是shell access,同时将SSH命令所要执行的命令赋值给SSH_ORIGINAL_COMMAND。所以与客户端交互的真正命令是(以pull代码为例):
SSH_ORIGINAL_COMMAND=git-upload-pack git-pack/home/git/gitlab-shell/bin/gitlab-shell key-1
"Shell"
GitLab Shell由ruby脚本和go程序组成,首先看GitLab Shell入口代码:bin/gitlab-shell。
#!/usr/bin/env ruby
# ...
key_id = /key-[0-9]+/.match(ARGV.join).to_s
original_cmd = ENV.delete('SSH_ORIGINAL_COMMAND')
# ...
require File.join(ROOT_PATH, 'lib', 'gitlab_shell')
if GitlabShell.new(key_id).exec(original_cmd)
# ...
这段代码里面的两个变量:
key_id-> sshd调用GitLab Shell时传入的参数。original_cmd-> 即上文提到的SSH_ORIGINAL_COMMAND环境变量,并且获取完即移除。
举个例子,如果用户通过git客户端调用git clone git@server的时候,实际上git客户端启动的是receive-pack(git由许许多多子命令组成)并且在内部执行的是ssh git@git@server git-upload-pack git@server,那么此时,服务端给GitLab Shell设定的环境变量则是git-upload-pack git@server,。
然后看lib/gitlab-shell.rb代码,初始化一个GitlabShell,并且执行exec,而exec里面有几个关键步骤:
def exec(origin_cmd)
unless origin_cmd
puts "Welcome to GitLab, #{username}!"
return true
end args = Shellwords.shellwords(origin_cmd)
args = parse_cmd(args) if GIT_COMMANDS.include?(args.first)
GitlabMetrics.measure('verify-access') { verify_access }
end process_cmd(args) true
rescue something #异常
end
1. 解析命令 parse_cmd
主要是处理Windows/Linux命令差异、屏蔽非法命令、LFS命令。
2. 验证权限 verify_access
/api/v4/internal/allowed
通过Rails的HTTP API :/api/v4/internal/allowed发送查询参数到Rails,接口返回此用户对这个仓库是否有此操作的权限。
参数:
{
command => git命令
repo => 仓库信息
key_id => SSH key id(在数据库能找查找到对应的用户)
protocol => ssh/env
}
3. 处理命令 process_cmd
处理命令,检测gitaly此特性是否有开启,如果开启则调用gitaly处理,否则则调用git原生命令。
设定环境变量,然后使用Kernel.exec调用目标进程替代当前进程。
env = {
'HOME' => ENV['HOME'],
'PATH' => ENV['PATH'],
'LD_LIBRARY_PATH' => ENV['LD_LIBRARY_PATH'],
'LANG' => ENV['LANG'],
'GL_ID' => @key_id,
'GL_PROTOCOL' => GL_PROTOCOL,
'GL_REPOSITORY' => @gl_repository,
'GL_USERNAME' => @username
}
# ...
Kernel.exec(env, *args, unsetenv_others: true, chdir: ROOT_PATH)
处理Git命令
以Pull,且调用Gitaly为例。
// bin/gitaly-upload-pack
code, err := handler.UploadPack(os.Args[1], &request)
// go/internal/handler/upload_pack.go
func UploadPack(gitalyAddress string, request *pb.SSHUploadPackRequest) (int32, error) {
# ... conn, err := client.Dial(gitalyAddress, dialOpts())
if err != nil {
return 0, err
}
defer conn.Close() ctx, cancel := context.WithCancel(context.Background())
defer cancel()
return client.UploadPack(ctx, conn, os.Stdin, os.Stdout, os.Stderr, request)
}
// go/vendor/gitlab.com/gitlab-org/gitaly/client/upload_pack.go
func UploadPack(ctx context.Context, conn *grpc.ClientConn, stdin io.Reader, stdout, stderr io.Writer, req *pb.SSHUploadPackRequest) (int32, error) {
ctx2, cancel := context.WithCancel(ctx)
defer cancel() ssh := pb.NewSSHServiceClient(conn)
stream, err := ssh.SSHUploadPack(ctx2)
if err != nil {
return 0, err
} if err = stream.Send(req); err != nil {
return 0, err
} inWriter := streamio.NewWriter(func(p []byte) error {
return stream.Send(&pb.SSHUploadPackRequest{Stdin: p})
}) return streamHandler(func() (stdoutStderrResponse, error) {
return smHandler(func() (stdoutStderrResponse, error) {
return stream.Recv()
}, func(errC chan error) {
_, errRecv := io.Copy(inWriter, stdin)
stream.CloseSend()
errC <- errRecv
}, stdout, stderr)
}
可以看到通过grpc跟gitaly server通信,获得响应之后:
# go/vendor/gitlab.com/gitlab-org/gitaly/client/std_stream.go
exited code => resp.GetExitStatus().GetValue()
stderr => stderr.Write(resp.GetStderr())
stdout => stdout.Write(resp.GetStdout())
git客户端将标准错误打印到控制台,解析标准输出作为git数据:通过远程ssh调用命令将数据打印到标准输出传输到客户端解析。
Cloning into 'gitlab-shell'...
remote: Counting objects: 5558, done.
remote: Compressing objects: 100% (2548/2548), done.
remote: Total 5558 (delta 3051), reused 5117 (delta 2754)
Receiving objects: 100% (5558/5558), 2.83 MiB | 2.72 MiB/s, done.
Resolving deltas: 100% (3051/3051), done.
Refs
GitLab Shell如何通过SSH工作的更多相关文章
- github/gitlab 管理多个ssh key
github/gitlab 管理多个ssh key 以前只使用一个 ssh key 在github上提交代码,由于工作原因,需要再添加一个ssh key在公司的 gitlab上提交代码,下面记录下配置 ...
- SSH工作过程简介和SSH协议算法集简介
SSH简介 SSH是Secure Shell(安全外壳)的简称,是一种在不安全的网络环境中,通过加密机制和认证机制,实现安全的远程访问以及文件传输等业务的网络安全协议. SSH协议采用了典型的客户端/ ...
- 全新 Mac 安装指南(编程篇)(环境变量、Shell 终端、SSH 远程连接)
注:本文专门用于指导对计算机编程与设计(尤其是互联网产品开发与设计)感兴趣的 Mac 新用户,如何在 Mac OS X 系统上配置开发与上网环境,另有<全新 Mac 安装指南(通用篇)>作 ...
- gitlab+TortoiseGit中使用SSH
1.在文件夹空白位置右键打开"Git Bash" 2.按 https://gitlab.yourhost.com/help/ssh/ssh.md 中的说明,输入命令 ssh-k ...
- centos shell编程6一些工作中实践脚本 nagios监控脚本 自定义zabbix脚本 mysql备份脚本 zabbix错误日志 直接送给bc做计算 gzip innobackupex/Xtrabackup 第四十节课
centos shell编程6一些工作中实践脚本 nagios监控脚本 自定义zabbix脚本 mysql备份脚本 zabbix错误日志 直接送给bc做计算 gzip innobacku ...
- J2EE进阶(十八)基于留言板分析SSH工作流程
J2EE进阶(十八)基于留言板分析SSH工作流程 留言板采用SSH(Struts1.2 + Spring3.0 + Hibernate3.0)架构. 工作流程(以用户登录为例): 首先是用 ...
- shell脚本批量ssh登陆主机并执行命令
shell脚本批量ssh登陆主机并执行命令 今天在客户现场遇到了这个问题,客户没有管理工具,无法批量登陆主机下发命令,几个个C段啊,让我一个一个登陆,.................. 所以写了个s ...
- 为GitLab帐号添加SSH keys并连接GitLab
https://blog.csdn.net/xyzchenxiaolin/article/details/51852333 为github帐号添加SSH keys使用git clone命令从GitLa ...
- [转+自]SSH工作原理
SSH工作原理 熟悉Linux的人肯定都知道SSH.SSH是一种用于安全访问远程服务器的网络协议.它将客户端与服务端之间的消息通过加密保护起来,这样就无法被窃取或篡改了.那么它安全性是如何实现的呢? ...
随机推荐
- bootstrap-select 下拉多选组件
<div class="form-group"> <label class="col-lg-2 col-sm-2 control-label" ...
- table添加行
需求是要实现表格的动态增加与删除,并且保留标题行和首行,找了半天jq插件,没找到合适的,所以自己写了个demo <!DOCTYPE html> <html> <head& ...
- CentOS7安装配置Bacula yum方法
参考: https://www.baidu.com/link?url=o2QIy2YZWjsJPAFJuYFhrH3nPvtyRkSe-o5Q_FqFZ5E1EMOsIOmGeKm0HAonwHOw8 ...
- android 获取Asset中Properties文件配置的键值对
1 获取 AssetManager AssetManager assetManager = context.getApplicationContext().getAssets(); 2 获取流 Str ...
- python 字典,列表,集合,字符串,基础进阶
python列表基础 首先当然是要说基础啦 列表list 1.L.append(object) -> None 在列表末尾添加单个元素,任何类型都可以,包括列表或元组等 2.L.extend(i ...
- 2016 多校联赛7 Joint Stacks (优先队列)
A stack is a data structure in which all insertions and deletions of entries are made at one end, ca ...
- 【Think in java读书笔记】序列化
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复成为原来的对象. 序列化机制能自动弥补不同操作系统之间的差异,也就是说在Wind ...
- 正则表达式的lastIndex属性
js中正则表达式的使用方式有两种,一种是正则表达式对象的方法,一种是字符串对象的方法,前者有exec(str).test(str)两个方法,后者有match(regexp).replace(regex ...
- 点击图片video全屏
<!doctype html> <html> <head> <meta charset="utf-8" /> <title&g ...
- React native Configuration with name 'default' not found.
添加插件后出现异常 FAILURE: Build failed with an exception. * What went wrong: A problem occurred configuring ...