2008年7月7日星期一

xauth

xauth 是一个很有意思的程序,也许平时不会直接用到,但是很多程序会默默的使用它为你工作,比如前面的 ssh。

提到 xauth 就不可避免的涉及到 X 的工作原理,这里仅仅讨论认证部分。比较傻瓜且很不安全的方式就是用 xhost,比较聪明的方式还是用 xauth。这里讨论后者,在后面提到前者的时候我们会将它们比较。xauth 认证靠一串 128bit 的数字,所谓的 magic cookie,正式名称为 MIT-MAGIC-COOKIE-1。这串数字由 X server 产生,一般一个 X client 需要访问 X server 根据 -display 或者 DISPLAY 变量给出的地址,查询本地 XAUTHORITY 文件(一般是 ~/.Xauthority)获得对应 magic cookie,这样才会被授权访问 X server。在 X11R6.3 以后还加入了一个新特性,就是 X11 服务器可以 on-the-fly 生成 magic cookie,并标注某些 trusted,某些为 untrusted,这样给不同的 Xclient 以不同的 cookie,就可以防止某些 X client 程序盗取键盘按键序列等敏感信息。

一般来说 X server 启动的时候会产生 magic cookie,我们只需要将合适的 cookie 分发到需要调用 X clients 的主机上,那些主机的 X clients 就可以出现在 X server 上了。比较常用的 magic cookie 的生成程序来自于 mcookie。

xauth 就是用来操纵 XAUTHORITY 文件的程序,使用它会方便分发 magic cookie。一方面可以直接用 xauth command 来执行相关的命令,另一方面也可以在命令行上通过 xauth 进入交互环境。命令不多,有 n 开头和非 n 开头的命令,n 表示输出用数字,在某些情况更加合适一些,如某些传输情况。由于 XAUTHORITY 文件访问可能被锁定,所以有 -b 选项尝试解锁,-i 选项忽视锁。下面是常用命令,
  • add displayname protocolname hexkey:添加一个 display 和对应的 magic key,中间的 protocolname 可以用 . 表示 magic cookie。
  • generate displayname protocolname [trusted|untrusted]:和 add 不同在于是自动连接 X server 获得 magic cookie。
  • [n]extract filename displayname:把指定 displayname 的 magic cookie 写入指定文件,如果文件名为 - 则是标准输出。
  • [n]list [displayname...]:显示 XAUTHORITY 里面的 magic cookies。
  • [n]merge [filename...]:把 filename 和现在的融合在一起。
  • remove displayname:删除某项。
  • source filename:执行 xauth 命令序列
  • info:显示相关信息
  • exit:保存并退出。
  • quit:不保存但退出。

我们可以分析一下 ssh 如何实现 X11 forwarding,首先设置 DISPLAY 变量,并将其关联到自己的 tunnel,然后需要把远程主机 XAUTHORITY 更改:
if read proto cookie && [ -n "$DISPLAY" ]; then
if [ ‘echo $DISPLAY | cut -c1-10‘ = ’localhost:’ ]; then
# X11UseLocalhost=yes
echo add unix:‘echo $DISPLAY |
cut -c11-‘ $proto $cookie
else
# X11UseLocalhost=no
echo add $DISPLAY $proto $cookie
fi | xauth -q -
fi
可见,如果是 localhost 开始(已经使用了 ssh)取 localhost: 后面的数字前面加上 unix:,否则直接使用。这个符合语句通过 echo 把命令显示并由管道输入给 xauth。有些不使用 ssh 的环境,如 telnet 或者 rsh,同时 home 又不公用,就需要一边 xauth list 一边 xauth merge 了。

相关程序:
xhost 来自于 x11-server-utils。
mcookie 来自于 util-linux。

2008年7月5日星期六

openssh

Linux 中最常用的几个包,openssh 怕是其中最常用的远程登录、传输文件和穿透网络的工具了。这里主要介绍 openssh-server 和 openssh-client。

ssh 就是 secure shell 的缩写,当初设计目的是为了避免使用具有安全隐患的 telnet 服务。众所周知,telnet 协议下所有数据是明文传输的,这意味着如果有用户想通过网络访问某台主机,他的用户名和密码都将可能被网络上别有用心的人捕获到。类似的传统的 rsh 系列,仅仅通过 .rhost 文件认证,数据也是明文传输,这会极大的考验系统管理的安全性。ssh 的出现就是为了替代 telnet 和 rsh,但是同时它做得更好。openssh 是 ssh 协议的一个实现,它提供了服务器和客户端程序,并在多个类 Unix 环境下都可以编译执行。Windows 里面比较常见的 ssh 客户端是 PuTTY。

