为团队管理多个 SSH 私钥

信息安全 SSH
2021-09-03 21:59:30

我有一个中型团队(约 10 人,但我们预计会增长)管理 AWS 上的一组 EC2 服务器(目前有几十个,但预计还会增长)。

由于 EC2 要求您拥有 AWS 生成的私有 SSH 密钥,而无需密码(这就是 AWS 生成它们的方式)才能访问 EC2 服务器,因为服务器的数量以及我的团队管理的不同环境和产品(以及随着人们继续前进)和离开团队),我对我们当前用于保护对 EC2 服务器的访问的解决方案越来越不满意 - 即拥有一个复制到每个团队成员本地计算机的单个私钥。

我正在考虑一些选择,如果您能提出更好的选择并讨论为什么它更好,我将不胜感激:

  1. 继续为我们的所有系统使用一个 SSH 密钥。
    • 优点:管理简单,相对安全(假设团队信任)
    • 缺点:团队成员获得密钥后,无法撤销访问权限;一次泄漏会危及所有系统的安全性;没有密码
  2. 每个产品/环境都有一个 SSH 密钥,分发给所有团队成员。
    • 优点:仍然不难管理,相对安全(假设团队信任)
    • 缺点:密钥不能被撤销;一次泄漏会危害所有系统(可能不是全部,如果没有所有密钥的初级成员是源);没有密码;难以使用,因为用户在环境之间移动时必须卸载和重新加载密钥
  3. 为每个产品/环境构建一个堡垒服务器;为每个产品/环境创建一个 SSH 密钥,并将私钥安装在堡垒服务器的已知用户帐户中;在已知用户帐户中安装每个团队成员的个人公钥。
    • 优点:允许密钥撤销;堡垒的妥协只影响 1 个环境;如果检测到用户密钥泄漏,则可以轻松防止未触及系统的危害;允许使用密码访问堡垒
    • 缺点:管理相对复杂(创建额外的服务器并运行非平凡的安装、添加团队成员、删除团队成员);昂贵(服务器不是免费的);使团队使用的软件工具复杂化;复杂的密钥撤销
  4. 使用密钥存储服务;为每个环境/产品创建一个密钥并存储在服务中;使用密码或个人 ssh 密钥控制对服务的访问;操作首先识别访问的环境/产品并获取 SSH 代理的密钥。
    • 优点:易于管理(假设服务可用);可以在每个用户或每个环境上撤销密钥;单点保护(不是关键任务);依靠 OpenSSH 代理来保护服务外部的密钥。
    • 缺点:单点故障;可能会使使用场景复杂化

我目前倾向于#4,但有没有我遗漏的严重问题?有没有这样的服务我可以使用还是我必须自己推出?

注意:我们不使用编排/配置服务器 a-la puppet/chef - 我们的编排软件大多是自制的,并安装在每个团队成员的本地系统上。它基本上只是一组从源代码管理加载的配方,用于执行各种场景,主要使用 AWS API。每个团队成员都有一个个人 AWS API 密钥,编排软件使用它来运行 AWS API。另外,有些场景需要SSH访问系统服务器,这就是我上面描述的问题所在。使用默认 AMI 用户(通常是“ubuntu”)访问 EC2 服务器,软件使用NOPASSWDsudo 执行本地操作。

4个回答

不确定您为什么使用 AWS 创建的密钥。“网络和安全/密钥对”屏幕也有一个“导入密钥对”按钮,我已经成功使用它。

当您添加一个可能正在站立新图像的新团队成员时,获取他的公钥并导入它。

在网络服务器或 S3 上保留一份标准“authorized_keys”文件的副本,每人一个或多个公钥(如果您想保留,可以是面向 VPN 的,或由团队共享的用户名和密码保护)您的团队身份保密。)现在您的部署过程的一部分是

curl -u username:password \
  https://team-server/private/authorized_keys.txt >/home/ec2-user/.ssh/authorized_keys

(这样的命令也可以添加到实例用户数据中以在实例创建时运行。)

要添加新的团队成员,请将他的公钥添加到文件中,然后在所有服务器上运行该命令。

要删除团队成员,请从文件中删除他的密钥,然后在所有服务器上运行该命令。

今天的最佳实践至少需要考虑使用堡垒主机或 VPN 类型的网络限制,以及根据您的问题的重点,中央凭证管理。我建议使用堡垒并使用基于 CA 的密钥签名。

为什么你应该(但不是必须)做堡垒

