背景介绍
kubeadm是Kubernetes提供的自动化部署方案,可以极大地简化广为诟病的Kubernetes集群搭建的复杂性。kubeadm init
命令对Kubernetes集群主节点的初始化流程中包含了以下若干步骤:
- PreflightPhase
- KubeletStartPhase
- CertsPhase
- KubeConfigPhase
- ControlPlanePhase
- EtcdPhase
- UploadConfigPhase
- UploadCertsPhase
- MarkControlPlanePhase
- BootstrapTokenPhase
- AddonPhase
CertsPhase
用于生成https服务端/客户端需要使用的证书、公钥/秘钥等。
这一部分大量使用到了go函数库中crypto模块的功能。一般而言,crypto模块的功能在日常开发中涉及较少,对kubeadm代码会造成不小的障碍。本文梳理了go语言标准库中crypto模块常用的场景,以及针对各场景给出了简短的代码示例。本文主要关注加密、解密等安全功能在go语言中的实践,相关的理论知识(比如各算法的原理、产生背景以及应用)此处不再赘述。
小文件的hash计算
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"fmt"
"io/ioutil"
)
data, err := ioutil.ReadFile(filename)
// 计算文件内容的hash值,并输出
fmt.Printf("Md5: %x\n\n", md5.Sum(data))
fmt.Printf("Sha1: %x\n\n", sha1.Sum(data))
fmt.Printf("Sha256: %x\n\n", sha256.Sum256(data))
fmt.Printf("Sha512: %x\n\n", sha512.Sum512(data))
大文件的hash计算
// 打开文件
file, err := os.Open(filename)
// 构建hasher对象(实现了writer接口)
hasher := md5.New()
_, err = io.Copy(hasher, file)
checksum := hasher.Sum(nil)
对称加密
生成随机数的三种方法
我们可以将生成随机数视为生成秘钥。
方法1
import (
"crypto/rand"
"math"
"math/big"
)
limit := int64(math.MaxInt64) // 允许的最大值
randInt, err := rand.Int(rand.Reader, big.NewInt(limit))
方法2
binary.Read()
方法将读取足够的字节以填充相应的数据类型
import (
"crypto/rand"
"encoding/binary"
)
var number uint32
err = binary.Read(rand.Reader, binary.BigEndian, &number)
方法3
直接生成相应长度的字节码slice
import (
"crypto/rand"
)
numBytes := 4
randomBytes := make([]byte, numBytes)
rand.Read(randomBytes)
fmt.Println("Random byte values: ", randomBytes)
加密算法
常用的对称加密算法是AES,而我们偶尔能够听到的DES是一个相对于AES更老的版本。下面我们演示的例子是基于go标准库中提供的aes算法实现。初始向量(initialization vector)的作用是使得对相同消息的加密结果也不一样,进一步提升数据的安全性。
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
)
func encrypt(key, message []byte) ([]byte, error) {
// 初始化block cipher
block, err := aes.NewCipher(key)
// 创建字节slice以持有后续产生的加密消息
cipherText := make([]byte, aes.BlockSize+len(message))
// 生成Initialization Vector (IV) nonce
// 这部分数据存储在slice的头部
// 其长度与AES的block大小一样
iv := cipherText[:aes.BlockSize]
_, err = io.ReadFull(rand.Reader, iv)
// 选择block cipher的工作模式
// 此处使用cipher feedback (CFB)模式
// 当然,CBCEncrypter也是一个备选项
cfb := cipher.NewCFBEncrypter(block, iv)
// 生成加密的消息,并将其存储在剩下的字节slice中
cfb.XORKeyStream(cipherText[aes.BlockSize:], message)
return cipherText, nil
}
解密
// AES解密
func decrypt(key, cipherText []byte) ([]byte, error) {
// 初始化block cipher
block, err := aes.NewCipher(key)
// 将IV nonce与加密消息分开
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
// 使用CFB block模式将加密消息解密
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(cipherText, cipherText)
return cipherText, nil
}
非对称加密
由于对称加密需要通讯双方共享秘钥,这一要求在互联网环境中会成为安全的隐患。由此安全专家们提出了RSA等非对称加密算法。
生成公钥/秘钥对
等价的openssl命令如下
# 生成秘钥
openssl genrsa -out priv.pem 2048
# 从私钥中提取公钥
openssl rsa -in priv.pem -pubout -out public.pem
对应的go代码如下
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"os"
)
// 生成公钥/秘钥对,并将其存储在PEM格式的文件中
privateKey, err := rsa.GenerateKey(rand.Reader, keySize)
// 分别将秘钥公钥进行PEM格式的编码
privatePem := getPrivatePemFromKey(privateKey)
publicPem := generatePublicPemFromKey(privateKey.PublicKey)
// Save the PEM output to files
savePemToFile(privatePem, privatePemFilename)
savePemToFile(publicPem, publicPemFilename