docker swarm集群网络安全TLS

  TLS 基础

docker swarm 集群间为了保证通信安全,使用TLS进行安全加固。分析TLS安全加固时需要一些网络安全背景知识。

  数字签名:《数字证书及CA的扫盲介绍》、《数字签名是什么

  CA和证书:《数字证书(Digital Certificate)与数字签名(Digital Signature)》、《OpenSSL 与 SSL 数字证书概念贴》、《基于OpenSSL自建CA和颁发SSL证书

  SSL/TLS:《SSL/TLS原理详解》、《grpc tls认证传输》以及很全的关于TLS 证书的文档(英文)《Survival guides - TLS/SSL and SSL (X.509) Certificates

  数字签名过程

发送人将发送内容使用HASH算法生成一个内容摘要(digest) —> 内容摘要(digest)使用私钥进行加密—>生成数字签名(signature) —> 数字签名(signature)附在信件下面 —> 将信件发送给收件人 —-> 收件人收到信件,并使用发送人的公钥进行解密:证明信件来自于发件人 —> 收件人使用同种Hash算法计算发送内容的摘要(digest),并和解密得到的digest进行对比:一致则证明信件内容没有别修改

一次数字签名涉及到一个哈希函数、发送者的公钥、发送者的私钥(不在签名里)。

数字签名使用私钥对内容摘要用私钥进行加密;加密通信过程中,使用公钥加密,私钥进行解密

  数字中心(CA)

验证公钥的正确性,为公钥做认证,使用自己的私钥为其他公钥和一些信息进行加密; —> 生成”数字证书”Digital Certificate。

  签名证书

CA进行对公钥和其他一些内容进行数字签名的信件或消息。

  证书常用扩展名

  1. .crt 证书文件 ,可以是DER(二进制)编码的,也可以是PEM( ASCII (Base64) )编码的 ,在类unix系统中比较常见
  2. .cer 也是证书 常见于Windows系统 编码类型同样可以是DER或者PEM的,windows 下有工具可以转换crt到cer
  3. .csr 证书签名请求 一般是生成请求以后发送给CA,然后CA会给你签名并发回证书.key 一般公钥或者密钥都会用这种扩展名,可以是DER编码的或者是PEM编码的 查看DER编码的(公钥或者密钥)的文件的命令为 openssl rsa -inform DER -noout -text -in xxx
  4. .key 查看PEM编码的(公钥或者密钥)的文件的命令为 openssl rsa -inform PEM -noout -text -in xxx.key

  swarmkit TLS加固分析

  概述

swarm 集群间个节点通信,先进行身份验证,再进行加密通信。

执行命令:

docker swarm init --advertise-addr

在 ${docker-root}/ /swarm/certificates/ 目录下生成四个文件

[root@localhost certificates]# ls

swarm-node.crt swarm-node.key swarm-root-ca.crt swarm-root-ca.key

文件说明:

  swarm-root-ca.crt:CA的自签名证书(执行docker swarm init 节点会启动CA(证书中心)线程,为其他节点颁发签名证书);

  swarm-root-ca.key:CA的私钥;

  swarm-node.crt:这个文件不是一个证书而是一个证书集合,包含:当前节点证书和证书中心CA的证书;包含CA的证书的目的是为了;

  swarm-node.key:当前节点的私钥;

docker swarm (LINUX下)的证书使用pem格式,查看证书信息

[root@localhost certificates]# openssl x509 -in swarm-node.crt -inform pem -noout -text

Certificate:

Data:

Version: 3 (0x2)

Serial Number:

24:45:68:8a:07:b7:e2:e1:2c:51:c0:be:43:da:1f:54:fe:09:4e:47

Signature Algorithm: ecdsa-with-SHA256

Issuer: CN=swarm-ca //证书颁发结构

Validity

Not Before: Feb 11 00:24:00 2018 GMT

Not After : May 12 01:24:00 2018 GMT

Subject: O=cvp2afdvt625g5ddhds5t9ldq, OU=swarm-manager, CN=83lp2qgjndo09mgpf96k4jkom //拥有者信息

Subject Public Key Info:

Public Key Algorithm: id-ecPublicKey

Public-Key: (256 bit)

pub:

04:9e:27:3c:81:2c:ea:99:f4:0c:45:c8:96:a4:7d:

94:5f:58:14:a9:98:61:a3:31:0a:ab:16:b5:7c:31:

31:a1:57:57:60:47:71:77:64:e6:99:53:25:63:02:

c0:b7:7f:1a:eb:78:92:9b:4b:94:a8:50:a2:f5:c2:

