kubeadm修改默認證書有效期

kubeadm 修改默認證書有效期

前言

出於安全考慮,k8s 團隊推薦定期更新版本,因此kubeadm生成的證書,有效期默認在代碼中寫死爲1年,一旦證書過期,k8s集羣將會崩潰,因此,續期 or 升級,成了一個一年一度必選題。但在生產環境中,每一次版本更新可能存在未知的風險,給已經穩定運行的集羣帶來諸多不確定性,而每年續期一次證書,對管理不太友好,頻繁手動操作也可能會帶來額外的風險。去年部署的1.14的集羣,證書快要到期了,現在決定修改kubeadm的源碼重新編譯,將續期證書的有效期進行延長。本篇記載分析源碼中證書更新的過程。

修改源碼

準備

首先拉取代碼,切換到指定k8s版本的tag,過程參考之前源碼分析的第一篇文章:

[k8s源碼閱讀筆記-準備工作

](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/README.md)

也可直接下載相應版本的源碼壓縮包:

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不是static pod託管,而是外部二進制部署的,所以這裏不需要更新etcd的證書
# 如果etcd也是一起託管的,直接執行一條命令即可:
# kubeadm alpha certs renew all --config=./kubeadmin-config.yaml

[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

可以看到,renew的證書,有效期也是長達10年的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章