您的位置:知识库 » 其他分类

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

作者: Jinhua_Wei  来源: CSDN  发布时间: 2020-07-09 16:10  阅读: 407 次  推荐: 1   原文链接   [收藏]  

  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节点生成。

  生成过程:

  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

  生成流程:

  执行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认证、通信流图图:

  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安全认证类。

1
0
标签:docker swarm

其他分类热门文章

    其他分类最新文章

      最新新闻

        热门新闻