8a:1b:81:d1:77

ASN1 OID: prime256v1

由上信息,我们可以看出签名证书中保护多种信息,比如证书持有者、证书颁发者、证书的公钥的散列值等,具体证书格式和内容参考文章《OpenSSL 与 SSL 数字证书概念贴》;

一个swarm证书包含内容

//issueRenewCertificate 函数

CSR: csr,

CN: node.ID,

Role: node.Spec.Role,

Status: api.IssuanceStatus{

State: api.IssuanceStateRenew,}

node.ID在创建证书前被 nodeID = identity.NewID() 创建并被包含在证书里面,所以docker swarm 集群里节点的nodeID是由leader节点创建。

docker swarm .key文件保留着当前节点的私钥,查看私钥

[root@localhost certificates]# cat swarm-node.key

-----BEGIN EC PRIVATE KEY-----

MHcCAQEEIEyyW3De4ix3KOdfv/DtxwtvykdTT9Wla7uUxlus4DjEoAoGCCqGSM49

AwEHoUQDQgAEnic8gSzqmfQMRciWpH2UX1gUqZhhozEKqxa1fDExoVdXYEdxd2Tm

mVMlYwLAt38a63iSm0uUqFCi9cKKG4HRdw==

-----END EC PRIVATE KEY-----

docker 加入swarm 集群,执行命令:

docker swarm join --token ${token-string} host-ip:port

命令执行成功后,在当前节点的 ${docker-root}/ /swarm/certificates/ 目录下出现三个文件

[root@localhost certificates]# ls

swarm-node.crt swarm-node.key swarm-root-ca.crt

文件说明:

swarm-root-ca.crt:从执行swarm init命令的节点下载的CA证书文件,使用来验证其他节点的签名证书的完整性、安全性

  swarm-node.key:当前节点的x509私钥

  swarm-node.crt:当前节点的公钥签名证书,颁发机构为 swarm-root-ca -→ 执行swarm init 的节点

  docker swarm certificates 生成

  执行docker swarm init 的节点证书

  swarm-root-ca.crt swarm-root-ca.key

证书中心(CA)的自签名证书文件,由leader节点生成。

生成过程:

docker swarm 集群:网络安全 TLS 分析

1. 执行swarm init 时,会在leader节点启动ca.server协程,响应其他节点的证书请求,颁发证书;

2. 若不适用外部CA,生成的swarm-root-ca.key会保留在本地,而swarm-root-ca.crt会分发给其他节点(其他节点join的时候),所以集群的每个node节点上的swarm-root-ca.crt都是一样的;

  swarm-node.crt swarm-node.key

生成流程:

docker swarm 集群:网络安全 TLS 分析

  执行swarm join 节点证书

  swarm-root-ca.crt

swarm-root-ca.crt文件不是当前节点生成的,从CA.server下载(默认leader节点会启动CA.server进程服务)。由于是第一次连接,没有TLS安全机制进行检测,为了保证根CA证书的安全、完整性,使用 JoinToken 散列验证证书;

  swarm-node.crt swarm-node.key

这两个证书文件和leader节点的同名的两个证书生成过程类似,只不过此节点的证书请求CSR通过TLS加密通道发送个leader节点(通信前,会验证leader节点的身份),由leader节点的 Ca.server 使用私钥(swarm-root-ca.key)签名生成签名证书,再有leader节点发送给新加入节点;

  swarm TLS安全加固源码分析

  swarm JoinToken源码分析

  Swarm JoinToken产生

JoinToken产生调用链(JoinToken由Leader产生)

/manager/manager.go: Run

/ca/certificates.go: GenerateJoinToken

JoinToken查询:

docker swarm join-token work/manager

  JoinToken使用

当我们将一个节点加入一个集群时,需执行命令:

docker swarm join --token ${token-string} host-ip:port

swarm token的作用:在加入集群前,当前节点没有swarm集群的根证书中心(root-ca)的证书,无法根据TLS安全认证验证数据的root-ca的证书完整、安全性,token的作用就是使用散列来验证根CA证书的完整、安全性;

token-string -> Digest(split(token)[2]) -> digest.NewDigestVerifier() 获取verifer算法:获取相应的hash算法;

SHA256 Algorithm = "sha256" // sha256 with hex encoding

SHA384 Algorithm = "sha384" // sha384 with hex encoding

SHA512 Algorithm = "sha512" // sha512 with hex encoding

根据相应的 hash 算法,获取token中的hash值于证书从新计算出的值进行对比验证。

参考代码: ca/certificates.go GetRemoteCA 函数

  CA server颁发证书流程分析

  client和CA server间通信

