1. 简介

两台主机通信的时候,如果是通过明文的方式通信两者之间的数据很容易被截获,两台主机要安全通信,需要在两者之间实现数据的加密。主要涉及到对称加密,非对称加密,哈希函数。

2. 对称加密

所谓对称加密,即加密和解密使用相同的密钥,这个密钥可以理解为就是密码。

这里A用一个对称加密密钥haha001加密了一个数据,然后把加密之后的数据发送给B之后,因为是加密数据,所以B是打不开的。所以A需要把密码告诉B才行。但是如何告诉B密钥是haha001呢?这是一个问题,因为这个过程很可能被别人监测到。

3. 非对称加密

对于非对称加密算法来说,包括2个密钥一个是公钥,一个是私钥。公钥是可以公开的,私钥需要保护好。对于非对称加密来说主要作用有两个,一是数据加密,而是数字签名。

3.1数据加密

上图里锁的图标表示公钥,钥匙的图标表示私钥。

  • 第一步:B把公钥发送给A
  • 第二步:A用B的公钥加密数据
  • 第三步:A把加密后的数据发送给B
  • 第四步:B用自己的私钥对加密数据进行解密

因为别人获取不到B的私钥,所以在第三步里即便数据被接获了也解密不了,因为只有B的私钥才能解密。

如果把对称加密和非对称加密结合使用的话,这样利用非对称加密就可以解决了前面对称加密算法中,对称加密私钥不方便传输的问题了。

  • 第一步: B把公钥发送给A
  • 第二步:A用B的公钥加密对称加密密钥haha001
  • 第三步:A把加密后的密钥发送给B
  • 第四步:B用自己的私钥解密A发送过来的数据,得到对称加密的密钥haha001
  • 第五步:B用haha001解密A发送过来的对称加密数据

这样A用对称加密的数据发送给B之后,B也能顺利的打开了,这种方式看起来安全,但其实很容易受到中间人攻击,因为第一步里我们假设的是A获取的就是B的公钥,而不是别人冒充的。

下面我们看一下中间人攻击的过程,这里假设C是中间人

  • 第一步:B把公钥发送给A,但C把原本发送给A的公钥截获了,此时C有了B的公钥。
  • 第二步:C把自己的公钥发送给A,但是宣称是“B的公钥”,A不明真相,以为就是B的公钥
  • 第三步:A用“B的公钥”(其实是C的公钥)加密对称加密的密钥
  • 第四步:把加密后的对称加密的密钥发送给B,但是这个又被C截获了,因为实际用的是C的公钥加密的,所以C用自己的私钥是能够解密的。此时C获取了对称加密的密钥haha001
  • 第五步:C用B的公钥加密对称加密密钥haha001,之后发送给B
  • 第六步:B用自己的私钥解密数据,得到对称密钥haha001
  • 第七步:A把用对称加密后的护具发送给B,但也被C截获了,因为C知道了对称加密密钥,所以C是能看到数据里的内容并做修改的。
  • 第八步:C再次用对称密钥haha001加密数据之后转发给B
  • 第九步:B用对称密钥haha001解密数据

整个过程A和B都以为他们是直接通信的,殊不知这中间所有的数据都被C看到了,这就是中间人攻击。
那么如何解决这个问题呢?我们先了解数字签名。

4. 数字签名

数字签名是私钥加密数据,公钥解密数据。

  • 第一步:B先把公钥发送给A
  • 第二步:B然后用哈希函数对所要传输的数据求的哈希值hash1,哈希函数的特点是输入不定长的值总是得到定长的值,比如:
