通过etcd集群搭建了解pki安全认证-01
本文使用3台CentOS7虚拟机搭建etcd集群,该集群是k8s高可用集群的基础。本文基于搭建过程解释了etcd中的pki机制,了解pki机制在集群安全中的一般工作模式。
0. 实验环境
创建3台CentOS7虚拟机,主机名为k8s1-k8s3,并分配ip 192.168.56.41-43,可通过以下vagrant项目快速启动:
$ git clone https://github.com/lprincewhn/k8s-ha.git
$ cd k8s-ha
$ vagrant up
1. 启动etcd非安全集群
1.1 安装并启动etcd
在3个节点上安装etcd:
# yum install -y etcd
# systemctl start etcd && systemctl enable etcd
使用etcdctl访问etcd并检查其状态验证启动成功。
# etcdctl cluster-health
member 8e9e05c52164694d is healthy: got healthy result from http://localhost:2379
1.2 修改配置启动集群
上述3个节点上的etcd并未形成集群,配置集群需修改配置文件/etc/etcd/etcd.conf中以下参数,以k8s1节点为例:
ETCD_DATA_DIR="/var/lib/etcd/default.etcd" # 数据存储目录,可使用默认值
ETCD_LISTEN_PEER_URLS="http://192.168.56.41:2380" # 用于侦听来自于其他节点请求
ETCD_LISTEN_CLIENT_URLS="http://192.168.56.41:2379,http://127.0.0.1:2379" # 服务URL,默认只允许本地访问,
ETCD_NAME="k8s1" # 节点名称,默认为default,在集群环境中有多个节点,因此需要修改
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.56.41:2380" # 广播给集群其他节点访问的地址
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.56.41:2379" # 广播给客户端访问的地址
ETCD_INITIAL_CLUSTER="k8s1=http://192.168.56.41:2380,k8s2=http://192.168.56.42:2380,k8s3=http://192.168.56.43:2380" # 集群及其URL列表,仅在集群第一次启动建立集群时有效
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster" # 集群相互认证时使用的token, 当有多个集群时需要为每个etcd集群配置不同token,仅在集群第一次启动建立集群时有效
集群的配置信息如节点url,token均存储在数据目录中,其配置项仅在建立集群时生效。因此当修改已有etcd集群配置时(如新增节点,从http变为https通信等操作),并不是简单的修改配置文件就能完成,而是要通过etcdctl的集群管理命令完成。
目前由于集群中没有任何数据,因此可删除已有实例,重新启动etcd。
# systemctl stop etcd
# rm -rf /var/lib/etcd/default.etcd
# systemctl start etcd
可以在任意一个节点上使用etcdctl验证集群状态:
# etcdctl cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from http://192.168.56.41:2379
cluster is healthy
etcdctl默认访问的服务端点是http://127.0.0.1:2379, 因此此时访问的是本地的etcd,但是etcd会像客户端广播所有节点的访问端点(ETCD_ADVERTISE_CLIENT_URLS参数)。etcdctl根据这些URL可以获取所有节点的状态。
如果之前在配置文件中的ETCD_LISTEN_CLIENT_URLS不包含http://127.0.0.1:2379, 或者需要访问其他节点上的etcd,可以通过–endpoints参数指定服务端点。
[root@k8s1 ~]# etcdctl --endpoints="http://192.168.56.41:2379" cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from http://192.168.56.41:2379
cluster is healthy
[root@k8s1 ~]# etcdctl --endpoints="http://192.168.56.42:2379" cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from http://192.168.56.41:2379
cluster is healthy
[root@k8s1 ~]# etcdctl --endpoints="http://192.168.56.43:2379" cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from http://192.168.56.41:2379
cluster is healthy
2. 集群通信场景
集群服务中的通信一般包括两种场景:
- 对外提供服务的通信,发生在集群外部的客户端和集群某个节点之间,etcd默认端口为2379。
- 集群内部的通信,发生在集群内部的任意两个节点之间,etcd的默认端口为2380。
后面的章节将针对这两部分通信逐步在etcd集群中应用pki机制,加强集群安全。
3. 创建RootCA
3.1 安装pki证书管理工具cfssl
使用openssl同样可以完整PKI的证书配置,cfssl是一个只是使用go语言编写,云原生的轻量级工具,其命令使用起来相对简单。安装命令如下:
# curl -s -L -o /bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
# curl -s -L -o /bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
# curl -s -L -o /bin/cfssl-certinfo https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64
# chmod +x /bin/cfssl*
3.2 PKI配置
对于上面描述的两种场景,通信过程中的证书作用是不同的:
- 服务器与客户端之间的通信,这种情况下服务器的证书仅用于服务器认证,客户端证书仅用于客户端认证
- 服务器间的通信,这种情况下每个etcd既是服务器也是客户端,因此其证书既要用于服务器认证,也要用于客户端认证。
创建PKI配置文件:
# mkdir /etc/etcd/pki
# cd /etc/etcd/pki
# cfssl print-defaults config > ca-config.json
# vi ca-config.json
基于上述两种场景修改生成的配置文件ca-config.json,在其中定义3个profile:
- server,作为服务器与客户端通信时的服务器证书,其配置如下:
# cat ca-config.json ... "server": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "server auth" ] } ...
- client,作为服务器与客户端通信时的客户端证书。
# cat ca-config.json ... "client": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "client auth" ] } ...
- peer,作为服务器间通信时用的证书,既认证服务器也认证客户端。
# cat ca-config.json ... "peer": { "expiry": "8760h", "usages": [ "signing", "key encipherment", "server auth", "client auth" ] }, ...
实验集群有3个节点,需要配置的所有证书总结如下:
- 根CA证书1个, 每个节点都保存,只保存证书即可。
- 服务器server证书1个,本实验中为整个集群使用1个证书,每个服务器均保存该证书和私钥。
- 客户端证书1个, 本实验环境中仅供etcdctl使用,因此在运行etcdctl的主机上保存证书和私钥即可。生产环境中每个访问etcd的客户端都应该有自己的客户端证书和私钥。
- 服务器peer证书3个, 每个节点保存自己的证书和私钥。
3.3 创建RootCA证书
准备创建证书请求文件所需配置,其中应该包含CA的标识,所在主机,证书加密算法等信息。
# cfssl print-defaults csr > rootca-csr.json
# vi rootca-csr.json
修改后内容如下,其中重要的是CN和hosts字段,CN是通信实体的标识,而由于CA证书不表示任何一台服务器,因此此处无需hosts字段。 names可根据实际情况添加作为辅助信息,key指明了加密算法。除非遇上某些特殊算法不被支持,否则没必要修改。
# cat rootca-csr.json
{
"CN": "ETCD Root CA",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "US",
"L": "CA",
"ST": "San Francisco"
}
]
}
运行以下指令生成3个文件:
- rootca.csr, 证书请求文件,实际上是cfssl的一个中间文件,由上述配置文件生成,其中的关键信息是通信实体的基本信息和一对公私密钥,这些内容将用于签发证书。
- rootca.pem, 证书文件,即上面提到的根CA证书,需要保存到所有服务器和客户端上,用于验证通信过程中交互的证书。与RootCA证书是自签证书,其实就是用证书请求文件中的私钥对其他信息包括公钥签名的过程。
- rootca-key.pem, 私钥文件,可用于验证rootca.pem, 日常通信不会使用,应该离线保存。
# cfssl gencert -initca rootca-csr.json | cfssljson -bare rootca
# ls rootca*
rootca.csr rootca-csr.json rootca-key.pem rootca.pem
可使用openssl指令查看上述生成的证书信息:
# openssl x509 -in rootca.pem -text -noout
4. 服务器端验证
4.1 创建服务器证书
和创建CA证书类似,准备创建证书请求文件所需配置:
# cfssl print-defaults csr > etcd-csr.json
# vi etcd.json
修改其中CN和hosts,与创建根CA证书不同,服务器证书需要指定hosts,表明该证书可以被哪些主机使用。如果需要使用域名,主机名或者其他ip访问etcd,此处需要配置所有可能用到的域名,主机名,IP。反过来说,如果hosts列表中配置了多个节点的IP,则表示这个证书可以在多台主机上使用。
将所有节点IP加入hosts列表,即整个集群可以共用这个证书:
{
"CN": "ETCD Cluster",
"hosts": [
"192.168.56.41",
"192.168.56.42",
"192.168.56.43"
],
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "US",
"L": "CA",
"ST": "San Francisco"
}
]
}
但这不是一个好的配置方案,考虑未来集群扩缩容时节点发生变化后,只能有两种处理方法:
- 每次变化都修改hosts列表重新生成证书,重新配置到集群现有节点上,这样会增加维护风险。
- 对新增的节点新创建独立的证书,对移除的节点不作处理,这样会使得证书信息趋于混乱。
在实际生产环境如果希望减少证书的数目,建议与组织管理的角度结合,在hosts列表中配置一个通用的名称,比如集群对外服务的域名或者虚拟IP。
运行以下指令生成证书及其私钥:
# cfssl gencert -ca=rootca.pem -ca-key=rootca-key.pem -config=ca-config.json -profile=server etcd-csr.json | cfssljson -bare etcd
参数说明如下:
- -ca: 签发证书的CA证书
- -ca-key: 签发证书的CA私钥,事实上,签发证书正是使用CA私钥对证书进行签名的过程。
- config: 指定配置文件
- profile: 指定配置文件中的profile,参考上面的描述,其中可定义证书的有效期,用途等。
如上所述,这个证书时所有节点共用的,因此需要将其拷贝到所有节点:
# scp /etc/etcd/pki/etcd*.pem k8s2:/etc/etcd/pki/
# scp /etc/etcd/pki/etcd*.pem k8s3:/etc/etcd/pki/
4.2 修改etcd配置并重启
修改配置文件,让该节点上的etcd服务在https端口,需要修改的参数如下:
ETCD_LISTEN_CLIENT_URLS="https://192.168.56.41:2379,http://127.0.0.1:2379"
ETCD_ADVERTISE_CLIENT_URLS="https://192.168.56.41:2379"
ETCD_CERT_FILE="/etc/etcd/pki/etcd.pem"
ETCD_KEY_FILE="/etc/etcd/pki/etcd-key.pem"
注意: 如果是用root用户生成的证书文件,由于私钥文件生成后的权限为rw——-,普通用户不可读,因此需要将其属主修改为etcd。
# chown -R etcd:etcd /etc/etcd/pki/
此时改变的仅仅时集群对外的服务方式,内部的通信方式并没有改变,因此无需删除实例,可直接重启etcd。
重启后,使用etcdctl指令访问集群,如果在不指定–ca-file参数,结果会提示 https://192.168.56.41:2379 访问失败,因为其证书是不受信任的。
# etcdctl cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
failed to check the health of member c920522ba9a75e17 on https://192.168.56.41:2379: Get https://192.168.56.41:2379/health: x509: certificate signed by unknown authority
member c920522ba9a75e17 is unreachable: [https://192.168.56.41:2379] are all unreachable
cluster is healthy
注意:ETCD_LISTEN_CLIENT_URLS中包含了http://127.0.0.1:2379, 因此直接指定该地址可以访问etcd,但是ETCD_ADVERTISE_CLIENT_URLS中不包含http://127.0.0.1:2379, 因此etcd在给客户端广播集群节点的地址时,只会广播https://192.168.56.41:2379, etcdctl紧接着用这个地址去查询集群健康状态时,但证书不受信任无法访问。
加上–ca-file参数指定用于校验的CA证书,即根CA证书后,访问正常。
# etcdctl --ca-file /etc/etcd/pki/rootca.pem cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from http://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from http://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from https://192.168.56.41:2379
cluster is healthy
上面输出可以看到,仅有1个节点启动了https。对其余两个节点重复本节操作即可。出于对rootca的安全考虑,服务器证书的生成操作在一台服务器上完成,生成后将其拷贝到相应节点即可。配置并重启完所有节点后,应该可以看到所有节点的侦听URL均为https协议。
# etcdctl --ca-file /etc/etcd/pki/rootca.pem cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from https://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from https://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from https://192.168.56.41:2379
cluster is healthy
5. 客户端验证
5.1 修改etcd配置并重启
启动客户端认证需要修改以下参数:
ETCD_CLIENT_CERT_AUTH="true"
ETCD_TRUSTED_CA_FILE="/etc/etcd/pki/rootca.pem"
重启etcd服务后发现即使指定了–ca-file参数,https节点仍然无法访问。这次的错误是证书错误,因为客户端没有提供任何证书。
# etcdctl --ca-file /etc/etcd/pki/rootca.pem cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from https://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from https://192.168.56.42:2379
failed to check the health of member c920522ba9a75e17 on https://192.168.56.41:2379: Get https://192.168.56.41:2379/health: remote error: tls: bad certificate
member c920522ba9a75e17 is unreachable: [https://192.168.56.41:2379] are all unreachable
cluster is healthy
5.2 创建客户端证书
创建客户端证书请求文件所需配置:
# cfssl print-defaults csr > etcdctl-csr.json
# vi etcdctl.json
修改后内容如下,etcdctl可能运行在多台节点上,因此不指定可以使用该证书的主机列表。
# cat etcdctl.json
{
"CN": "ETCDCTL",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "US",
"L": "CA",
"ST": "San Francisco"
}
]
}
运行以下指令生成证书及其私钥:
# cfssl gencert -ca=rootca.pem -ca-key=rootca-key.pem -config=ca-config.json -profile=client etcdctl-csr.json | cfssljson -bare etcdctl
然后在etcdctl命令行中指定生成的证书和私钥,才能成功访问节点:
# etcdctl --ca-file /etc/etcd/pki/rootca.pem --cert-file /etc/etcd/pki/etcdctl.pem --key-file /etc/etcd/pki/etcdctl-key.pem cluster-health
member 6bf749bb3ab16843 is healthy: got healthy result from https://192.168.56.43:2379
member 7c1dfc5e13a8008a is healthy: got healthy result from https://192.168.56.42:2379
member c920522ba9a75e17 is healthy: got healthy result from https://192.168.56.41:2379
cluster is healthy
5.3 修改其他节点的客户端认证配置
把根CA证书拷贝到集群的所有节点当中:
# scp /etc/etcd/pki/rootca.pem k8s2:/etc/etcd/pki/rootca.pem
# scp /etc/etcd/pki/rootca.pem k8s3:/etc/etcd/pki/rootca.pem
然后参考5.1修改配置重启服务。