我们先要了解一下 ssh 实现的大致思想。ssh 服务器和客户端支持对称和不对称加密算法,它们通过密钥交换使得可以用对方的公钥加密一个具有时效性的随机产生对称加密密钥作为两者通信信道的加密手段,同时还支持用户通过传统密码认证登录系统,也支持使用不对称密钥认证。由于加密隧道的存在,这也使得我们可以利用这个把 X11 client 的请求 forward 到客户端,这样实现远程启动图形界面的程序;当然,隧道还可以用于传输文件;另外一个重要作用是加密隧道为我们穿透私有网络提供了桥梁。

下面我们看看 openssh-server 里面的东西:
/usr/sbin/sshd
/usr/lib/openssh/sftp-server
/usr/share/man/man5/sshd_config.5.gz
/usr/share/man/man8/sftp-server.8.gz
/usr/share/man/man8/sshd.8.gz
/usr/share/doc/openssh-client
/var/run/sshd
/etc/init.d/ssh
/etc/network/if-up.d/openssh-server
/etc/pam.d/sshd
/etc/default/ssh
/usr/lib/sftp-server
/usr/share/man/man5/authorized_keys.5.gz
/usr/share/doc/openssh-server
可见 server 程序是 sshd,配置文件是 /etc/ssh/sshd_config。第一次运行 sshd 的时候会产生服务器的密钥,也放在/ etc/ssh 目录下,一般是 RSA 和 DSA 密钥两对(一个公钥、一个私钥)。sshd 每接受一个连接会 fork 出来一个进程处理对应的请求,当接受到 SIGUP 的信号会重新读取自己的配置文件。sshd 的命令行参数请见 man page,这可以方便定制从 /etc/init.d/ssh 启动的参数。

在 ssh 协议 1 里面,服务器仅支持 RSA,它产生一个一般只有 768bit 的额外密钥(服务器打开的时候就产生,并且使用过一定时间,如每小时,后就会重新生成,不保存在磁盘上)。当客户端连接的时候,服务器将自己的 RSA 公钥和这个额外的密钥一起发送给对方,客户端收到后通过比较自己知道的对方 RSA 公钥和发送过来的 RSA 公钥确认的确是目的主机,然后产生一个 256bit 随机数,把该随机数用对方的 RSA 公钥和额外密钥加密发回到服务器,服务器解开此随机数后就可以和客户端使用该随机数作为对称加密的密钥了,如 Blowfish、3DES 。而在 ssh 协议 2 里面,密钥交换是通过 Diffie-Hellman 方法,会话的完整性通过数字签名算法(如 md5、sha 等保证)。

完成这部分后就是进行常规的用户认证工作,这部分现在多交给 PAM 完成。登录后,sshd 将会做如下事情,
  1. 如果从 tty 登录,没有声明执行的命令,则显示上次登录时间、motd(根据 ~/.hushlogin 决定)。
  2. 记录登录时间。
  3. 如果有 /et/nologin 则显示并退出,root 除外。
  4. 将权限变更到普通用户。
  5. 建立基本环境。
  6. 执行 ~/.ssh/environment,根据配置文件是否允许用户自定义。
  7. 切换到用户目录。
  8. 执行 ~/.ssh/rc 或者 /etc/ssh/sshrc。
  9. 执行 shell。
SSHRC 存在的目的是为了允许登录前准备一些东西,比如用户的主目录什么的,另外 X11 forwarding 功能的实现也是在这一步,通过 xauth 认证。另外有 ~/.ssh/authorized_keys 这个文件是所有允许使用公钥认证的列表,其格式对协议 1 为 options, bits, exponent, modulus, comment,协议 2 为 options, keytype, base64-encoded key, comment,其中 options 不允许插入空格(除非引号内),可以包括 command="commands"(默认为 SSH_ORIGINAL_COMMAND 变量),environment="NAME=VALUE"(设置环境变量),from="pattern list"(设置访问地址范围),no-agent-forwarding(禁止 agent forwarding),no-port-forwarding(禁止 port forwarding),no-pty(不打开 pty),no-user-rc(不使用 ~/.ssh/rc),no-X11-forwarding(取消 X11 forwarding),permiteopen="host:port"(限制 -L 打开的本地端口),tunnel="n"(创建一个 tun 设备)。options 之间用逗号隔开。SSH_KNOWN_HOSTS 在 /etc/ssh 和 ~/.ssh/known_hosts ,一般用空格分开下面的域 hostnames, bits, exponent, modulus, comment。hostnames 一般有明文记录的,现在多数默认是存 hash 值,这使得每一个 host 对应的公钥信息记录在本地,一旦对方密钥不符合可以拒绝连接。在 /etc/ssh/moduli 文件中储存的是 Diffie-Hellman 算法的相关分组信息。