1
2
3
4
5
6
7
8
9
10
11
root@vms61:~# wc -c /etc/hosts
291 /etc/hosts
root@vms61:~# wc -c /etc/services
19183 /etc/services
root@vms61:~#
root@vms61:~#
root@vms61:~# md5sum /etc/hosts
219ebb29a192b6e41af2dc98865df58e /etc/hosts
root@vms61:~# md5sum /etc/services
567c100888518c1163b3462993de7d47 /etc/services
root@vms61:~#
  • 第三步:B会用自己的私钥对哈希值hash1进行加密
  • 第四步:B把原始数据和加密后的哈希值发送给A
  • 第五步:A先用B的公钥把收到的加密后的哈希值hash1解密,得到hash1,这个hash1和第2步生成hash1是一样的。
  • 第六步:B会对收到的数据data求得哈希值hash2
  • 第七步:B对比hash1和hash2来判断data在传输过程中是不是被修改过。如果两个hash值是一样的,就说明数据data在传输过程中是没有被修改的,如果两个哈希值不一样说明数据传输过程中被修改过。

这里会带来两个问题:

  • 第一:这里也会遇到中间人攻击的问题,即第一步的公钥被C截获了,然后把自己的公钥发送给了A。在第四步里,数据data和hash1被C截获,然后C修改了data的数据,然后求得哈希值之后用自己的私钥做数据签名。其实所有的中间人攻击的根本在于此。
  • 第二:即使没有中间人攻击,B的密钥对跟B这个主体之间没有必然的联系,因为B可以随时删除掉自己的密钥对,然后重新生成。

5. 证书中心

在互联网上存在一种权威机构叫做证书中心(简称CA),一个前提就是我们都要信任CA。

前面讲中间人攻击的主要原因就是A获取B的公钥可能是假冒的,所以A不会信任别人发给他的公钥,且B也知道他直接把他自己生成的公钥发送给别人,别人也不信任,所以B不会再生成私钥了。

  • 第一步:B会用自己的私钥生成一个证书请求文件(类似于申请书)
  • 第二步:B把证书请求文件发送到CA去申请证书。
  • 第三步:CA会审核B的身份,之后会颁发证书给B,这个证书其实就是公钥,只是上面有了CA的数字签名,以证明这个证书是CA颁发的。这一步解决了”数字签名“部分B和他的密钥对之间每有必然联系的问题,因为这里有CA来证明这个密钥对就是B的,B不能抵赖。
  • 第四步:B会把证书发送给A,说这是我的证书并且上面有CA的数字签名,不会被别人伪冒。
  • 第五步:A是持怀疑态度的,到底是不是真的由CA颁发的,还是别人伪冒的呢?所以A要验证这个证书的真伪性。此时A需要CA的公钥(像浏览器里都内置了权威CA的公钥),然后会用权威CA的公钥验证B证书的公钥上的数字签名。

之后,A会用B的证书加密对称加密算法的密钥发送给B,B用私钥解密,这样A和B就可以安全的传输数据了,整个过程叫做TLS(传输层安全)
这里只有A验证了B的证书合法性,所以叫做单向TLS,如果A也有自己的私钥及CA颁发的证书,那么除了A要验证B证书的合法性之外,B也要验证A证书的合法性,即两边互相验证,这个就叫做mTLS(mutual TLS,双向TLS)了。

6. k8s里的mTLS

kubeadm方式部署,创建2个CA中心,kubernetes组件一个,etcd组件一个。组件签发证书为1年,CA证书为10年。

6.1 验证tls

1
2
3
4
5
6
7
openssl rand -hex 10
cat bb.csv
56247b326b6e5bf83198,admin2,3

- --token-auth-file=/etc/kubernetes/pki/bb.csv

kubectl -s="https://192.168.26.51:6443" --insecure-skip-tls-verify=true --token="56247b326b6e5bf83198" get pods -n kube-system

7. cfssl工具签发私有证书

下载地址

1
2
3
curl -OL https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
curl -OL https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
curl -OL https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64

修改文件名权限

1
for i in * ; do n=${i%_*} ; mv $i $n; done ; chmod +x

7.1 概念

ca-config.json:可以定义多个 profiles,分别指定不同的过期时间、使用场景等参数;后续在签名证书时使用某个 profile;
signing:表示该证书可用于签名其它证书;生成的 ca.pem 证书中 CA=TRUE;
server auth:表示client可以用该 CA 对server提供的证书进行验证;
client auth:表示server可以用该CA对client提供的证书进行验证;

