kubeadm修改默认证书有效期,解决证书过期问题

kubeadm 修改默认证书有效期

前言

出于安全考虑,k8s 团队推荐定期更新版本,因此kubeadm生成的证书,有效期默认在代码中写死为1年,一旦证书过期,k8s集群将会崩溃,因此,续期 or 升级,成了一个一年一度必选题。但在生产环境中,每一次版本更新可能存在未知的风险,给已经稳定运行的集群带来诸多不确定性,而每年续期一次证书,对管理不太友好,频繁手动操作也可能会带来额外的风险。去年部署的1.14的集群,证书快要到期了,现在决定修改kubeadm的源码重新编译,将续期证书的有效期进行延长。本篇记载分析源码中证书更新的过程。

修改源码

准备

首先拉取代码,切换到指定k8s版本的tag,过程参考之前源码分析的第一篇文章:

k8s源码阅读笔记-准备工作

也可直接下载相应版本的源码压缩包:

wget https://codeload.github.com/kubernetes/kubernetes/tar.gz/v1.14.3

代码结构

kubeadm代码结构如下,我们根据cmd命令入手,来找相应的生成证书的逻辑具体在哪。

50

证书生成方式分为两种,一种是执行**kubeadm init命令初始化生成的证书,一种是执行kubeadm alpha certs renew**,分别来看下。

Init证书

根据cmd的语义,可以很方便的找到相应的逻辑在哪个文件内,例如kubeadm init 命令的逻辑,在cmd/kubeadm/app/cmd/init.go文件内。

通过函数名称,可以很容易辨认哪个是证书操作的函数,一级一级地查找:

cmd/kubeadm/app/cmd/init.go:176

initRunner.AppendPhase(phases.NewCertsPhase())

==> cmd/kubeadm/app/cmd/phases/init/certs.go:62

Phases: newCertSubPhases()

==> cmd/kubeadm/app/cmd/phases/init/certs.go:96

return certsphase.CreateCertAndKeyFilesWithCA(cert, caCert, cfg)

==> cmd/kubeadm/app/phases/certs/certs.go:161

// 这里分两步,第一步生成证书 ,第二步,证书写入磁盘
func (k *KubeadmCert) CreateFromCA(ic *kubeadmapi.InitConfiguration, caCert *x509.Certificate, caKey *rsa.PrivateKey) error {
   cfg, err := k.GetConfig(ic)
   if err != nil {
      return errors.Wrapf(err, "couldn't create %q certificate", k.Name)
   }
   // 生成证书
   cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg)
   if err != nil {
      return err
   }
   // 写入磁盘
   err = writeCertificateFilesIfNotExist(
      ic.CertificatesDir,
      k.BaseName,
      caCert,
      cert,
      key,
      cfg,
   )

   if err != nil {
      return errors.Wrapf(err, "failed to write or validate certificate %q", k.Name)
   }

   return nil
}

CreateFromCA函数分为两步,第一步生成证书 ,第二步,证书写入磁盘。生成证书这里使用的是单独的封装pkiutil.NewCertAndKey()方法,猜测多个地方会使用到它。

生成证书

cmd/kubeadm/app/util/pkiutil/pki_helpers.go:76

==> cmd/kubeadm/app/util/pkiutil/pki_helpers.go:82

==> cmd/kubeadm/app/util/pkiutil/pki_helpers.go:557