配置文件格式很简单,如 Port 指定端口,ListenAddress 指定监听地址(有多个地址的主机),Protocol 指定版本(现在多为 2),HostKey 指定密钥位置,前面所说的几种 forwarding 可以在此禁止,一般禁止使用 ~/.rhosts 和 ~/.shosts 这些认证方式,还可以选择是否允许密码或者密钥。

对于 openssh-client 而言,东西多很多,
/usr/bin/ssh-add
/usr/bin/ssh-agent
/usr/bin/ssh-keyscan
/usr/bin/ssh
/usr/bin/ssh-vulnkey
/usr/bin/ssh-argv0
/usr/bin/ssh-keygen
/usr/bin/scp
/usr/bin/sftp
/usr/bin/ssh-copy-id
/usr/lib/openssh/ssh-keysign
/usr/share/man/man5/ssh_config.5.gz
/usr/share/man/man5/moduli.5.gz
/usr/share/man/man8/ssh-keysign.8.gz
/usr/share/man/man1/ssh-agent.1.gz
/usr/share/man/man1/ssh-keyscan.1.gz
/usr/share/man/man1/ssh-add.1.gz
/usr/share/man/man1/ssh-vulnkey.1.gz
/usr/share/man/man1/sftp.1.gz
/usr/share/man/man1/ssh-argv0.1.gz
/usr/share/man/man1/ssh.1.gz
/usr/share/man/man1/ssh-copy-id.1.gz
/usr/share/man/man1/scp.1.gz
/usr/share/man/man1/ssh-keygen.1.gz
/usr/share/lintian/overrides/openssh-client
/usr/share/doc/openssh-client/README.Debian.gz
/usr/share/doc/openssh-client/OVERVIEW.gz
/usr/share/doc/openssh-client/NEWS.Debian.gz
/usr/share/doc/openssh-client/README.dns
/usr/share/doc/openssh-client/README.compromised-keys.gz
/usr/share/doc/openssh-client/README.tun.gz
/usr/share/doc/openssh-client/copyright
/usr/share/doc/openssh-client/changelog.gz
/usr/share/doc/openssh-client/changelog.Debian.gz
/usr/share/doc/openssh-client/README
/usr/share/doc/openssh-client/faq.html
/etc/ssh/ssh_config
/etc/ssh/moduli
/usr/bin/slogin
/usr/share/man/man1/slogin.1.gz
这里主要解释每一个小模块的作用,如 ssh 是客户端命令行程序,scp 用于把本地和远程主机上文件进行复制,sftp 提供了一个类似于 ftp 的 TUI,ssh-keygen 用于产生密钥,ssh-agent 也就是所谓的 agent(多用于多主机之间的穿梭,避免将私钥放置到不可靠主机上),ssh-add 用于向 agent 添加一个需要管理的密钥,ssh-keyscan 用于收集公钥(主要用于建立 SSH_KNOWN_HOSTS 文件)。系统客户端配置文件在 /etc/ssh/ssh_config 而个人配置文件在 ~/.ssh/config。

关于 ssh 的应用非常多,这里仅仅提一点思路,具体调用参数请参考 man page:
  • 对于具有多个网络设备的主机,可以用 -b 选择合适的地址进行连接。
  • 如果直接 ssh user@host 则会登录,如果 ssh user@host command 则执行命令后退出。
  • 通过管道可以把一台机器上的输出导入到另一台机器上,如 tar cv DIR | ssh host "cat >DIR.tar" 的作用就是把本地 DIR 目录做成一个 tar ball,输出到标准输出(因为 tar 没有 f 选项),管道把这个命令的输出与后面的输入连接,我们知道 cat 就是把标准输入显示到标准输出,其输出重定向 > 使得最后写入到文件 DIR.tar 中。
  • ssh 能很方便的把 X11 client 程序的请求 forward 到本地,因此只要本地打开了 X server,则可以显示远程 X client,前面 sshd_config 中我们看到 X11DisplayOffset 10,这使得连接到该主机的 ssh 客户端的 DISPLAY 变量指向 localhost:10+,这里的 localhost 指 client 所在主机,后面 10+ 指从 10 开始计数,当有多个 client 连接时,这个计数器会增加。
  • 通过 ssh 可以建立隧道序列,如从 A 到 B 建立 ssh 连接,然后从 B 连接到 C,这时只要每个连接都打开了 X11 forwarding,那么在 C 上运行 X11 程序,X11 client 的请求将会从 C -> B -> A 最终显示在 A 上,这对一般的隧道也是如此。隧道的串联极大的增强了 ssh 的穿透性。
  • 端口映射很有用,-L [bind_address:]port:host:hostport 将本地打开一个端口,映射到 host:hostport,这有什么用呢?比如本地是 A,远程是 B,那么映射的 host:hostport 是 B 可以访问的,但 A 没有直接访问的权限(如私有网络等)。这样 A 通过访问本地 port 就相当于访问了 host:hostport。类似还有一个 -R [bind_address:]port:host:hostport,这会在远程主机上打开 port 端口,将其请求转到本地网络上的 host:hostport。这里有一个比较复杂的例子。
  • 方便开设代理服务器。要知道上面的办法可以应付一些问题,如 ssh、telnet 端口的映射,但是对于比较特殊的如 ftp 等就无能为力了,这主要是因为 ftp 分控制信道端口和实际数据传输端口,仅仅打开一个端口进行映射是行不通的,同时也不可能预知究竟会用哪个端口。-D [bind_address:]port 将会在本地打开一个 SOCKS 代理,这时只需要将本地的程序设置使用该代理地址(如 127.0.0.l:port),ssh 将会自动开设需要的端口。默认情况这个端口只有本地能访问,可以用 -g 向其他机器打开使用权限。
  • -e 设置逃脱键,怎么逃脱?如 A -> B,建立连接后,shell 的控制字符 C-z 被远程 shell 拦截,因此如果需要 suspend 该连接直接 C-z 是没有作用的,默认的 escape 字符是 ~,按 ~C-z 可以达到该目的。其他的如 ~# 列出所有的连接,~C 获得命令行(添加 port forwarding 等)
  • 建立 tun,-w local_tun[:remote_tun],这使得本地出现一个 tun 设备,通过更改路由,就可以实现最简单的 VPN 了。需要 root 权限。
  • 常用选项在 ~/.ssh/config 里面写,如打开 X11 forwarding 或者 agent forwarding 等,参考 man page 里面哪些选项可以用。

