如何使用TLS/SSL和防火墙规则保护您的CoreOS集群
介绍
如果您计划在您无法控制的网络环境中运行 CoreOS 集群,例如在共享数据中心内或通过公共互联网,您可能已经注意到 etcd
通过发出未加密的 HTTP 请求进行通信。 可以通过在集群中的每个节点上配置 IPTables 防火墙来降低这种行为的风险,但一个完整的解决方案最好使用加密的传输层。
幸运的是,etcd
支持点对点 TLS/SSL 连接,因此集群的每个成员都经过身份验证,所有通信都被加密。 在本指南中,我们将首先配置一个包含三个成员的简单集群,然后在每台机器上配置 HTTPS 端点和基本防火墙。
先决条件
本指南主要基于 CoreOS 系统组件介绍 和 在 DigitalOcean 上设置 CoreOS 集群的指南中讨论的概念。
您应该熟悉 etcd
、fleetctl
、cloud-config
文件和生成发现 URL 的基础知识。
为了创建和访问集群中的机器,您需要一个与您的 DigitalOcean 帐户关联的 SSH 公钥。 有关在 DigitalOcean 中使用 SSH 密钥的详细信息,请参见此处。
如果您想使用 DigitalOcean API 创建您的 CoreOS 机器,请参阅 本教程 以获取有关如何生成和使用具有写入权限的个人访问令牌的信息。 API 的使用是可选的,但从长远来看可能会节省您的时间,尤其是在您预期构建更大的集群时。
生成新的发现 URL
通过在浏览器中访问 https://discovery.etcd.io/new?size=3 并复制显示的 URL,从 discovery.etcd.io 检索新的发现 URL ,或通过在本地计算机上的终端使用 curl
:
curl -w "\n" "https://discovery.etcd.io/new?size=3"
保存返回的网址; 我们很快就会在我们的 cloud-config
中使用它。
编写包含 HTTPS 配置的 Cloud-Config 文件
我们将从编写 cloud-config
开始。 cloud-config
将在初始化每个服务器时作为 用户数据 提供,定义集群的重要配置细节。 这个文件会很长,但不会比 基本集群指南 中的版本复杂得多。 我们将明确告诉 fleet
使用 HTTPS 端点,为我们的防火墙启用名为 iptables-restore
的服务,并写出配置文件告诉 etcd
和 fleet
在哪里查找 SSL 证书。
在本地机器上打开一个终端,确保你在你的主目录中,然后使用 nano
(或你喜欢的文本编辑器)创建并打开 ~/cloud-config.yml
:
cd ~ nano cloud-config.yml
粘贴以下内容,然后将 etcd2
部分中的 https://discovery.etcd.io/token
更改为您在上一部分中声明的发现 URL。
如果您不想启用防火墙,也可以删除 iptables-restore
部分。
粘贴时要小心压痕。 cloud-config
是用 YAML 编写的,它对空格很敏感。 有关特定行的信息,请参阅文件中的注释,然后我们将更详细地介绍一些重要部分。
~/cloud-config.yml
#cloud-config coreos: etcd2: # generate a new token for each unique cluster from https://discovery.etcd.io/new: discovery: https://discovery.etcd.io/token # multi-region deployments, multi-cloud deployments, and Droplets without # private networking need to use $public_ipv4: advertise-client-urls: https://$private_ipv4:2379,https://$private_ipv4:4001 initial-advertise-peer-urls: https://$private_ipv4:2380 # listen on the official ports 2379, 2380 and one legacy port 4001: listen-client-urls: https://0.0.0.0:2379,https://0.0.0.0:4001 listen-peer-urls: https://$private_ipv4:2380 fleet: # fleet defaults to plain HTTP - explicitly tell it to use HTTPS on port 4001: etcd_servers: https://$private_ipv4:4001 public-ip: $private_ipv4 # used for fleetctl ssh command units: - name: etcd2.service command: start - name: fleet.service command: start # enable and start iptables-restore - name: iptables-restore.service enable: true command: start write_files: # tell etcd2 and fleet where our certificates are going to live: - path: /run/systemd/system/etcd2.service.d/30-certificates.conf permissions: 0644 content: | [Service] # client environment variables Environment=ETCD_CA_FILE=/home/core/ca.pem Environment=ETCD_CERT_FILE=/home/core/coreos.pem Environment=ETCD_KEY_FILE=/home/core/coreos-key.pem # peer environment variables Environment=ETCD_PEER_CA_FILE=/home/core/ca.pem Environment=ETCD_PEER_CERT_FILE=/home/core/coreos.pem Environment=ETCD_PEER_KEY_FILE=/home/core/coreos-key.pem - path: /run/systemd/system/fleet.service.d/30-certificates.conf permissions: 0644 content: | [Service] # client auth certs Environment=FLEET_ETCD_CAFILE=/home/core/ca.pem Environment=FLEET_ETCD_CERTFILE=/home/core/coreos.pem Environment=FLEET_ETCD_KEYFILE=/home/core/coreos-key.pem
作为可选步骤,您可以将 cloud-config
粘贴到 官方 CoreOS Cloud Config Validator 并按 Validate Cloud-Config。
保存文件并退出。 在nano
中,可以通过Ctrl-X退出,y确认写入文件,Enter确认文件名来完成保存。
让我们看一下 cloud-init.yml
中的一些特定块。 首先,fleet
值:
fleet: # fleet defaults to plain HTTP - explicitly tell it to use HTTPS: etcd_servers: https://$private_ipv4:4001 public-ip: $private_ipv4 # used for fleetctl ssh command
请注意,etcd_servers
设置为 https
URL。 对于普通的 HTTP 操作,不需要设置此值。 但是,如果没有显式配置,HTTPS 将失败。 ($private_ipv4
是 CoreOS 初始化过程理解的变量,不是你需要改变的。)
接下来我们来到 write_files
块。 值分为文件系统 path
、permissions
掩码和 content
,其中包含文件的所需内容。 在这里,我们指定 etcd2
和 fleet
服务的 systemd
单元文件应该设置指向我们将生成的 TLS/SSL 证书的环境变量:
write_files: # tell etcd2 and fleet where our certificates are going to live: - path: /run/systemd/system/etcd2.service.d/30-certificates.conf permissions: 0644 content: | [Service] # client environment variables Environment=ETCD_CA_FILE=/home/core/ca.pem ... - path: /run/systemd/system/fleet.service.d/30-certificates.conf permissions: 0644 content: | [Service] # client auth certs Environment=FLEET_ETCD_CAFILE=/home/core/ca.pem ...
虽然我们告诉服务在哪里可以找到证书文件,但我们自己还不能提供这些文件。 为此,我们需要知道每台 CoreOS 机器的私有 IP 地址,该地址仅在机器创建后可用。
注意: 在CoreOS Droplet 上,cloud-config
的内容在Droplet 创建后不能更改,每次开机都会重新执行文件。 您应该避免将 write-files
部分用于您计划在构建集群后修改的任何配置,因为它将在下次启动 Droplet 时重置。
提供液滴
现在我们已经定义了 cloud-config.yml
,我们将使用它来配置集群的每个成员。 在 DigitalOcean 上,我们可以采用两种基本方法:通过基于 Web 的控制面板,或从命令行使用 cURL 调用 DigitalOcean API。
使用 DigitalOcean 控制面板
在同一数据中心区域内创建三个新的 CoreOS Droplet。 确保每次都勾选 Private Networking 和 Enable User Data。
- coreos-1
- coreos-2
- coreos-3
在 User Data 字段中,从上方粘贴 cloud-config.yml
的内容,确保您已将发现 URL 插入到文件顶部附近的 discovery
字段中。
使用 DigitalOcean API
作为一种可以节省重复粘贴到字段中的替代方法,我们可以编写一个简短的 Bash 脚本,它使用 curl
通过我们的 cloud-config
从 DigitalOcean API 请求一个新的 Droplet,并调用它一次每个液滴。 使用 nano
(或您选择的文本编辑器)打开一个名为 makecoreos.sh
的新文件:
cd ~ nano makecoreos.sh
粘贴并保存以下脚本,根据您的集群的需要调整 region
和 size
字段(nyc3
和 512mb
的默认值可用于演示目的,但您可能需要不同的区域或更大的 Droplets 用于实际项目):
~/makecoreos.sh
#!/usr/bin/env bash # A basic Droplet create request. curl -X POST "https://api.digitalocean.com/v2/droplets" \ -d'{"name":"'"$1"'","region":"nyc3","size":"512mb","private_networking":true,"image":"coreos-stable","user_data": "'"$(cat ~/cloud-config.yml)"'", "ssh_keys":[ "'$DO_SSH_KEY_FINGERPRINT'" ]}' \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json"
现在,让我们将环境变量 $DO_SSH_KEY_FINGERPRINT
和 $TOKEN
分别设置为与您的 DigitalOcean 帐户和 API 个人访问令牌关联的 SSH 密钥的指纹。
有关获取个人访问令牌和使用 API 的信息,请参阅本教程。
为了找到与您的帐户关联的密钥的指纹,请检查 帐户设置 的安全部分,在 SSH 密钥 下。 它将采用 公钥指纹 的形式,类似于 43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8
。
我们在这里使用 export
以便 shell 的子进程,如 makecoreos.sh
,将能够访问变量。 每次使用脚本时都必须在当前 shell 中设置,否则 API 调用将失败:
export DO_SSH_KEY_FINGERPRINT="ssh_key_fingerprint" export TOKEN="your_personal_access_token"
注意: 如果您刚刚为 API 生成了个人访问令牌,请记住将其保存在方便和安全的地方。 在首次创建时向您显示后无法检索它,并且任何拥有令牌的人都可以控制您的 DigitalOcean 帐户。
一旦我们为每个所需的凭据设置了环境变量,我们就可以运行脚本来创建每个所需的 Droplet。 makecoreos.sh
在其对 API 的调用中使用其第一个参数来填写 name
字段:
bash makecoreos.sh coreos-1 bash makecoreos.sh coreos-2 bash makecoreos.sh coreos-3
您应该看到描述每个新 Droplet 的 JSON 输出,并且所有三个都应该出现在控制面板中的 Droplet 列表中。 他们可能需要几秒钟才能完成启动。
登录到 coreos-1
无论您使用的是控制面板还是 API,您现在都应该拥有三个正在运行的 Droplet。 现在是记录他们的公共和私有 IP 的好时机,可以通过单击控制面板中的单个 Droplet,然后单击 Settings 链接来使用它们。 生成证书和配置防火墙时,将需要每个 Droplet 的私有 IP 地址。
让我们测试一个 Droplet。 确保您的 SSH 密钥已添加到本地 SSH 代理:
eval $(ssh-agent) ssh-add
在DigitalOcean控制面板中找到coreos-1的公网IP,开启SSH代理转发连接:
ssh -A core@coreos-1_public_ip
首次登录集群的任何成员时,我们可能会收到来自 systemd
的错误消息:
OutputCoreOS stable (766.5.0) Failed Units: 1 iptables-restore.service
这表明防火墙尚未配置。 目前,忽略此消息是安全的。 (如果您选择不在 cloud-config
中启用防火墙,您将不会看到错误消息。 您以后可以随时启用 iptables-restore
服务。)
在我们担心防火墙之前,让我们让集群的每个成员上的 etcd2
实例相互通信。
使用 CFSSL 生成自签名证书
CFSSL 是 CloudFlare 发布的用于处理 TLS/SSL 证书的工具包。 在撰写本文时,它是 CoreOS 维护者选择的用于生成自签名证书的工具,优于 OpenSSL 和现已弃用的 etcd-ca
。
在本地计算机上安装 CFSSL
CFSSL 需要有效的 Go 安装才能从源代码安装。 请参阅 本指南以安装 Go。
确保您的 $GOPATH
设置正确并添加到您的 $PATH
,然后使用 go get
安装 cfssl
命令:
export GOPATH=~/gocode export PATH=$PATH:$GOPATH/bin go get -u github.com/cloudflare/cfssl/cmd/cfssl go get -u github.com/cloudflare/cfssl/...
作为一种替代方法,可以从 pkg.cfssl.org 检索预构建的二进制文件。 首先确保 ~/bin
存在并且在您的路径中:
mkdir -p ~/bin export PATH=$PATH:~/bin
然后使用 curl
为您的平台检索 cfssl
和 cfssljson
的最新版本:
curl -s -L -o ~/bin/cfssl https://pkg.cfssl.org/R1.1/cfssl_linux-amd64 curl -s -L -o ~/bin/cfssljson https://pkg.cfssl.org/R1.1/cfssljson_linux-amd64
确保 cfssl
二进制文件是可执行的:
chmod +x ~/bin/cfssl chmod +x ~/bin/cfssljson
生成证书颁发机构
现在已经安装了 cfssl
命令,我们可以使用它们来生成自定义证书颁发机构,我们将使用该证书颁发机构为我们的每台 CoreOS 机器签署证书。 让我们首先创建并输入一个新目录来存储这些文件:
mkdir ~/coreos_certs cd ~/coreos_certs
现在,在 nano
(或您喜欢的文本编辑器)中创建并打开 ca-config.json
:
nano ca-config.json
粘贴并保存以下内容,用于配置 cfssl
将如何进行签名:
~/coreos_certs/ca-config.json
{ "signing": { "default": { "expiry": "43800h" }, "profiles": { "client-server": { "expiry": "43800h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] } } } }
这里值得注意的是 expiry
,当前设置为 43800 小时(或 5 年),以及 client-server
配置文件,其中包括 server auth
和 client auth
用法. 对于点对点 TLS,我们需要这两者。
接下来,创建并打开 ca-csr.json
。
nano ca-csr.json
粘贴以下内容,根据您的位置和组织的需要调整 CN
和 names
数组。 对 hosts
条目以及地点和组织名称使用虚构值是安全的:
~/coreos_certs/ca-csr.json
{ "CN": "My Fake CA", "hosts": [ "example.net", "www.example.net" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "CO", "O": "My Company", "ST": "Lyons", "OU": "Some Org Unit" } ] }
如果要将这些与 ca-config.json
和 ca-csr.json
的默认值进行比较,可以使用 cfssl
打印默认值。 对于 ca-config.json
,使用:
cfssl print-defaults config
对于 ca-csr.json
,使用:
cfssl print-defaults csr
在 ca-csr.json
和 ca-config.json
就位后,生成证书颁发机构:
cfssl gencert -initca ca-csr.json | cfssljson -bare ca -
为 CoreOS 机器生成和签署证书
现在我们有了证书颁发机构,我们可以为 CoreOS 机器编写默认值:
创建并打开coreos-1.json
:
nano coreos-1.json
粘贴并保存以下内容,将其调整为 coreos-1 的私有 IP 地址(通过单击单个 Droplet 在 DigitalOcean 控制面板中可见):
~/coreos_certs/coreos-1.json
{ "CN": "coreos-1", "hosts": [ "coreos-1", "coreos-1.local", "127.0.0.1", "coreos-1_private_ip" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "Lyons", "ST": "Colorado" } ] }
最重要的部分是 CN
,它应该是您的主机名,以及 hosts
数组,它必须包含所有:
- 您的本地主机名
127.0.0.1
- CoreOS 机器的私有 IP 地址(不是面向公众的 IP)
这些将作为 subjectAltNames 添加到生成的证书中。 etcd
连接(包括到 127.0.0.1
的本地环回设备)要求证书具有与连接主机名匹配的 SAN。
如果需要,您还可以更改 names
数组以反映您的位置。 同样,为地名使用虚构值是安全的。
对剩余的每台机器重复此过程,使用适当的 hosts
条目创建匹配的 coreos-2.json
和 coreos-3.json
。
注意: 如果您想查看 coreos-1.json
的默认值,可以使用 cfssl
:
cfssl print-defaults csr
现在,对于每台 CoreOS 机器,生成一个签名证书并将其上传到正确的机器:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client-server coreos-1.json | cfssljson -bare coreos chmod 0644 coreos-key.pem scp ca.pem coreos-key.pem coreos.pem core@coreos-1_public_ip:
这将创建三个文件(ca.pem
、coreos-key.pem
和 coreos.pem
),确保密钥文件的权限正确,然后通过 scp
将它们复制到 [ X149X]core 在 coreos-1 上的主目录。
对剩余的每台机器重复此过程,请记住,每次调用该命令都会覆盖之前的一组证书文件:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client-server coreos-2.json | cfssljson -bare coreos chmod 0644 coreos-key.pem scp ca.pem coreos-key.pem coreos.pem core@coreos-2_public_ip:
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=client-server coreos-3.json | cfssljson -bare coreos chmod 0644 coreos-key.pem scp ca.pem coreos-key.pem coreos.pem core@coreos-3_public_ip:
检查 coreos-1 上的 etcd2 功能
有了证书,我们应该能够在 coreos-1 上运行 fleetctl
。 首先,通过 SSH 登录:
ssh -A core@coreos-1_public_ip
接下来,尝试列出集群中的所有机器:
fleetctl list-machines
您应该会看到列出的每台机器的标识符及其私有 IP 地址:
OutputMACHINE IP METADATA 7cb57440... 10.132.130.187 - d91381d4... 10.132.87.87 - eeb8726f... 10.132.32.222 -
如果 fleetctl
无限期挂起,则可能需要重新启动集群。 退出到本地机器:
exit
使用 SSH 向每台 CoreOS 机器发送 reboot
命令:
ssh core@coreos-1_public_ip 'sudo reboot' ssh core@coreos-2_public_ip 'sudo reboot' ssh core@coreos-3_public_ip 'sudo reboot'
稍等片刻,重新连接到 coreos-1,然后再次尝试 fleetctl
。
在集群成员上配置 IPTables 防火墙
有了证书,本地网络上的其他机器应该不可能控制您的集群或从 etcd2
中提取值。 尽管如此,如果可能的话,减少可用的攻击面是一个好主意。 为了限制我们的网络暴露,我们可以为每台机器添加一些简单的防火墙规则,阻止来自集群中对等方以外的来源的大多数本地网络流量。
请记住,如果我们在 cloud-config
中启用了 iptables-restore
服务,我们将在首次登录 CoreOS 机器时看到 systemd
错误消息:
OutputCoreOS stable (766.5.0) Failed Units: 1 iptables-restore.service
这让我们知道,虽然服务已启用,但 iptables-restore
未能正确加载。 我们可以使用 systemctl
来诊断:
systemctl status -l iptables-restore
Output● iptables-restore.service - Restore iptables firewall rules Loaded: loaded (/usr/lib64/systemd/system/iptables-restore.service; enabled; vendor preset: disabled) Active: failed (Result: exit-code) since Wed 2015-11-25 00:01:24 UTC; 27min ago Process: 689 ExecStart=/sbin/iptables-restore /var/lib/iptables/rules-save (code=exited, status=1/FAILURE) Main PID: 689 (code=exited, status=1/FAILURE) Nov 25 00:01:24 coreos-2 systemd[1]: Starting Restore iptables firewall rules... Nov 25 00:01:24 coreos-2 systemd[1]: iptables-restore.service: Main process exited, code=exited, status=1/FAILURE Nov 25 00:01:24 coreos-2 systemd[1]: Failed to start Restore iptables firewall rules. Nov 25 00:01:24 coreos-2 iptables-restore[689]: Can't open /var/lib/iptables/rules-save: No such file or directory Nov 25 00:01:24 coreos-2 systemd[1]: iptables-restore.service: Unit entered failed state. Nov 25 00:01:24 coreos-2 systemd[1]: iptables-restore.service: Failed with result 'exit-code'.
这里有很多信息,但最有用的一行是包含 iptables-restore[689]
的那一行,它是 systemd
试图与其进程 ID 一起运行的进程的名称。 这是我们经常会发现失败服务的实际错误输出的地方。
防火墙无法恢复,因为虽然我们在 cloud-config
中启用了 iptables-restore
,但我们还没有为它提供包含我们所需规则的文件。 我们本可以在创建 Droplet 之前完成此操作,但无法知道在创建 Droplet 之前将为其分配哪些 IP 地址。 现在我们知道了每个私有 IP,我们可以编写一个规则集。
在编辑器中打开一个新文件,粘贴以下内容,并将 coreos-1_private_ip
、coreos-2_private_ip
和 coreos-3_private_ip
替换为每台 CoreOS 机器的私有 IP 地址。 您可能还需要调整 Accept all TCP/IP traffic...
下面的部分,以反映您打算从集群提供的公共服务,尽管此版本应该可以很好地用于演示目的。
/var/lib/iptables/rules-save
*filter :INPUT DROP [0:0] :FORWARD DROP [0:0] :OUTPUT ACCEPT [0:0] # Accept all loopback (local) traffic: -A INPUT -i lo -j ACCEPT # Accept all traffic on the local network from other members of # our CoreOS cluster: -A INPUT -i eth1 -p tcp -s coreos-1_private_ip -j ACCEPT -A INPUT -i eth1 -p tcp -s coreos-2_private_ip -j ACCEPT -A INPUT -i eth1 -p tcp -s coreos-3_private_ip -j ACCEPT # Keep existing connections (like our SSH session) alive: -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT # Accept all TCP/IP traffic to SSH, HTTP, and HTTPS ports - this should # be customized for your application: -A INPUT -p tcp -m tcp --dport 22 -j ACCEPT -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT # Accept pings: -A INPUT -p icmp -m icmp --icmp-type 0 -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT -A INPUT -p icmp -m icmp --icmp-type 11 -j ACCEPT COMMIT
将以上内容复制到剪贴板,登录 coreos-1,然后使用 CoreOS 上的默认文本编辑器 Vim 打开 rules-save
:
ssh -A core@coreos-1_public_ip
sudo vim /var/lib/iptables/rules-save
进入编辑器后,输入 :set paste
并按 Enter 确保关闭自动缩进,然后按 i 进入插入模式并粘贴防火墙规则. 按 Esc 退出插入模式,按 :wq 写入文件并退出。
警告: 确保文件的最后一行有一个尾随换行符,否则 IPTables 可能会因语法错误而失败,尽管文件中的所有命令都显示正确。
最后,确保文件具有适当的权限(用户可读写,组和世界只读):
sudo chmod 0644 /var/lib/iptables/rules-save
现在我们应该准备好再次尝试该服务:
sudo systemctl start iptables-restore
如果成功,systemctl
将静默退出。 我们可以通过两种方式检查防火墙的状态。 首先,通过使用 systemctl status
:
sudo systemctl status -l iptables-restore
其次,列出当前的 iptables
规则本身:
sudo iptables -v -L
我们使用 -v
选项来获得详细输出,这将使我们知道给定规则适用于哪个接口。
一旦您确信 coreos-1 上的防火墙已配置,请注销:
exit
接下来,重复此过程以在 coreos-2 和 coreos-3 上安装 /var/lib/iptables/rules-save
。
结论
在本指南中,我们定义了一个包含三个成员的基本 CoreOS 集群,为每个成员提供 TLS/SSL 证书以进行身份验证和传输安全,并使用防火墙阻止来自本地数据中心网络上其他 Droplet 的连接。 这有助于缓解在共享网络上使用 CoreOS 所涉及的许多基本安全问题。
从这里开始,您可以应用 本系列其余关于 CoreOS 入门的技术来定义和管理服务。