实用protobuf实现客户端和服务端通信,CA相关protobuf定义文件:/swarmkit/api/ca.proto protoc生成文件:ca.pb.go ;

  CA server处理 client端证书请求

数据流:

底层grpc server收到来自客户端的protobuf格式的序列化流 -→ ca.pb.go:NodeCAServer -→ api.(*authenticatedWrapperNodeCAServer).IssueNodeCertificate -→ ca.(*Server).IssueNodeCertificate

帖上整个调用链堆栈

Feb 13 21:33:52 localhost dockerd: github.com/docker/swarmkit/ca.(*Server).IssueNodeCertificate(0xc42112a000, 0x20fde60, 0xc4217d3bc0, 0xc4217d3c20, 0x0, 0x0, 0x0)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/ca/server.go:186 +0x409

Feb 13 21:33:52 localhost dockerd: github.com/docker/swarmkit/api.(*authenticatedWrapperNodeCAServer).IssueNodeCertificate(0xc421713da0, 0x20fde60, 0xc4217d3bc0, 0xc4217d3c20, 0x421137, 0x1550200, 0x155ab80)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/api/ca.pb.go:131 +0x51

Feb 13 21:33:52 localhost dockerd: github.com/docker/swarmkit/api.(*raftProxyNodeCAServer).IssueNodeCertificate(0xc42112a140, 0x20fde60, 0xc4217d3bc0, 0xc4217d3c20, 0x0, 0x0, 0x0)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/api/ca.pb.go:791 +0x9f

Feb 13 21:33:52 localhost dockerd: github.com/docker/swarmkit/api._NodeCA_IssueNodeCertificate_Handler(0x155ab80, 0xc42112a140, 0x20fde60, 0xc4217d3bc0, 0xc42158a440, 0x0, 0x0, 0xc420a2268c, 0x10, 0x10)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/api/ca.pb.go:427 +0x27d

Feb 13 21:33:52 localhost dockerd: google.golang.org/grpc.(*Server).processUnaryRPC(0xc4217d6a20, 0x2101540, 0xc4210f4090, 0xc421771180, 0xc421713e40, 0x20c9e00, 0xc4217d3b90, 0x0, 0x0)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/google.golang.org/grpc/server.go:522 +0xa14

Feb 13 21:33:52 localhost dockerd: google.golang.org/grpc.(*Server).handleStream(0xc4217d6a20, 0x2101540, 0xc4210f4090, 0xc421771180, 0xc4217d3b90)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/google.golang.org/grpc/server.go:682 +0x6ad

Feb 13 21:33:52 localhost dockerd: google.golang.org/grpc.(*Server).serveStreams.func1.1(0xc42186b0a0, 0xc4217d6a20, 0x2101540, 0xc4210f4090, 0xc421771180)

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/google.golang.org/grpc/server.go:348 +0xab

Feb 13 21:33:52 localhost dockerd: created by google.golang.org/grpc.(*Server).serveStreams.func1

Feb 13 21:33:52 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/google.golang.org/grpc/server.go:349 +0xa3

CA server 在 /swarmkit/ca/server.go 的 IssueNodeCertificate 函数中真正处理证书请求

IssueNodeCertificate 函数:

1. 查看申请节点是否为集群中节点,并做相应处理

2. 若是新加入节点,为新节点创建NodeId等信息,并生成cert证书

//IssueNodeCertificate 函数片段

......

nodeID = identity.NewID()

// Create a new node

err := s.store.Update(func(tx store.Tx) error {

node := &api.Node {

ID: nodeID,

Certificate: api.Certificate {

CSR: request.CSR,

CN: nodeID,

Role: role,

Status: api.IssuanceStatus {

State: api.IssuanceStatePending,

},

},

Spec: api.NodeSpec{

Role: role,

Membership: api.NodeMembershipAccepted,

},

}

return store.CreateNode(tx, node)

})

...

创建证书时,将证书状态置为api.IssuanceStatePending,并调用store.CreateNode创建节点数据库信息,创建完成后产生state.EventCreateNode事件,到现在证书还没有CA签名,当manager被选举为leader时,会创建CA server服务协程,为其他节点开启CA签名服务。(具体代码参见manager.go run函数)

CA server 主要服务代码:

//ca/server.go run

...

