前言
最近搞istio升级时,经常碰到mTLS加密的问题。恰好最近有同事问到双向加密的事情,感觉每次都是看完当时懂,过后又感觉只是一知半解,讲不清楚。此次做个总结。
什么是对称加密?
加密解密使用同一个密钥
加密速度快
问题:客户端与服务端如何使用同一个密钥?
encrypt(明文,秘钥) = 密文
decrypt(密文,秘钥) = 明文
什么是非对称加密?
非对称加密,有公私钥之分
加密速度慢
公钥加密的数据,则只能用私钥来解密(加密);
私钥加密的数据,只能用公钥来解密(签名)。
公钥是可以公开的密钥,可以在网络上传输。
由于加密速度慢,通常会结束对称加密一起使用。
通过非对称加密方式,传递对称加密的私钥;真正的通信使用对称加密
什么是mtls?
mtls即双向认证
客户端验需要证服务器端证书: 如在访问https的时候,使用curl -k
可以跳过客户端对服务端证书的验证。服务端生成证书+密钥,客户端校验服务端的证书。在通过浏览器访问https加密网站的时候,服务端会把它的证书发给客户端,浏览器内置的CA根证书会校验该证书的过期时间、域名地址等是否合法。
服务端需要验证客户端证书:双向认证会多这一步,此时客户端需要带上自己的证书+密钥来访问服务端。
如下:
[root@ssa3 ~]# curl https://127.0.0.1:2379/version --cacert /etc/ssl/etcd/ssl/ca.pem --cert /etc/ssl/etcd/ssl/admin-ssa3.pem --key /etc/ssl/etcd/ssl/admin-ssa3-key.pem
{"etcdserver":"3.3.12","etcdcluster":"3.3.0"}
在访问https网页的时候,具体是怎样的传输过程?
思考:早期网银给你一个数字证书,需要自己安装在浏览器上面,这个证书是什么证书?
- 有可能是ca证书,如果早期银行的证书不在浏览器上的CA根证书链里面,此时服务器发过来公钥(即证书),此时浏览器不识别,就会导致连接不成功。
- 有没有可能是双向认证,这个证书是客户端证书,对客户端数据加密后让服务端来验证客户的证书? — 感觉这种可能性不大,因为一般需要证书+密钥,缺密钥。
关于jwt
格式:
base64(Header).base64(Payload).Signature
位于七层,主要用于保存请求登陆信息。
有过期时间。
为什么需要Jwt?
如果要实现多点登陆
,一般是使用cookie与session来解决,客户端存cookie,登陆成功后,把登陆的信息hash加密后存到服务端session中。服务端给客户端返回sessionID,下次浏览器访问的时候,带上这个seesionID,服务端就能知道该请求的登陆状态。
但是如果跨域,就要求session持久化并做共享。多了一个持久化层,多了一个故障点。
为了减少这个故障点,可以使用JWT。不用服务端来存session,当服务端认证通过后,会直接给客户端返回一个token,客户端在访问的时候,直接带上自己的认证信息,即JWT。
什么是Jwt?
JWT签名信息
是对令牌头
、负载
和签名
三者通过令牌头中指定的哈希算法计算出来的摘要值。
摘要是惟一的标识,例如从网上下载镜像,会附带md5校验值。但是不能用这个md5值推导出镜像。
-
Header: Json对象,存放加密算法类型
-
Payload: Json对象,存储实际要传输的数据
iss (issuer):签发人
exp (expiration time):过期时间
sub (subject):主题
aud (audience):受众
nbf (Not Before):生效时间
iat (Issued At):签发时间
jti (JWT ID):编号
- Signature签名,该部分是对前两者的签名,防止数据被篡改,secret由服务器端保存。生成jwt的过程在服务端完成,然后把Jwt发给客户端。签名公式为:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
根据上面的公式可知,前两个Json对象使用base64来加密;但是可以使用
base64 --decode
来解密查看其内容。因此任何人都可以看,那这样加密的目的是什么?
因为对于用户的一些特殊字段,在数据传输过程中容易被转义,如点
或斜杠
等。此时使用base64来解决该问题。另外在传输二进制的时候,也通常使用base64加密传输。
参考阮一峰jwt入门
如何使用jwt?
客户端收到jwt后,可以存在cookie中,但是这样无法跨域。
最好的做法是存在HTTP Header请求头中的Authorization
字段里面
Authorization: Bear <token>
还有另一种做法:跨域的时候,放在Post请求体中。
如:在KubeSphere中使用JWT
使用token直接登陆的方法:
- 先获取secret:
[root@master1 ~]# kubectl -n kubesphere-system get secret kubesphere-secret -o yaml
,获取token
- 将token base64 decode出来
hugo@zack:/Users/hugo $ echo "ZXlKaGJHY2lPaUpJVXpJMU5pSXNJblI1Y0NJNklrcFhWQ0o5LmV5SmxiV0ZwYkNJNkltRmtiV2x1UUd0MVltVnpjR2hsY21VdWFXOGlMQ0oxYzJWeWJtRnRaU0k2SW1Ga2JXbHVJaXdpZEc5clpXNWZkSGx3WlNJNkluTjBZWFJwWTE5MGIydGxiaUo5LlR2UG1IaFlGT2NacGpXamtkS3JrMXVNdktIQlhnREdTck5GU0k1Q3ROWVU="|base64 --decode
- 使用Bearer token
hugo@zack:/Users/hugo $ curl http://139.198.19.140:30011/kapis/tenant.kubesphere.io/v1alpha2/events -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGt1YmVzcGhlcmUuaW8iLCJ1c2VybmFtZSI6ImFkbWluIiwidG9rZW5fdHlwZSI6InN0YXRpY190b2tlbiJ9.TvPmHhYFOcZpjWjkdKrk1uMvKHBXgDGSrNFSI5CtNYU"
jwt与tls加密冲突吗?
tls主要解决传输加密;jwt解决的是请求认证的问题。一个四层、一个七层,不冲突。
由于只要拿到jwt后,就可以登陆服务,因此最好使用https来访问,并设置合适的过期时间。
什么是证书?
如何生成证书?
关于如何创建key pem可以参考在安装k8s时候etcd的证书密钥
总结:
- 先创建ca密钥
- 根据密钥创建ca证书
规律:先创建密钥,再创建证书,证书是根据密钥创建出来的
节点的证书:
- 先创建密钥
- 再创建csr证书请求
- 最后创建出证书
# Root CA
if [ -e "$SSLDIR/ca-key.pem" ]; then
# Reuse existing CA
cp $SSLDIR/{ca.pem,ca-key.pem} .
else
openssl genrsa -out ca-key.pem > /dev/null 2>&1
openssl req -x509 -new -nodes -key ca-key.pem -days -out ca.pem -subj "/CN=etcd-ca" > /dev/null 2>&1
fi
# ETCD member
if [ -n "$MASTERS" ]; then
for host in $MASTERS; do
cn="${host%%.*}"
# Member key
openssl genrsa -out member-${host}-key.pem > /dev/null 2>&1
openssl req -new -key member-${host}-key.pem -out member-${host}.csr -subj "/CN=etcd-member-${cn}" -config ${CONFIG} > /dev/null 2>&1
openssl x509 -req -in member-${host}.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out member-${host}.pem -days -extensions ssl_client -extfile ${CONFIG} > /dev/null 2>&1
# Admin key
openssl genrsa -out admin-${host}-key.pem > /dev/null 2>&1
openssl req -new -key admin-${host}-key.pem -out admin-${host}.csr -subj "/CN=etcd-admin-${cn}" > /dev/null 2>&1
openssl x509 -req -in admin-${host}.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out admin-${host}.pem -days -extensions ssl_client -extfile ${CONFIG} > /dev/null 2>&1
done
fi
https://github.com/kubernetes-sigs/kubespray/blob/master/roles/etcd/templates/make-ssl-etcd.sh.j2
参考istio如何生成证书的,有完整的方法
https://github.com/istio/istio/blob/master/security/samples/plugin_ca_certs/gen_certs.sh#L33
参考kubesphere如何生成证书的
https://github.com/kubesphere/kubesphere/blob/master/hack/generate_certs.sh
tls与sni什么关系
istio的virtualservice中支持http tcp tls三种协议,为什么需要有tls协议?
tls是一层加密协议,虽然也位于传输层,却在tcp之上。因此tls协议也可以按tcp协议把流量做分流;就好比是http的流量,也可以使用tcp流量做分流。
那什么是sni呢,sni tls跟istio并没有什么关系。只不过istio在virtualservice使用tls协议,对tls协议多了一层支持。就好比区分它是http流量后,可以使用Http的一些属性更好地来支持它。
那么到底什么是sni呢?
很多提供tls的服务器,其实是可以配置多个证书,同样是443端口,可以有两个证书,比如hello.com和world.com (edited)
客户端在发起请求的时候会指定sni,当然也有默认的证书,但是如果sni指定错误,会导致tls流程错误,因为证书就返回错误,客户端会出现校验不通过。https://tools.ietf.org/html/rfc6066#section-3
类似于nginx可以通过vhosts的不同指向不同的后端服务。
http的场景如下:
hello.com world.com 指向同一个IP+端口,后端通过匹配hosts来区分流量。
这个是http层面的了 就是虚拟服务器。
但是在tls握手阶段还没有hostname,但是客户端需要指定需要哪个证书。
这个时候只能通过tls的sni来做,sni的指定是由tls客户端做的。
也就是说sni的作用主要在提供多证书才有用,对于一个端口一个证书的意义不大,因为有默认证书。
参考
http://icyfenix.cn/architect-perspective/general-architecture/system-security/transport-security.html
https://www.jianshu.com/p/29e0ba31fb8d