// NewSignedCert creates a signed certificate using the given CA certificate and key
func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certificate, caKey crypto.Signer) (*x509.Certificate, error) {
	serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64))
	if err != nil {
		return nil, err
	}
	if len(cfg.CommonName) == 0 {
		return nil, errors.New("must specify a CommonName")
	}
	if len(cfg.Usages) == 0 {
		return nil, errors.New("must specify at least one ExtKeyUsage")
	}

	certTmpl := x509.Certificate{
		Subject: pkix.Name{
			CommonName:   cfg.CommonName,
			Organization: cfg.Organization,
		},
		DNSNames:     cfg.AltNames.DNSNames,
		IPAddresses:  cfg.AltNames.IPs,
		SerialNumber: serial,
		NotBefore:    caCert.NotBefore,
    // 过期时间在这里,默认是当前时间+1年,这里可以修改一下,改成10年
		// NotAfter:     time.Now().Add(duration365d).UTC(),
    NotAfter:     time.Now().Add(duration365d * 10).UTC(),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  cfg.Usages,
	}
	certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &certTmpl, caCert, key.Public(), caKey)
	if err != nil {
		return nil, err
	}
	return x509.ParseCertificate(certDERBytes)
}

这里是init证书默认定义有效期的位置,renew证书应该也是调用的这里,是不是呢?还是再来确认一下。

Renew证书

根据cmd的语义,可以很方便的找到相应的逻辑在哪个文件内,例如kubeadm alpha certs renew命令的逻辑,在cmd/kubeadm/app/cmd/alpha/certs.go文件内。

通过函数名称,可以很容易初步锁定在newCmdCertsRenewal()函数,再开始一级一级地查找:

cmd/kubeadm/app/cmd/alpha/certs.go:60

==> cmd/kubeadm/app/cmd/alpha/certs.go:68

==> cmd/kubeadm/app/cmd/alpha/certs.go:99

// get the implementation of renewing this certificate
			renewalFunc := generateRenewalFunction(cert, caCert, cfg)

==> cmd/kubeadm/app/cmd/alpha/certs.go:151

// 更新已存在的证书
err = renewal.RenewExistingCert(internalcfg.CertificatesDir, cert.BaseName, renewer)

==> cmd/kubeadm/app/phases/certs/renewal/renewal.go:42

newCert, newKey, err := impl.Renew(cfg)

这里发现impl.Renew()是个接口方法,倒回上一级去查看impl

==> cmd/kubeadm/app/cmd/alpha/certs.go:148

		renewer, err := getRenewer(cfg, caCert.BaseName)

==> cmd/kubeadm/app/cmd/alpha/certs.go:166

func getRenewer(cfg *renewConfig, caCertBaseName string) (renewal.Interface, error) {
	if cfg.useAPI {
		kubeConfigPath := cmdutil.GetKubeConfigPath(cfg.kubeconfigPath)
		client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
		if err != nil {
			return nil, err
		}
    // k8s自管理式的刷新证书
		return renewal.NewCertsAPIRenawal(client), nil
	}

	caCert, caKey, err := certsphase.LoadCertificateAuthority(cfg.cfg.CertificatesDir, caCertBaseName)
	if err != nil {
		return nil, err
	}
  // 刷新本地文件证书
	return renewal.NewFileRenewal(caCert, caKey), nil
}

这里的证书刷新方式分为两种,一种是k8s自管理式的证书,一种是本地文件证书(默认/etc/kubernetes/pki/),两种方式有何差别,参考官方文档:

Certificate Management with kubeadm

自管理的证书有效期默认3个月。而一般使用的都是本地证书文件,所以直接看第二种

cmd/kubeadm/app/phases/certs/renewal/filerenewal.go:34

// NewFileRenewal takes a certificate pair to construct the Interface.
func NewFileRenewal(caCert *x509.Certificate, caKey *rsa.PrivateKey) Interface {
	return &FileRenewal{
		caCert: caCert,
		caKey:  caKey,
	}
}

// Renew takes a certificate using the cert and key
func (r *FileRenewal) Renew(cfg *certutil.Config) (*x509.Certificate, *rsa.PrivateKey, error) {
	return pkiutil.NewCertAndKey(r.caCert, r.caKey, cfg)
}

Renew方法在下面,果然,也是调用的 pkiutil.NewCertAndKey()方法,也即是说,在上面修改那一行代码,init和renew的时候生成证书都会调用,都会生效。