我们会喜欢它,当员工离开,即使他把他的SSH密钥的副本,他不能达到SSH端点。这是堡垒主机、VPN 和防火墙的任务。堡垒主机在基础设施方面也有很大帮助,因为如果您可以限制对堡垒的访问(例如,通过删除前雇员的 ssh 密钥),那么用户将自动被限制访问其他端点。

虽然 Bastion 主机可能会使进行密钥管理实践变得更容易,但它在技术上并不是必需的。尽管如此,我还是强烈建议使用堡垒主机:它提高了安全性;简化基础设施;并协助审计。

管理密钥

将 pubkeys 基本复制到 authorized_keys

作为自动化任务的一部分复制公钥将起作用。Ansible、SaltStack、特定于云的部署工具、cron 等都是如何实现的示例。但是它没有下面的 Key-signing 的明显优势。如果您已经拥有适合您的自动化设备,它可以继续正常工作。有一些优点——但你已经付出了额外的工作来让它工作,而且它没有坏(对吧?)。

免费IPA

如果在所有服务器和工作站/笔记本电脑上配置 FreeIPA,您可以集中管理密钥。我发现了一些显然可以与它一起使用的工具来支持使用 CA 签名的密钥,尽管我还没有探索这个选项。

密钥签名

至少自 2010 *以来,对签名 SSH 密钥证书的内置支持显然已在 openssh 中提供,尽管我最近才知道它。SSH 密钥签名似乎优于所有其他可用的密钥管理机制。Facebook 最近(2016 年)发布了关于他们如何管理证书的帖子 - 签署 SSH 密钥是主要成分。

核心思想是您拥有自己的内部 CA 来验证您的公钥,并让您的所有 ssh 端点都信任该 CA。当您连接到端点时,ssh 会显示您的公钥和签名证书,端点会验证证书是否正确,并且您被授予访问权限。

密钥签名的优点:

  • 您的“个人”密钥变得短暂 - 您不必担心备份密钥
  • 实际密钥不受信任,这意味着泄露的密钥并不重要
  • 证书过期,这意味着受损的密钥-证书组合有时间限制,此后它不再有权访问您的基础设施
  • 服务器密钥也可以签名,这意味着当您连接到签名端点时,您不必手动批准或管理冲突的指纹(FreeIPA 也可以处理这个问题,尽管它在技术上仍然会“老式”复制公钥)

如何使用密钥签名?

不幸的是,没有专门为协调 CA 信任和密钥签名而构建的著名的交钥匙系统——但有一些工具可以让您组合起来完成这项工作。对于一个团队,您可能愿意编写自己的系统来签署密钥,尽管我目前正在研究一些工具集。其中最重要的是 HashiCorp 的 Vault,它承诺使用其Secrets Engine解决签名 SSH 证书的问题。CommerceHub 的ssh-ca-server,承诺允许您的 CA 通过 Web 界面对密钥进行签名,同时使用 LDAP 身份验证来确保仅对有效用户的密钥进行签名。如前所述,我不确定这些是否是完整的解决方案 - 但它们至少涵盖了很多领域。似乎还有很多其他的工具集——尽管我还不知道有什么似乎是“最好的”工具集。另请参阅对此答案的知情评论。

我想手动完成 - 或者我想了解它是如何完成的

以个人身份,只要您保留 CA 密钥密码,您就可以使用 CA 签名,如下所示。在小型企业环境中,我建议将密钥分开,这样用户就无法直接访问它,或者将现有工具(如上所述)放在一起为您完成繁重的工作。您还应该建立适当的备份机制——这一控制对所有内容的访问:

示例手动用例

在我的示例中,我们将使用.ssh/.ca测试客户端上的 ca 数据文件夹。注意当然这只是一个例子。最好的方法是通过远程服务:

tricky@testclient $ export CAPATH=~/.ssh/.ca

生成 CA 密钥对

您可以使用rsa类型 - 我只是更喜欢ed25519

tricky@testclient $ ssh-keygen -C CA -f ${CAPATH}/ca -t ed25519
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/tricky/.ssh/.ca/ca.
Your public key has been saved in /home/tricky/.ssh/.ca/ca.pub.
The key fingerprint is:
SHA256:qGSZbssuQOV0TR8WrTV/gzXBSyTO4uAKUO7q+fJUDYw CA
The key's randomart image is:
+--[ED25519 256]--+
|     .o. +o  .oo.|
|   oo.o.o .+o .= |
|  +..E o oo.oo+ o|
| . .oo .+.o .o + |
|.   =o..So .  . .|
|.  +..o .        |
| . .+. .         |
|  o+o.           |
|   =Bo           |
+----[SHA256]-----+
tricky@testclient $ cat ${CAPATH}/ca.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKg9nxnaBLOQKnfuxUQgb3Y1hiqIaMEtXwr8WsX5bLlv CA