for {

select {

case event := <-updates:

switch v := event.(type) {

case state.EventCreateNode:

s.evaluateAndSignNodeCert(ctx, v.Node)

case state.EventUpdateNode:

// If this certificate is already at a final state

// no need to evaluate and sign it.

if !isFinalState(v.Node.Certificate.Status) {

s.evaluateAndSignNodeCert(ctx, v.Node)

}

case state.EventUpdateCluster:

s.updateCluster(ctx, v.Cluster)

}

case <-ctx.Done():

return ctx.Err()

case <-s.ctx.Done():

return nil

}

}

CA server服务检查三个事件:

state.EventCreateNode   state.EventUpdateNode   state.EventUpdateCluster

在 IssueNodeCertificate  函数,node信息创建完成,发送 state.EventCreateNode 事件,被CA server检测到,调用evaluateAndSignNodeCert函数完成证书签名;

新节点加入集群证书申请整体流程:

CA rpc server 收到新节点CSR(证书请求,保护公钥) -→ 处理请求为新节点生成NODE信息,并附加到证书 -→ CA server检测到新节点的创建,使用根CA的私钥为证书签名;

  新节点加入swarm集群时证书申请

签名证书申请主要函数ca/certificates.go GetRemoteSignedCertificate,首先看看此函数的调用链

Feb 14 15:14:00 localhost dockerd: github.com/docker/swarmkit/ca.GetRemoteSignedCertificate(0x20fde60, 0xc420f5d200, 0xc4209fd500, 0x146, 0x1c0, 0xc420222ae0, 0x55, 0xc420f709f0, 0xc42016fd40, 0x0, ...)

Feb 14 15:14:00 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/ca/certificates.go:604 +0x95

Feb 14 15:14:00 localhost dockerd: github.com/docker/swarmkit/ca.(*RootCA).RequestAndSaveNewCertificates(0xc420059020, 0x20fde60, 0xc420f5d200, 0xc420f5d3b0, 0x2e, 0xc420f5d410, 0x2e, 0xc420222ae0, 0x55, 0xc42016fd40, ...)

Feb 14 15:14:00 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/ca/certificates.go:174 +0x1d4

Feb 14 15:14:00 localhost dockerd: github.com/docker/swarmkit/ca.LoadOrCreateSecurityConfig(0x20fde60, 0xc420f5d200, 0xc420192100, 0x1f, 0xc420222ae0, 0x55, 0x1720611, 0xd, 0xc42016fd40, 0xc420058fc0, ...)

Feb 14 15:14:00 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/ca/config.go:270 +0x80f

Feb 14 15:14:00 localhost dockerd: github.com/docker/swarmkit/agent.(*Node).run(0xc420a9de00, 0x20fdde0, 0xc42000c668, 0x0, 0x0)

Feb 14 15:14:00 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/agent/node.go:216 +0x56a

Feb 14 15:14:00 localhost dockerd: created by github.com/docker/swarmkit/agent.(*Node).Start

Feb 14 15:14:00 localhost dockerd: /root/dockerbuild/BUILD/docker-engine/vendor/src/github.com/docker/swarmkit/agent/node.go:158 +0x3d8

ca.LoadOrCreateSecurityConfig函数首先在本地找签名证书,如果在本地没有签名证书调用RequestAndSaveNewCertificates函数生成、获取证书。调用GetRemoteSignedCertificate函数前,先生成包含公钥的证书请求(CSR)和私钥。

ca/certificates.go GetRemoteSignedCertificate 函数将CSR封装于api.IssueNodeCertificateRequest请求,使用api.NewNodeCAClien(protobuf相关)和Ca.Server通信,并在未超时时间内等待签名证书返回。(具体细节参考GetRemoteSignedCertificate 函数)

  docker swarm 节点间MutableTLS认证、通信

在SSL/TSL协议中,有两种认证通信方式:单向认证(TLS)和双向认证(Mutual TLS),具体协议认证过程参考《An Introduction to Mutual SSL Authentication》。docker swarm 集群两节点间需互相验证身份,所以使用Mutual TLS方式认证身份。

docker swarm MTLS认证、通信流图图:

docker swarm 集群:网络安全 TLS 分析

SSL/TLS 在TCP/UDP上应用层下,SSL/TLS协议代码被封装于golang的crypto包中,所以只分析在swarm代码中,如何使用MTLS进行加密通信,不涉及SSL/TLS协议代码。

几个验证过程注意:

1. 在新节点建立从CA server获取根CA的自签名证书时,由于没有CA证书无法进行认证,所以这次通信是没有使用TLS认证授权的,下载文件swarm-root-ca.cert使用token散列验证;

2. 新加入节点,向CA server 请求证书,此时新节点没有CA签名证书,无法和server进行双向认证,这个过程使用单向认证 –→ 客户端验证CA server 身份;

3. 上述情况的外,都使用双向认证(MTLS);