修改完成,开始重新编译!

编译

go环境问题这里不赘述了,网上文章很多,跳过。

如果是linux,直接执行:

make WHAT=cmd/kubeadm GOFLAGS=-v

如果是mac,执行:

make cross WHAT=cmd/kubeadm GOFLAGS=-v

报错:

./hack/run-in-gopath.sh:行33: _output/bin/deepcopy-gen: 权限不够
make[1]: *** [gen_deepcopy] 错误 1
make: *** [generated_files] 错误 2

解决办法:

chmod +x _output/bin/deepcopy-gen

重新编译,执行完成后,编译好的二进制文件路径为:

./_output/local/bin/linux/amd64/kubeadm

使用此文件,覆盖原来的kubeadm文件:

mv /usr/bin/kubeadm /usr/bin/kubeadm-bak
cp ./_output/local/bin/linux/amd64/kubeadm  /usr/bin/kubeadm

验证

init新建证书

先看看kubeadm init时创建的证书

[root@vm254011 ~]# kubeadm init --config kubeadmin-config.yaml

...
Your Kubernetes control-plane has initialized successfully!
...

[root@vm254011 ~]# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text |grep ' Not'
            Not Before: May 11 12:17:34 2020 GMT
            Not After : May  9 12:17:34 2030 GMT

[root@vm254011 ~]# date
2020年 05月 11日 星期二 20:37:55 CST

可以看到,证书有效期为10年(默认时间为UTC时间,实际本地时区为UTC+8)

renew刷新证书

下面的步骤适用于 证书续期证书过期后 的处理方法

过了一个晚上,再来对上面init创建的证书进行renew:

# 如果etcd也是一起托管的,直接执行一条命令即可:
# kubeadm alpha certs renew all --config=./kubeadmin-config.yaml

# 因为我的环境里etcd不是static pod托管,而是外部二进制部署的,所以这里不需要更新etcd的证书
[root@vm254011 ~]# kubeadm alpha certs renew apiserver --config=./kubeadmin-config.yaml
[root@vm254011 ~]# kubeadm alpha certs renew apiserver-kubelet-client --config=./kubeadmin-config.yaml
[root@vm254011 ~]# kubeadm alpha certs renew front-proxy-client --config=./kubeadmin-config.yaml

[root@vm254011 ~]# openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text |grep ' Not'
            Not Before: May 11 12:17:34 2020 GMT
            Not After : May 10 00:36:16 2030 GMT

[root@vm254011 ~]# date
2020年 05月 12日 星期二 08:37:01 CST

[root@vm254011 ~]# stat /etc/kubernetes/pki/apiserver
最近访问:2020-05-12 08:36:23.764591842 +0800
最近更改:2020-05-12 08:36:16.395438142 +0800
最近改动:2020-05-12 08:36:16.395438142 +0800

# 基于新证书重新生成各个组件的配置文件
kubeadm alpha kubeconfig user --org system:masters --client-name kubernetes-admin  > /etc/kubernetes/admin.conf
kubeadm alpha kubeconfig user --client-name system:kube-controller-manager > /etc/kubernetes/controller-manager.conf
kubeadm alpha kubeconfig user --client-name system:kube-scheduler > /etc/kubernetes/scheduler.conf

# 重启kubelet组件
systemctl restart kubelet
# 重启controller、scheduler、apiserver3个组件docker容器
# docker restart 命令

# 复制证书和配置文件到其他master
scp -r /etc/kubernetes/pki/* root@vm254012:/etc/kubernetes/pki/
scp /etc/kubernetes/scheduler.conf root@vm254012:/etc/kubernetes/
scp /etc/kubernetes/controller-manager.conf root@vm254012:/etc/kubernetes/
scp /etc/kubernetes/admin.conf root@vm254012:/etc/kubernetes/

# 其他master同样重启kubelet和3个组件docker容器
# ...

可以看到,renew的证书,有效期也是长达10年的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值