分发公共部分

将文件分发ca.pub到 ssh 守护程序可访问的位置中的远程端点,例如/etc/ssh/ca.pub- 这只需在每台服务器上执行一次。确保它归根用户所有,非根用户不可写。其他用户也可以阅读。

配置远程端点

将以下行添加到sshd_config远程端点上的文件中,指的是您添加ca.pub文件的位置。同样,这只需在每台服务器上执行一次:

TrustedUserCAKeys /etc/ssh/ca.pub

测试配置文件。您可能会在配置中发现“不推荐使用的选项”警告,特别是如果您使用的是非库存生成的配置 - 但只要没有实际错误,您可以重新启动 sshd 服务:

root@testserver $ sshd -t
/etc/ssh/sshd_config line 23: Deprecated option UsePrivilegeSeparation
/etc/ssh/sshd_config line 26: Deprecated option KeyRegenerationInterval
/etc/ssh/sshd_config line 27: Deprecated option ServerKeyBits
/etc/ssh/sshd_config line 45: Deprecated option RSAAuthentication
/etc/ssh/sshd_config line 54: Deprecated option RhostsRSAAuthentication
root@testserver $ systemctl restart sshd
root@testserver $

确保您没有使用旧密钥

要进行测试,请重命名您现有的密钥,以便您不再使用显式受信任的密钥:

for type in dsa rsa ed25519 ; do
 mv ~/.ssh/id_${type}{,.bak}
 mv ~/.ssh/id_${type}.pub{,.bak}
done

请参阅末尾的注释**以恢复这些原始密钥(毕竟,在您的 CA 被所有端点信任之前,您将需要现有的密钥)。

作为健全性检查,如果您使用的服务(例如 keychain 或 ssh-agent)会记住您的密钥,您可能需要停止该服务,以便知道您的测试没有使用旧密钥:

tricky@testclient $ killall ssh-agent

创建一个新的用户密钥对

您可以使用rsa类型 - 我只是更喜欢ed25519

tricky@testclient $ ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in testkey.
Your public key has been saved in testkey.pub.
The key fingerprint is:
SHA256:0cKlJxYQrizfd5FDboyVYhn9RK7oHkpsBbarZuv+DoQ brendan@tricky-desktop.4d.eset.co.za
The key's randomart image is:
+--[ED25519 256]--+
|      ooo....    |
|     . . B.o.    |
|      + @ *o.    |
|   o o = & o.    |
|  E + . S O      |
|   + o + . o     |
|    o * + .      |
|    o= + o       |
|   ==++ .        |
+----[SHA256]-----+
tricky@testclient $

签署新的用户密钥对

密钥签名使用可用于帮助审计的序列号。我不介意在示例中记录这一点。您至少会在后续签名事件中增加序列号。序列号必须是数字:

tricky@testclient $ export serial=1

身份理论上可以是任何东西 - 但客户端用户名或 user@hostname 是一个很好的起点

tricky@testclient $ export IDENTITY="${USER}@$(hostname)"

密钥需要授予特定远程用户的访问权限。使用典型的堡垒配置,您不会在这里拥有“root”:

tricky@testclient $ export users="root,${USER}"

对于有效期,我一般建议使用 4 小时(14400 秒)——但您可能需要更长的时间跨度。Facebook 的帖子建议一整天(86400 秒)。其他具有自动身份验证的人建议只需五分钟。

tricky@testclient $ export VALIDITY_PERIOD=86400

请注意,您签署了公钥,因为它是需要被端点接受的公共部分:

tricky@testclient $ ssh-keygen -s ${CAPATH}/ca -I ${IDENTITY} -n ${users} -V +${VALIDITY_PERIOD} -z ${serial} ~/.ssh/id_ed25519.pub
Enter passphrase:
Signed user key /home/tricky/.ssh/id_ed25519-cert.pub: id "tricky@testclient" serial 1 for root,tricky valid from 2018-10-09T20:06:00 to 2018-10-10T20:06:00

连接到端点:

tricky@testclient $ ssh root@testserver
Last login: Mon Oct  8 20:46:31 2018 from testclient.localdomain
root@testserver ~ #

*感谢@PatrickMevzek 提供参考信息:

TrustedUserCAKeys ... was added first in a commit on March 4 2010 openssh.com/txt/release-5.4 "Add support for certificate authentication of users and hosts using a new, minimal OpenSSH certificate format (not X.509)."

**

