身份验证和授权

注意:在文档中,我们建议创建一个单一的部署用户,并在团队成员之间共享。如果您知道为什么这是一个坏主意(或者为什么在某些情况下这可能违反您所在司法管辖区的规定),我们假设您非常了解如何使用组、umask 和 setgid 位来使此操作对团队成员的唯一登录可靠地工作。

要创建此部署用户,我们假设已经执行了以下操作

root@remote adduser deploy
root@remote $ passwd -l deploy

第一行创建了一个完全标准的用户,它有一个主目录,我们稍后会用到,并且有一个 shell,因此它可以登录。这需要在您环境中的每一台服务器上完成。

第二行锁定了用户,它将用户的密码更改为一个不可键入的字符串,确保用户没有可用于登录的密码。

身份验证

我们需要自动化的、无提示的身份验证有两个地方

  1. 从我们的工作站/笔记本电脑/等设备到我们的服务器。我们使用SSH 密钥来实现这一点,理想情况下,使用密钥代理进行密码保护。
  2. 从我们的服务器到代码仓库。我们这样做是为了让我们的服务器能够从 Github 或类似的平台上检出我们的应用程序代码并安装到服务器上。这通常使用SSH 代理转发、HTTP 认证或部署密钥来完成。

1.1 工作站到服务器的 SSH 密钥

SSH 密钥是一种机制,它允许将密钥的公钥部分放置在服务器上,当我们想要认证到该服务器时,我们的 SSH 客户端使用该密钥的私钥部分与服务器进行协商,如果密钥正确,我们就可以登录。

注意:如果您使用的是 Windows,您可以使用 Git for Windows 生成 ssh 密钥。为此,请按照以下步骤操作

  1. 安装 Git for Windows.
  2. 打开“Git Bash”并按照以下说明操作,始终在 Git Bash 提示符内进行。
  3. 激活 ssh-agent:$ eval "$(ssh-agent -s)"

注意:如果您想使用 Putty 工具 通过 ssh 密钥连接到远程服务器(从 Windows),那么您需要使用 puttygen 工具生成 ppk 文件。

提示:如果您团队中有多个开发人员,他们都应该将他们的公钥添加到 deploy 用户的 authorized_keys 文件中,这样如果有人离职或被解雇,您可以从该文件中删除他们的密钥,而其他人可以继续工作!

然后我们需要创建密钥。

me@localhost $ ssh-keygen -t rsa -C 'me@my_email_address.com'

系统会提示您输入密码,这是可以的。输入一个密码并妥善保管。此密码确保即使您的计算机被盗,人们仍然需要密码才能访问您的密钥,从而访问您的服务器。

为了避免每次使用密钥时都必须输入此密码,大多数操作系统都具有密钥代理的概念。此密钥代理在使用之间安全地存储 SSH 密钥,通常在给定时间段内第一次需要密钥时,SSH 代理会加载密钥,提示您输入密码,然后密钥代理会记住密钥一段时间(在 OSX 上,它往往是无限的,在 Linux 上,这可能从 15 分钟到更长时间不等)。

我们可以通过运行 ssh-add -l 来查看 SSH 代理中加载了哪些密钥

me@localhost $ ssh-add -l
2048 af:ce:7e:c5:93:18:39:ff:54:20:7a:2d:ec:05:7c:a5 /Users/me/.ssh/id_rsa (RSA)

如果您没有看到任何列出的密钥,您可以简单地运行 ssh-add

me@localhost $ ssh-add
Identity added: /Users/me/.ssh/id_rsa (/Users/me/.ssh/id_rsa)

通常,ssh-add 会在您添加密钥时询问您密码。

注意:虽然使用 SSH 代理不是强制性的(可以简单地使用无密码密钥,并依赖 SSH 来查找密钥并进行交换),但使用 SSH 代理可以提高安全性,因为我们可以使用带密码的密钥而无需在每次使用时都提示。它允许我们使用同一个密钥通过服务器访问存储库,而无需创建额外的身份。

此时,密钥已加载到代理中,我们需要将密钥的公钥部分放入每个远程服务器上的一个名为 /home/users/deploy/.ssh/authorized_keys 的文件中,要获取该文件的内容,我们可以向本地密钥代理请求它已加载的密钥的公钥部分。

me@localhost $ ssh-add -L
ssh-rsa jccXJ/JRfGxnkh/8iL........dbfCH/9cDiKa0Dw8XGAo01mU/w== /Users/me/.ssh/id_rsa

当您运行它时,这将更长,我剪掉了输出,因为它看起来很糟糕。

这一行,作为一行,需要将其发送到远程服务器并添加到 deploy 用户的 ~/.ssh/authorized_keys 文件的单独一行中。然后,该文件需要更改为权限模式 0600(所有者读写,组无,其他无),在 ~/.ssh 目录中,该目录需要权限 0700(所有者读写执行,组无,其他无)。

如果您使用的是 Linux,通常存在一个命令 ssh-copy-id 可以简化此过程,否则工作流程类似于