CN: Common Name,浏览器使用该字段验证网站是否合法,一般写的是域名。
C: Country, 国家
L: Locality,地区,城市
ST: State,州,省
O: Organization Name,组织名称,公司名称
OU: Organization Unit Name,组织单位名称,公司部门

7.2 生成ca配置文件

1
cfssl print-defaults config > ca-config.json

ca-config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"signing": {
"default": {
"expiry": "1680h"
},
"profiles": {
"www": {
"expiry": "8760h",
"usages": [
"signing",
"key encipherment",
"server auth"
]
}
}
}
}

7.3 生成证书请求文件配置

1
cfssl print-defaults csr > ca-csr.json

root@vms41:~/CA# cat ca-csr.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"CN": "ck8s.com",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "CN",
"ST": "Jiangsu",
"L": "Xuzhou",
"O": "Lduan",
"OU": "cks"
}
]
}

7.4 生成自签名证书

1
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
1
2
3
4
5
6
7
8
9
root@vms41:~/CA# ll
total 28
drwxr-xr-x 2 root root 4096 Sep 23 15:26 ./
drwx------ 6 root root 4096 Sep 23 12:18 ../
-rw-r--r-- 1 root root 343 Sep 23 12:13 ca-config.json
-rw-r--r-- 1 root root 460 Sep 23 15:26 ca.csr
-rw-r--r-- 1 root root 265 Sep 23 12:18 ca-csr.json
-rw------- 1 root root 227 Sep 23 15:26 ca-key.pem
-rw-r--r-- 1 root root 810 Sep 23 15:26 ca.pem

7.5 生成用户证书请求文件

1
cfssl print-defaults csr > client-csr.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"CN": "ck8s.com",
"key": {
"algo": "ecdsa",
"size": 256
},
"hosts": [
"www1.ck8s.com"
],
"names": [
{
"C": "CN",
"L": "Xuzhou",
"ST": "Jiangsu",
"O": "Lduan",
"OU": "cks"
}
]
}

7.6 签发client证书

1
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=www client-csr.json| cfssljson -bare client
1
2
3
4
5
6
7
8
9
10
11
12
13
root@vms41:~/CA# ll
total 44
drwxr-xr-x 2 root root 4096 Sep 23 15:36 ./
drwx------ 6 root root 4096 Sep 23 15:33 ../
-rw-r--r-- 1 root root 343 Sep 23 12:13 ca-config.json
-rw-r--r-- 1 root root 460 Sep 23 15:26 ca.csr
-rw-r--r-- 1 root root 265 Sep 23 12:18 ca-csr.json
-rw------- 1 root root 227 Sep 23 15:26 ca-key.pem
-rw-r--r-- 1 root root 810 Sep 23 15:26 ca.pem
-rw-r--r-- 1 root root 517 Sep 23 15:36 client.csr
-rw-r--r-- 1 root root 306 Sep 23 15:33 client-csr.json
-rw------- 1 root root 227 Sep 23 15:36 client-key.pem
-rw-r--r-- 1 root root 871 Sep 23 15:36 client.pem

8. cert-manager

官方文档:https://letsencrypt.org/zh-cn/docs/
运行方式:https://letsencrypt.org/zh-cn/how-it-works/
安装文档:https://letsencrypt.org/zh-cn/how-it-works/

8.1 创建

1
kubectl apply -f cert-manager.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
root@vms40:~/yaml/cert-manager# kubectl -n cert-manager get all
NAME READY STATUS RESTARTS AGE
pod/cert-manager-594bcb5484-22467 1/1 Running 0 55s
pod/cert-manager-cainjector-544bcd9bfc-bzzzp 1/1 Running 0 55s
pod/cert-manager-webhook-5999fd64fb-gzx9c 1/1 Running 0 55s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cert-manager ClusterIP 10.103.180.45 <none> 9402/TCP 55s
service/cert-manager-webhook ClusterIP 10.98.222.244 <none> 443/TCP 55s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/cert-manager 1/1 1 1 55s
deployment.apps/cert-manager-cainjector 1/1 1 1 55s
deployment.apps/cert-manager-webhook 1/1 1 1 55s