下面介绍如何使用密钥,最直接就是 ssh-keygen 产生对应的密码了,一般使用 -t 指明密钥类型,如 rsa 或者 dsa,为主机产生的密钥不应带有口令,如果不希望每次输入口令,可以空缺下来。-H 会把 SSH_KNOWN_HOSTS 的主机名 hash 掉。-p 修改原来的口令,如果忘掉了口令就只能重新产生并覆盖了。有一些比较有意思的应用是 ssh 和 cvs 或者 subversion 联合起来,使用密钥而不是密码可以极大的减少每次输入密码的痛苦,不过这也将管理密码的问题转移到了密钥上。

一般说来,密钥放在自己可以处理的地方,如自己有完全控制能力的笔记本,而不放在比较开放的环境,自己没有 root
的控制权或者有多人拥有这种权利。这产生了一个严重的问题就是会限制密钥的应用范围。ssh-agent 的出现算是缓解了这个矛盾。使用 ssh-agent 的基本方法是在启动别的程序之前首先启动它,比如可以在 X 的 .xinitrc 里面用它启动 WM,这样 ssh-agent 会为后面所有的程序设置好几个环境变量,ssh 执行的时候会优先查看这些变量寻找对应的 ssh-agent,只要通过 ssh-add 把需要的密钥加入到 ssh-agent 就可以万事无忧了。某些桌面环境,如 GNOME 会自动启用 ssh-agent,我们所要做的可能就是 ssh-add 一下。下面是一个典型的应用实例,
$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-IIXzU30554/agent.30554; export SSH_AUTH_SOCK;
SSH_AGENT_PID=30555; export SSH_AGENT_PID;
echo Agent pid 30555;

这种情况下这些环境变量必须手工设定,把程序输出复制一下就可以了。然后可以通过 ssh-add -l 查看已经加入的密钥,如果没有则通过 ssh-add 添加。之后通过 ssh B,从本地到 B(注意 B 的 ~/.ssh/authorized_keys 需要加入对应的公钥),再从 B 到 C(C 的 ~/.ssh/authorized_keys 需要配置),但是 B 上不需要有 A 上的私钥。这是因为登录 B 后 ssh 把 A 上 ssh-agent 通讯的 sock 通过环境变量带到 B,从 B 带入 C,当 C 上主机要求密钥认证时经过该地址和 A 上的 ssh-agent 协商即可。

为了方便把本地的 rsa 公钥安装到远程机器上,一般可以用 ssh-copy-id 命令,其实手工编辑对应的 authorized_keys 也是可以的。有一些密钥已经被认为十分脆弱,ssh-vulnkey 可以检测出来,如可以用 ssh-keyscan 获得对应的 RSA 公钥通过管道灌给 ssh-vulnkey。ssh-argv0 是这样用的,把主机名 ln -s /usr/bin/ssh-argv0 FucQ,然后该连接放到 PATH 中就可以直接输入主机名进行 ssh 连接了。还有两个包 openssh-blacklist 和 openssh-blacklist-extra 含有出现过问题的 ssh 公钥,供 ssh-vulnkey 使用。