me@localhost $ ssh root@remote
root@remote $ su - deploy
deploy@remote $ cd ~
deploy@remote $ mkdir .ssh
deploy@remote $ echo "ssh-rsa jccXJ/JRfGxnkh/8iL........dbfCH/9cDiKa0Dw8XGAo01mU/w== /Users/me/.ssh/id_rsa" >> .ssh/authorized_keys
deploy@remote $ chmod 700 .ssh
deploy@remote $ chmod 600 .ssh/authorized_keys

记住:这需要在您要使用的每个服务器上完成,您可以对每个服务器使用相同的密钥,但建议每个开发人员只使用一个密钥。私钥之所以被称为私钥是有原因的!

如果我们正确地完成了所有操作,我们现在应该能够执行以下操作

me@localhost $ ssh deploy@one-of-my-servers.com 'hostname; uptime'
one-of-my-servers.com
19:23:32 up 62 days, 44 min, 1 user, load average: 0.00, 0.01, 0.05

这应该在无需输入 SSH 密钥的密码或提示您输入 SSH 密码(deploy 用户根本没有密码)的情况下发生。

验证这是否对所有服务器都有效,并将您的私钥保存在安全的地方。如果您与多个团队成员合作,通常需要收集所有人的公钥,实际上,如果您的团队已经使用 SSH 密钥访问 Github,您可以在以下 URL 处访问任何用户的 SSH 密钥

  • https://github.com/theirusername.keys

这可以使将用户的密钥放到服务器上变得更加容易,因为您可以简单地使用 curl/wget 将每个用户的密钥直接从 Github 放入服务器上的授权密钥文件。

如果您的服务器无法直接访问,并且您需要使用 SSH ProxyCommand 选项,则应执行以下操作

require 'net/ssh/proxy/command'

set :ssh_options, proxy: Net::SSH::Proxy::Command.new('ssh mygateway.com -W %h:%p')

# OR

server 'internal-hostname',
  ssh_options: {
    proxy: Net::SSH::Proxy::Command.new('ssh mygateway.com -W %h:%p'),
  }

1.2 从我们的服务器到仓库主机

在确定了从工作站到服务器的访问权限后,还有另一个跳跃需要解决,即让部署用户自动访问代码仓库。 选项按优先级排序

1.2.1 SSH 代理转发

由于我们已经设置了 SSH 代理,我们可以使用 SSH 的代理转发功能,使此密钥代理可用于进一步的跳跃。 简而言之,我们可以使用我们自己的 ssh 密钥从服务器到 Github 进行身份验证。

以下是如何检查它是否有效,首先获取仓库的 URL

me@localhost $ git config remote.origin.url
git@github.com:capistrano/rails3-bootstrap-devise-cancan.git

这里我们列出了我们对 rails3-bootstrap-devise-cancan 仓库的私有(用于测试目的)分支,该分支从 Rails 示例和教程项目中分叉出来。

我们可以尝试通过我们的服务器访问仓库,方法如下

# List SSH keys that are loaded into the agent
me@localhost $ ssh-add -l
# Make sure they key is loaded if 'ssh-add -l' didn't show anything
me@localhost $ ssh-add
me@localhost $ ssh -A deploy@one-of-my-servers.com 'git ls-remote git@github.com:capistrano/rails3-bootstrap-devise-cancan.git'

我们首先检查代理是否已加载密钥。 如果没有,我们只需加载它并在提示时输入密码。

最后,我们使用 Git 的 ls-remote 列出远程对象,这与 Capistrano 在尝试部署之前内部执行的检查完全相同。 -A 选项可能需要或不需要在您的系统上使用,值得尝试两种方式,以便了解您的系统默认情况下如何处理代理转发。

如果您收到错误“主机密钥验证失败”。 登录到您的服务器并以部署用户身份运行命令 ssh git@github.com 将 github.com 添加到已知主机列表。

来自 SSH 文档

-A  Enables forwarding of the authentication agent connection.  This can also be
   specified on a per-host basis in a configuration file.

   Agent forwarding should be enabled with caution.  Users with the ability to
   bypass file permissions on the remote host (for the agent's UNIX-domain
   socket) can access the local agent through the forwarded connection.  An
   attacker cannot obtain key material from the agent, however they can perform
   operations on the keys that enable them to authenticate using the identities
   loaded into the agent.

简单来说,你不应该将 SSH 代理转发到你不信任管理员的机器上,因为他们可以覆盖系统上的权限,并使用你的密钥,就好像他们是你一样。也就是说,如果你不信任你的服务器管理员,也许他们不应该访问你的服务器!

1.2.2 HTTP 身份验证

在 HTTP 身份验证的情况下,**请务必使用 HTTPS**,否则你的密码将以明文形式通过网络发送,这取决于你的主机网络基础设施,这可能是非常糟糕的消息。

通常,当我们尝试使用 Github 的 https 方法列出我们的远程对象时,系统会提示我们输入用户名和密码。