获取根CA证书代码

//GetRemoteCA

insecureCreds := credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}) //不使用TLS验证

opts := []grpc.DialOption {

grpc.WithTransportCredentials(insecureCreds),

grpc.WithBackoffMaxDelay(10 * time.Second),

grpc.WithPicker(picker)

}

firstAddr, err := picker.PickAddr()

if err != nil {

return RootCA{}, err

}

conn, err := grpc.Dial(firstAddr, opts...)

  获取节点签名证书

//GetRemoteSignedCertificate

if creds == nil { //请求签名证书时,此为nil

creds = credentials.NewTLS(&tls.Config{ServerName: CARole, RootCAs: rootCAPool})//只验证CARole身份

}

opts := []grpc.DialOption{

grpc.WithTransportCredentials(creds),

grpc.WithBackoffMaxDelay(10 * time.Second),

grpc.WithPicker(picker)

}

firstAddr, err := picker.PickAddr()

if err != nil {

return nil, err

}

conn, err := grpc.Dial(firstAddr, opts...)

  其他情况双向认证

LoadOrCreateSecurityConfig函数:

//LoadOrCreateSecurityConfig

clientTLSCreds, serverTLSCreds, err = LoadTLSCreds(rootCA, paths.Node) //加载磁盘中证书和私钥

if err != nil { //磁盘没有证书包错(一般节点第一次加入或者创建集群时),加载失败

log.Debugf("no valid local TLS credentials found: %v", err)

var (

tlsKeyPair *tls.Certificate

err error

)

//Leader节点,自己为自己创建节点信息,并使用为自己创建证书并用CA签名

if rootCA.CanSign() { //Leader节点才可以启动签名证书服务,所以Leader节点此处为真

// Create a new random ID for this certificate

cn := identity.NewID()

org := identity.NewID()

if nodeInfo != nil {

nodeInfo <- api.IssueNodeCertificateResponse{

NodeID: cn,

NodeMembership: api.NodeMembershipAccepted,

}

}

tlsKeyPair, err = rootCA.IssueAndSaveNewCertificates(paths.Node, cn, proposedRole, org) //创建并签名证书

if err != nil {

return nil, err

}

} else { //非Leader节点,创建证书请求并向Leader 或CA 服务请求签名证书

// There was an error loading our Credentials, let's get a new certificate issued

// Last argument is nil because at this point we don't have any valid TLS creds

tlsKeyPair, err = rootCA.RequestAndSaveNewCertificates(ctx, paths.Node, token, picker, nil, nodeInfo)

if err != nil {

return nil, err

}

}

// Create the Server TLS Credentials for this node. These will not be used by agents.

serverTLSCreds, err = rootCA.NewServerTLSCredentials(tlsKeyPair) //客户端TLS连接配置

if err != nil {

return nil, err

}

// Create a TLSConfig to be used when this node connects as a client to another remote node.

// We're using ManagerRole as remote serverName for TLS host verification

clientTLSCreds, err = rootCA.NewClientTLSCredentials(tlsKeyPair, ManagerRole) //服务端TLS连接配置

if err != nil {

return nil, err

}

log.Debugf("new TLS credentials generated: %s.", paths.Node.Cert)

} else {

if nodeInfo != nil {

nodeInfo <- api.IssueNodeCertificateResponse{

NodeID: clientTLSCreds.NodeID(),

NodeMembership: api.NodeMembershipAccepted,

}

}

log.Debugf("loaded local TLS credentials: %s.", paths.Node.Cert)

}

return NewSecurityConfig(&rootCA, clientTLSCreds, serverTLSCreds), nil //LTS连接配置

LoadOrCreateSecurityConfig函数返回了TLS安全双向认证的连接配置,此后使用安全连接配置进行安全通行;

例如

//agent/node.go run函数

...

securityConfig, err := ca.LoadOrCreateSecurityConfig(ctx, certDir, n.config.JoinToken, ca.ManagerRole, picker.NewPicker(n.remotes), issueResponseChan)

...

managerErr = n.runManager(ctx, securityConfig, managerReady) // 使用securityConfig安全配置

...

agentErr = n.runAgent(ctx, db, securityConfig.ClientTLSCreds, agentReady) //使用securityConfig安全配置

由于manager既要做服务端又要做客户端,所以服务端和客户端的双向安全配置;

NewClientTLSCredentials、NewServerTLSCredentials 底层调用 golang crypto 包里面tls安全认证类。

以上是 docker swarm集群网络安全TLS 的全部内容, 来源链接: utcz.com/a/125720.html

回到顶部