for type in dsa rsa ed25519 ; do
 mv ~/.ssh/id_${type}{.bak,}
 mv ~/.ssh/id_${type}.pub{.bak,}
done

恢复后,我建议您签署现有密钥,但也开始将它们视为临时密钥。保留原始密钥的备份,以防您认为信任它们但不支持基于 CA 的密钥签名的服务可能需要它们。

在重新阅读所有这些之后,我坚持认为:直接授予许多用户 root 访问权限通常不是一个好主意。使用sudo是推荐的方式(你甚至可以运行su,有组限制)。

为什么不使用 *nix 组的原始行为?

  • 为每个用户添加一个特定的帐户(您可以使用 LDAP、NIS 甚至rsync在您的passwd,上管理用户帐户复制groupshadow*$USER/.ssh/authorized_keys通过一个简单的 shell 脚本...... NIS很好!)

    adduser alice
    
  • 为每个用户添加特定的密码(用户可能或需要更改它们......或不)

    passwd alice
    
  • 将用户添加到具有特定权限的组(访问 sudo,例如)

    adduser alice sudo
    
  • 这样做,您可以创建另一个具有共同权限的组,以便在特定项目上一起工作,即使没有访问root 权限

这样做可以让能力

  • 让用户在没有密码的情况下连接,但只能在他的帐户上。然后在用户运行时询问用户密码sudo

    su - alice <<<eocmd
       curl -u username:password \
          https://team-server/private/authorized_keys.txt >~/.ssh/authorized_keys
    eocmd
    
  • 添加或删除任意数量的用户

    deluser alice
    
  • 放弃或恢复权利

    • 访问任何主机/服务器

      usermod --expiredate 1 alice
      
    • 访问特定的组权限。

      deluser alice sudo
      
  • 为每个用户保持特定authorized_keys,用于示例

    su - alice -c 'curl ... >>.ssh/authorized_keys'
    

    允许一个用户从许多不同的点进行连接。事实上,由于每个authorized_key文件都归他的用户所有,每个人都可以做他想做的事,而不会损害整个主机。

...并将PermitRootLogin保持为no in /etc/ssh/sshd_config

我相信您要么误解了,要么高估了堡垒主机的复杂性。

bastion server for each product/environment ...您应该只需要 1x 堡垒主机(2x 用于冗余以防万一)。

running non-trivial installation ...如果您的 OpenSSH 已经是堡垒主机(您只需像其他任何面向外部的服务器一样对其进行加固)

我目前为我的个人设置运行一个堡垒主机,这就是我所做的:

堡垒主机- VPS(每月花费我 2.50 美元 1cpu 512mb 500gb/m ymmv)它有 OpenSSH、UFW、Fail2Ban

内部 [0-7] - 我为我的朋友和家人管理的各种 VPS 和物理服务器......每个都配置了 OpenSSH 以仅接受来自堡垒主机的连接

堡垒主机的 authorized_keys(个人将其移出用户配置文件,因此有 1 个文件来统治它们,因此非管理员用户无法修改它)

ssh-rsa AAAA ... 4W95 myAdminUser@desktop
ssh-rsa AAAA ... rHjK alice
ssh-rsa AAAA ... DkQP bob

您可以为非管理员用户禁用每个密钥上的 shell ...我个人相信我的用户在堡垒主机 ymmv 上拥有非 root shell

内部 [0-7] 的 /etc/ssh/sshd_config

...
AllowUsers *@<bastion.host.static.ip>
...

桌面的 ~/.ssh/config:

Host bh
  HostName bastion.host.domain.name
  ControlPath ~/.ssh/cm-%r@%h:%p
  ControlMaster auto
  ControlPersist 10m

Host intN
  HostName n.internal.domain.name
  User <username>
  IdentityFile ~/.ssh/id_rsa
  ProxyCommand ssh -W %h:%p bh

使用它~/.ssh/config,您将使用命令ssh intN连接...交替ssh user1@intN并且scp intN:/foo/bar ./也可以工作

当有人想要访问时,我告诉他们给我一个用户名和一个 pub-key,将一个帐户添加到他们应该有权访问的每个服务器,并将 pub-key 添加到那里的用户~/.ssh/authorized_keys(我个人使用github 的 ssh-key自动执行此操作apiAnsible ymmv)

当某人不再需要访问时,我会#从该特定框的 authorized_keys 列表中注释掉 pub-key

当某人不再需要访问任何内容时,我会#从堡垒主机上的 authorized_keys 列表中注释掉 pub-key ...即使在各个服务器上留下了密钥...如果他们无法进入堡垒主机,他们也不能进入任何事情。