1.2.2.1 使用常规用户名/密码
me@localhost $ git ls-remote https://github.com/capistrano/rails3-bootstrap-devise-cancan.git
Username for 'https://github.com': myownusername
Password for 'https://capistrano@github.com':

这种挑战响应提示不适合自动化操作,因此有两种方法可以解决这个问题,具体取决于你的服务器的主机操作系统。第一种方法是使用 netrc 文件,我们不会讨论它,因为 netrc 是一个全局文件,不适合安全使用。

另一种机制,也是**非常**重要的一点,就是始终使用 HTTPS 而不是普通的 HTTP,将用户名和密码嵌入到 URL 中,注意,如果你的密码包含特殊字符,这种方法将无法正常工作。

me@localhost $ git ls-remote https://capistrano:ourverysecretpassword@github.com/capistrano/rails3-bootstrap-devise-cancan.git
3419812c9f146d9a84b44bcc2c3caef94da54758HEAD
3419812c9f146d9a84b44bcc2c3caef94da54758HEADrefs/heads/master

密码的更大问题是,无论是在 URL 中内联,还是输入到 netrc 文件中,密码都会访问**你的整个 Github 帐户**,而不仅仅是一个单独的仓库。

1.2.2.2 使用 OAuth 个人 API 令牌

这种机制仍然可以访问你能够访问的**所有仓库**,但在 Github,他们最近推出了一项名为 个人 API 令牌 的功能,它允许你执行以下操作:

me@localhost $ git ls-remote https://XXXX:@github.com/capistrano/rails3-bootstrap-devise-cancan.git
3419812c9f146d9a84b44bcc2c3caef94da54758HEAD
3419812c9f146d9a84b44bcc2c3caef94da54758HEADrefs/heads/master

其中 XXXX 是一个个人 API 令牌,因此

Github Personal API Token Page

1.2.3 部署密钥

部署密钥是 Github 和其他一些平台的一项功能,它允许你为 Github 和服务器之间的连接生成**第二组** SSH 密钥。

在这种情况下,有点奇怪的是,公钥被上传到仓库主机,而私钥必须复制到你要部署的每个服务器上。

Github 有一个非常棒的关于这方面的指南,其中大部分(不出所料)与上面的 SSH 密钥说明重叠。

授权

本主题的第二部分是我们的部署用户需要被授权在服务器上的部署目录中工作。这意味着我们需要能够工作,理想情况下无需使用 sudo(默认的 Capistrano 食谱都不需要 sudo),或者对于您的自定义食谱,您需要配置无密码 sudo。配置 sudo 以在某些情况下授予某些用户访问某些命令的权限超出了本文档的范围,但足以说明类似以下内容

deploy ALL=NOPASSWD:/etc/init.d/mysqld, /etc/init.d/apache2

此示例将授予名为 deploy 的用户访问权限以调用 sudo /etc/init.d/mysql _________ 以及 apache2 控制脚本。

授予无密码 sudo 不应轻率进行。 这可能很危险。例如,如果一个非特权用户可以编辑他们可以以 root 身份运行的脚本,他们可以轻松地编辑它以执行他们想要的任何恶意操作。谨慎使用此功能,并且理想情况下,应设计您的系统,以便非特权用户可以重新启动服务,或者服务在注意到更改时自行重新启动。

要配置此层次结构,暂时忽略您可能需要或可能不需要的无密码 sudo 访问权限(具体取决于您的服务器设置情况)

me@localhost $ ssh root@remote
# Capistrano will use /var/www/....... where ... is the value set in
# :application, you can override this by setting the ':deploy_to' variable
root@remote $ deploy_to=/var/www/rails3-bootstrap-devise-cancan-demo
root@remote $ mkdir -p ${deploy_to}
root@remote $ chown deploy:deploy ${deploy_to}
root@remote $ umask 0002
root@remote $ chmod g+s ${deploy_to}
root@remote $ mkdir ${deploy_to}/{releases,shared}
root@remote $ chown deploy ${deploy_to}/{releases,shared}

注意: chmod g+s 是一项非常方便且鲜为人知的 Unix 功能,这意味着在操作系统级别,无需过多关注运行时的权限,在 ${deploy_to} 目录中创建的所有文件和目录都将继承组所有权,这意味着在这种情况下,即使我们是 root,文件也将被创建为由 root 拥有,组为 deployumask 0002 确保在此会话期间创建的文件以所有者读/写、组:读/写、其他:无的权限创建。这意味着我们将能够从 Apache 或我们的 Web 服务器读取这些文件,方法是在 deploy 组命名空间中运行 Web 服务器。

root@remote # stat -c "%A (%a) %n" ${deploy_to}/
drwx--S--- (2700)  /var/www/rails3-bootstrap-devise-cancan-demo

root@remote # stat -c "%A (%a) %n" ${deploy_to}/*
drwxrwsr-x (2775)  /var/www/rails3-bootstrap-devise-cancan-demo/releases
drwxrwsr-x (2775)  /var/www/rails3-bootstrap-devise-cancan-demo/shared
Fork me on GitHub