NAME DESIRED CURRENT READY AGE
replicaset.apps/cert-manager-594bcb5484 1 1 1 55s
replicaset.apps/cert-manager-cainjector-544bcd9bfc 1 1 1 55s
replicaset.apps/cert-manager-webhook-5999fd64fb 1 1 1 55s

8.2 创建clusterIssuer

创建secret
dns的token

1
2
3
4
5
6
7
8
9
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token-secret
namespace: cert-manager
type: Opaque
stringData:
api-token: were2wrj234lk4rj23l

8.3 创建clusterissue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
privateKeySecretRef:
name: letsencrypt-dns01
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- dns01:
cloudflare:
email: my-cloudflare-acc@example.com
apiTokenSecretRef:
key: api-token
name: cloudflare-api-token-secret

DNS-01 的校验原理是利用 DNS 提供商的 API Key 拿到你的 DNS控制权限, 在 Let’s Encrypt 为 ACME 客户端提供令牌后,ACME 客户端 (cert-manager) 将创建从该令牌和您的帐户密钥派生的 TXT 记录,并将该记录放在 _acmechallenge.<YOUR_DOMAIN>。Let’s Encrypt 将向 DNS 系统查询该记录,如果找到匹配项,就可以颁发证书。此方法不需要你的服务使用 Ingress,并且支持泛域名证书。

8.3 申请证书

1
kubectl apply -f certificate.yaml 
1
2
3
4
5
6
7
8
9
10
11
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: www-ck8s-com
spec:
dnsNames:
- www.ck8s.com
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns01
secretName: www-ck8s-com-tls

查看证书申请

1
2
3
root@vms40:~/yaml/cert-manager# kubectl get certificaterequests.cert-manager.io 
NAME APPROVED DENIED READY ISSUER REQUESTOR AGE
www-ck8s-com-dfwlq True False letsencrypt-dns01 system:serviceaccount:cert-manager:cert-manager 27s

查看签发状态

1
2
3
4
root@vms40:~/yaml/cert-manager# kubectl get challenges.acme.cert-manager.io 
NAME STATE DOMAIN AGE
www-ck8s-com-dfwlq-3476287311-2898304443 pending www.ck8s.com 63s

查看证书

1
2
3
4
root@vms40:~/yaml/cert-manager# kubectl get certificate
NAME READY SECRET AGE
www-ck8s-com False www-ck8s-com-tls 2m38s

修改ingress使用签发的证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: mying
spec:
ingressClassName: nginx
tls:
- hosts:
- www1.ck8s.com
secretName: www-ck8s-com-tls
rules:
- host: www1.ck8s.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc1
port:
number: 80

自动申请证书
创建ingress自动去ClusterIssuer申请证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: extensions/v1beta1 
kind: Ingress
metadata:
name: solo-ingress
annotations:
kubernietes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- www.ck8s.com
secretName: rhce-tls
rules:
- host: www.ck8s.com
http:
paths:
- path: /
backend:
serviceName: svc1
servicePort: 80

8.4 使用自签名CA

8.4.1 创建secret

把CA的证书和密钥写入到 secre

1
kubectl create secret tls ca-key --cert=ca.pem --key=ca-key.pem --namespace=cert-manager

8.4.2 创建clusterissuer

用自己的CA模拟签发机构

1
2
3
4
5
6
7
8
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: cert-manager
spec:
ca:
secretName: ca-ke

8.4.3 创建ingress

ingress中指定签发机构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: extensions/v1beta1 
kind: Ingress
metadata:
name: solo-ingress
annotations:
kubernietes.io/ingress.class: "nginx"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
tls:
- hosts:
- www.ck8s.com
secretName: rhce-tls
rules:
- host: www.ck8s.com
http:
paths:
- path: /
backend:
serviceName: svc1
servicePort: 80