对很多开发者,尤其是手握几台甚至几十台机器的同学而言,登录到远程机器处理事务应该是家常便饭。如果你也是其中一员,悄悄问一句,你平时是如何记住一堆帐号、机器地址以及一些附加登录选项的呢?或许你也是这么干的:
倘若你有一个远程服务器,地址为 test.server.com
, 防止人为攻击,你将 ssh 的端口号从 22 改成了 8892,当你需要登录到这台机器时,需要这么做:
➜ ~ ssh YOURNAME@test.server.com -p 8892
password: *******
尚好。如果你先前已经将已经注册了一个公钥/私钥对,并且正确的部署到了远程机器,你可以省却输入密码这个环节(推荐学习 ssh-copy-id
命令)。
为了可以再懒一点,索性将这一串代码添加一个 alias:
➜ ~ alias test='ssh YOURNAME@test.server.com -p 8892'
➜ ~ test
一条 test
命令即可让你登录到服务器,对于只有一两台远程机器的你,应该算是特别便捷了。
SSH 连接建立之前,会在系统中寻找它的配置,一般有两个位置。
/etc/ssh/ssh_config
这里是对所有用户适用的全局配置 ~/.ssh/config
或者 $HOME/.ssh/config
这是用户的个人配置,这些配置会覆盖全局配置 注意:一般来说,我们给 ~/.ssh
文件夹赋予的权限为 0700
。
配置格式比较简单,以下配置等同于上面我们的登录命令设置:
Host test
HostName test.server.com
User YOURNAME
Port 8892
可以是 param value
也可以为 param=value
,其中 param 对大小写不敏感,value 对大小写敏感。
使用 ssh test
即可完成登录,当我们有稍微麻烦点配置的时候,如数据库的 3306 端口对外不开放,可以开放另一个接口,然后内部跳转到 3306,使用命令行的写法是:
ssh -f -N -L 8999:127.0.0.1:3306 test@database.server.com
大串的命令行,过多的参数,实在是有点不好记。而在 config 文件中的配置就一目了然:
Host test
HostName test.server.com
User YOURNAME
LocalForward 8999 127.0.0.1:3306
当你有多台远程终端的时候,config 文件的优势就更加明显了:
# serverlist
Host list
HostName *.serverlist.com
User YOURNAME
IdentityFile ~/.ssh/serverlist.com.key
# personal server
Host personal
HostName proxy1.barretlee.com proxy2.barretlee.com
User barretlee
IdentityFile ~/.ssh/proxy.barretlee.com.key
# school
HostName 222.20.74.89
User school
LocalForward 8999 127.0.0.1:3306
IdentityFile ~/.ssh/school.key
# and so on.
这里常用的的 param 也不是很多:
Host
,SSH 连接名 HostName
,如上所示,可以是通配符,可以是 IP,也可以是域名等 User
,登录的用户名 IdentifyFile
,version 1 协议下默认是 ~/.ssh/identify
,version 2 协议下,默认是依次匹配: ~/.ssh/id_dsa
, ~/.ssh/id_ecdsa
, ~/.ssh/id_rsa
,还有 version 2 兼容模式。 LocalForward
端口的内部跳转 Port
,端口设置,默认 SSH 的端口是 22 Protocal
,协议版本号,1 或者 2 向仓库 push 代码之前,我们都会做一番设置,简单归纳为如下几步:
barretlee/test.git
ssh-keygen -t rsa -C "barret.china@gmail.com"
ssh-add id_rsa
,部分终端中还需要手动开启 ssh-agent ssh -T git@git.github.com
突然有一天,你又注册了一个帐号 xiaohuzige,也走了一遍上面的流程,发现死活也连接不上,服务器总是提示: You have no permission to access this repo
,这是怎么回事呢?
这个时候,你可以输入这个命令,看看是哪个环节出了问题:
➜ ~ ssh -vT git@github.com
OpenSSH_6.9p1, LibreSSL 2.1.7
debug1: Reading configuration data /Users/barretlee/.ssh/config
debug1: /Users/barretlee/.ssh/config line 2: Applying options for *
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 20: Applying options for *
debug1: /etc/ssh/ssh_config line 102: Applying options for *
debug1: Connecting to github.com [192.30.252.128] port 22.
debug1: Connection established.
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_rsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /Users/barretlee/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_6.9
debug1: Remote protocol version 2.0, remote software version libssh-0.7.0
debug1: no match: libssh-0.7.0
debug1: Authenticating to github.com:22 as 'git'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: server->client chacha20-poly1305@openssh.com <implicit> none
debug1: kex: client->server chacha20-poly1305@openssh.com <implicit> none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ssh-rsa SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8
debug1: Host 'github.com' is known and matches the RSA host key.
debug1: Found key in /Users/barretlee/.ssh/known_hosts:4
Warning: Permanently added the RSA host key for IP address '192.30.252.128' to the list of known hosts.
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: Roaming not allowed by server
debug1: SSH2_MSG_SERVICE_REQUEST sent
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey
debug1: Next authentication method: publickey
debug1: Trying private key: /Users/barretlee/.ssh/id_rsa
debug1: Trying private key: /Users/barretlee/.ssh/id_dsa
debug1: Trying private key: /Users/barretlee/.ssh/id_ecdsa
debug1: Trying private key: /Users/barretlee/.ssh/id_ed25519
debug1: No more authentication methods to try.
Permission denied (publickey).
不加 v 参数,输出的内容很简洁:
➜ ~ ssh -T git@github.com
Warning: Permanently added the RSA host key for IP address '192.30.252.129' to the list of known hosts.
Permission denied (publickey).
好吧,我知道你看不懂上面一长串的内容,也没心情看下去,但是你可以把焦点落到重复的那几段:
debug1: ...
debug1: xxx, No such file or directory
debug1: ...
debug1: Trying private key: /Users/barretlee/.ssh/xxx
debug1: ...
之前我们提到了 Protocal Version 1 和 Version 2,不同的版本号,默认的私钥地址不一样,所以程序会不断去尝试寻找默认的地址,如果没找到,最后会提示,授权失败,禁止访问。
我们可以在 push 代码之前,使用 ssh-agent 来管理私钥的 session,如:
➜ ~ ssh-add .ssh/coding_barretlee
Identity added: ~/.ssh/coding_barretlee (coding_barretlee)
➜ ~ ssh-add .ssh/github_barretlee
Identity added: ~/.ssh/github_barretlee (github_barretlee)
那么当程序寻找私钥的时候,就会优先到 ssh-agent 添加的 session 中寻找。session 的生命周期不是很长,当你重启电脑之后它就没了。如果你有遇到了 Permission Denied
的提示,请重新执行 ssh-add 命令。
当你在某个 Git 服务器上有多个帐号的时候,可能某个帐号总是提示: Permission Denied
。拿 coding.net
上来说,我有两个账户,一个是 barretlee,另一个是 taobaofed,由于先前我一直用的 barretlee 账户,后来者 taobaofed 的代码死活推不上去。反反复复地检查配置,反反复复地检查上传的公钥,反反复复地使用 ssh -T git@git.coding.net
测试,没看到哪里不对,然而 taobaofed 的代码就是推不上去,这是怎么回事呢?
当然,上面 SSH 介绍了那么多,其简明易懂的配置在这里也是可以用上的:
### default for all ##
Host *
ForwardAgent no
ForwardX11 no
ForwardX11Trusted yes
User nixcraft
Port 22
Protocol 2
ServerAliveInterval 60
ServerAliveCountMax 30
## barretlee coding ##
Host coding-barretlee
HostName git.coding.net
User barretlee
IdentityFile ~/.ssh/coding_barretlee
## taobaofed coding ##
Host coding-taobaofed
HostName git.coding.net
User taobaofed
IdentityFile ~/.ssh/coding_taobaofed
使用这种配置,我们可以避免使用 ssh-agent 添加 session 操作。可是,我就是按照上面的方式配置的呀,依然不行!
这里的问题在于,我们的 User 项不正确,每次推送代码的时候,git 会读取上次的的 User 配置,而我的配置是 barretlee,那么下次提交代码的时候虽然 IdentityFile 用对了,但是 User 不是 taobaofed,所以死活也推不动代码。
解决的方案很简单,如果在下载代码之前就已经设置好了两个帐号,你可以通过如下命令克隆代码:
➜ ~ git clone git@coding-taobaofed:taobaofed/blog.git
如果是在已有的仓库中,其默认 origin 配置会是: https://git.coding.net/taobaofed/blog.git
或者 git@git.coding.net:taobaofed/blog.git
,你可以将仓库下的 .git/config
文件修改下:
[remote "origin"]
url = git@coding-taobaofed:taobaofed/blog.git
fetch = +refs/heads/*:refs/remotes/origin/*
防止克隆其他仓库代码也出现问题,我们可以将 ~/.ssh/config
稍加修改:
## barretlee coding ##
Host git.coding.net coding-barretlee
HostName git.coding.net
User barretlee
IdentityFile ~/.ssh/coding_barretlee
## taobaofed coding ##
Host coding-taobaofed
HostName git.coding.net
User taobaofed
IdentityFile ~/.ssh/coding_taobaofed
那么,通过正常的 clone(如 git clone git@git.coding.net:barretlee/blog.git
)就不会出现 Permission Denied
的提示了。
撰写本文之前,我已经踩坑三小时,花了三个小时,摸索出来这么些东西,感觉以后妈妈再也不用担心我的 Git/SSH 的配置问题了。
最主要的手段依然是通过 ssh -vT
查看 SSH 交互过程中出现了什么障碍,debug 信息还是很有考究价值的!