如何使用TLS/SSL和防火墙规则保护您的CoreOS集群

来自菜鸟教程
跳转至:导航、​搜索

介绍

如果您计划在您无法控制的网络环境中运行 CoreOS 集群,例如在共享数据中心内或通过公共互联网,您可能已经注意到 etcd 通过发出未加密的 HTTP 请求进行通信。 可以通过在集群中的每个节点上配置 IPTables 防火墙来降低这种行为的风险,但一个完整的解决方案最好使用加密的传输层。

幸运的是,etcd 支持点对点 TLS/SSL 连接,因此集群的每个成员都经过身份验证,所有通信都被加密。 在本指南中,我们将首先配置一个包含三个成员的简单集群,然后在每台机器上配置 HTTPS 端点和基本防火墙。

先决条件

本指南主要基于 CoreOS 系统组件介绍在 DigitalOcean 上设置 CoreOS 集群的指南中讨论的概念。

您应该熟悉 etcdfleetctlcloud-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 的服务,并写出配置文件告诉 etcdfleet 在哪里查找 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 块。 值分为文件系统 pathpermissions 掩码和 content,其中包含文件的所需内容。 在这里,我们指定 etcd2fleet 服务的 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 NetworkingEnable 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

粘贴并保存以下脚本,根据您的集群的需要调整 regionsize 字段(nyc3512mb 的默认值可用于演示目的,但您可能需要不同的区域或更大的 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 为您的平台检索 cfsslcfssljson 的最新版本:

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 authclient auth 用法. 对于点对点 TLS,我们需要这两者。

接下来,创建并打开 ca-csr.json

nano ca-csr.json

粘贴以下内容,根据您的位置和组织的需要调整 CNnames 数组。 对 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.jsonca-csr.json 的默认值进行比较,可以使用 cfssl 打印默认值。 对于 ca-config.json,使用:

cfssl print-defaults config

对于 ca-csr.json,使用:

cfssl print-defaults csr

ca-csr.jsonca-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.jsoncoreos-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.pemcoreos-key.pemcoreos.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_ipcoreos-2_private_ipcoreos-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-2coreos-3 上安装 /var/lib/iptables/rules-save

结论

在本指南中,我们定义了一个包含三个成员的基本 CoreOS 集群,为每个成员提供 TLS/SSL 证书以进行身份验证和传输安全,并使用防火墙阻止来自本地数据中心网络上其他 Droplet 的连接。 这有助于缓解在共享网络上使用 CoreOS 所涉及的许多基本安全问题。

从这里开始,您可以应用 本系列其余关于 CoreOS 入门的技术来定义和管理服务。