123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /*
- Copyright 2019 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package renewal
- import (
- "crypto/x509"
- "sort"
- "github.com/pkg/errors"
- clientset "k8s.io/client-go/kubernetes"
- certutil "k8s.io/client-go/util/cert"
- kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
- kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
- certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
- "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
- )
- // Manager can be used to coordinate certificate renewal and related processes,
- // like CSR generation or checking certificate expiration
- type Manager struct {
- // cfg holds the kubeadm ClusterConfiguration
- cfg *kubeadmapi.ClusterConfiguration
- // kubernetesDir holds the directory where kubeConfig files are stored
- kubernetesDir string
- // certificates contains the certificateRenewHandler controlled by this manager
- certificates map[string]*CertificateRenewHandler
- // cas contains the CAExpirationHandler related to the certificates that are controlled by this manager
- cas map[string]*CAExpirationHandler
- }
- // CertificateRenewHandler defines required info for renewing a certificate
- type CertificateRenewHandler struct {
- // Name of the certificate to be used for UX.
- // This value can be used to trigger operations on this certificate
- Name string
- // LongName of the certificate to be used for UX
- LongName string
- // FileName defines the name (or the BaseName) of the certificate file
- FileName string
- // CAName defines the name for the CA on which this certificate depends
- CAName string
- // CABaseName defines the base name for the CA that should be used for certificate renewal
- CABaseName string
- // readwriter defines a CertificateReadWriter to be used for certificate renewal
- readwriter certificateReadWriter
- }
- // CAExpirationHandler defines required info for CA expiration check
- type CAExpirationHandler struct {
- // Name of the CA to be used for UX.
- // This value can be used to trigger operations on this CA
- Name string
- // LongName of the CA to be used for UX
- LongName string
- // FileName defines the name (or the BaseName) of the CA file
- FileName string
- // readwriter defines a CertificateReadWriter to be used for CA expiration check
- readwriter certificateReadWriter
- }
- // NewManager return a new certificate renewal manager ready for handling certificates in the cluster
- func NewManager(cfg *kubeadmapi.ClusterConfiguration, kubernetesDir string) (*Manager, error) {
- rm := &Manager{
- cfg: cfg,
- kubernetesDir: kubernetesDir,
- certificates: map[string]*CertificateRenewHandler{},
- cas: map[string]*CAExpirationHandler{},
- }
- // gets the list of certificates that are expected according to the current cluster configuration
- certListFunc := certsphase.GetDefaultCertList
- if cfg.Etcd.External != nil {
- certListFunc = certsphase.GetCertsWithoutEtcd
- }
- certTree, err := certListFunc().AsMap().CertTree()
- if err != nil {
- return nil, err
- }
- // create a CertificateRenewHandler for each signed certificate in the certificate tree;
- // NB. we are not offering support for renewing CAs; this would cause serious consequences
- for ca, certs := range certTree {
- for _, cert := range certs {
- // create a ReadWriter for certificates stored in the K8s local PKI
- pkiReadWriter := newPKICertificateReadWriter(rm.cfg.CertificatesDir, cert.BaseName)
- // adds the certificateRenewHandler.
- // PKI certificates are indexed by name, that is a well know constant defined
- // in the certsphase package and that can be reused across all the kubeadm codebase
- rm.certificates[cert.Name] = &CertificateRenewHandler{
- Name: cert.Name,
- LongName: cert.LongName,
- FileName: cert.BaseName,
- CAName: ca.Name,
- CABaseName: ca.BaseName, //Nb. this is a path for etcd certs (they are stored in a subfolder)
- readwriter: pkiReadWriter,
- }
- }
- pkiReadWriter := newPKICertificateReadWriter(rm.cfg.CertificatesDir, ca.BaseName)
- rm.cas[ca.Name] = &CAExpirationHandler{
- Name: ca.Name,
- LongName: ca.LongName,
- FileName: ca.BaseName,
- readwriter: pkiReadWriter,
- }
- }
- // gets the list of certificates that should be considered for renewal
- kubeConfigs := []struct {
- longName string
- fileName string
- }{
- {
- longName: "certificate embedded in the kubeconfig file for the admin to use and for kubeadm itself",
- fileName: kubeadmconstants.AdminKubeConfigFileName,
- },
- {
- longName: "certificate embedded in the kubeconfig file for the controller manager to use",
- fileName: kubeadmconstants.ControllerManagerKubeConfigFileName,
- },
- {
- longName: "certificate embedded in the kubeconfig file for the scheduler manager to use",
- fileName: kubeadmconstants.SchedulerKubeConfigFileName,
- },
- //NB. we are excluding KubeletKubeConfig from renewal because management of this certificate is delegated to kubelet
- }
- // create a CertificateRenewHandler for each kubeConfig file
- for _, kubeConfig := range kubeConfigs {
- // create a ReadWriter for certificates embedded in kubeConfig files
- kubeConfigReadWriter := newKubeconfigReadWriter(kubernetesDir, kubeConfig.fileName,
- rm.cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
- // adds the certificateRenewHandler.
- // Certificates embedded kubeConfig files in are indexed by fileName, that is a well know constant defined
- // in the kubeadm constants package and that can be reused across all the kubeadm codebase
- rm.certificates[kubeConfig.fileName] = &CertificateRenewHandler{
- Name: kubeConfig.fileName, // we are using fileName as name, because there is nothing similar outside
- LongName: kubeConfig.longName,
- FileName: kubeConfig.fileName,
- CABaseName: kubeadmconstants.CACertAndKeyBaseName, // all certificates in kubeConfig files are signed by the Kubernetes CA
- readwriter: kubeConfigReadWriter,
- }
- }
- return rm, nil
- }
- // Certificates returns the list of certificates controlled by this Manager
- func (rm *Manager) Certificates() []*CertificateRenewHandler {
- certificates := []*CertificateRenewHandler{}
- for _, h := range rm.certificates {
- certificates = append(certificates, h)
- }
- sort.Slice(certificates, func(i, j int) bool { return certificates[i].Name < certificates[j].Name })
- return certificates
- }
- // CAs returns the list of CAs related to the certificates that are controlled by this manager
- func (rm *Manager) CAs() []*CAExpirationHandler {
- cas := []*CAExpirationHandler{}
- for _, h := range rm.cas {
- cas = append(cas, h)
- }
- sort.Slice(cas, func(i, j int) bool { return cas[i].Name < cas[j].Name })
- return cas
- }
- // RenewUsingLocalCA executes certificate renewal using local certificate authorities for generating new certs.
- // For PKI certificates, use the name defined in the certsphase package, while for certificates
- // embedded in the kubeConfig files, use the kubeConfig file name defined in the kubeadm constants package.
- // If you use the CertificateRenewHandler returned by Certificates func, handler.Name already contains the right value.
- func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) {
- handler, ok := rm.certificates[name]
- if !ok {
- return false, errors.Errorf("%s is not a valid certificate for this cluster", name)
- }
- // checks if the certificate is externally managed (CA certificate provided without the certificate key)
- externallyManaged, err := rm.IsExternallyManaged(handler.CABaseName)
- if err != nil {
- return false, err
- }
- // in case of external CA it is not possible to renew certificates, then return early
- if externallyManaged {
- return false, nil
- }
- // reads the current certificate
- cert, err := handler.readwriter.Read()
- if err != nil {
- return false, err
- }
- // extract the certificate config
- cfg := &pkiutil.CertConfig{
- Config: certToConfig(cert),
- PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(),
- }
- // reads the CA
- caCert, caKey, err := certsphase.LoadCertificateAuthority(rm.cfg.CertificatesDir, handler.CABaseName)
- if err != nil {
- return false, err
- }
- // create a new certificate with the same config
- newCert, newKey, err := NewFileRenewer(caCert, caKey).Renew(cfg)
- if err != nil {
- return false, errors.Wrapf(err, "failed to renew certificate %s", name)
- }
- // writes the new certificate to disk
- err = handler.readwriter.Write(newCert, newKey)
- if err != nil {
- return false, err
- }
- return true, nil
- }
- // RenewUsingCSRAPI executes certificate renewal uses the K8s certificate API.
- // For PKI certificates, use the name defined in the certsphase package, while for certificates
- // embedded in the kubeConfig files, use the kubeConfig file name defined in the kubeadm constants package.
- // If you use the CertificateRenewHandler returned by Certificates func, handler.Name already contains the right value.
- func (rm *Manager) RenewUsingCSRAPI(name string, client clientset.Interface) error {
- handler, ok := rm.certificates[name]
- if !ok {
- return errors.Errorf("%s is not a valid certificate for this cluster", name)
- }
- // reads the current certificate
- cert, err := handler.readwriter.Read()
- if err != nil {
- return err
- }
- // extract the certificate config
- cfg := &pkiutil.CertConfig{
- Config: certToConfig(cert),
- PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(),
- }
- // create a new certificate with the same config
- newCert, newKey, err := NewAPIRenewer(client).Renew(cfg)
- if err != nil {
- return errors.Wrapf(err, "failed to renew certificate %s", name)
- }
- // writes the new certificate to disk
- err = handler.readwriter.Write(newCert, newKey)
- if err != nil {
- return err
- }
- return nil
- }
- // CreateRenewCSR generates CSR request for certificate renewal.
- // For PKI certificates, use the name defined in the certsphase package, while for certificates
- // embedded in the kubeConfig files, use the kubeConfig file name defined in the kubeadm constants package.
- // If you use the CertificateRenewHandler returned by Certificates func, handler.Name already contains the right value.
- func (rm *Manager) CreateRenewCSR(name, outdir string) error {
- handler, ok := rm.certificates[name]
- if !ok {
- return errors.Errorf("%s is not a known certificate", name)
- }
- // reads the current certificate
- cert, err := handler.readwriter.Read()
- if err != nil {
- return err
- }
- // extracts the certificate config
- cfg := &pkiutil.CertConfig{
- Config: certToConfig(cert),
- PublicKeyAlgorithm: rm.cfg.PublicKeyAlgorithm(),
- }
- // generates the CSR request and save it
- csr, key, err := pkiutil.NewCSRAndKey(cfg)
- if err != nil {
- return errors.Wrapf(err, "failure while generating %s CSR and key", name)
- }
- if err := pkiutil.WriteKey(outdir, name, key); err != nil {
- return errors.Wrapf(err, "failure while saving %s key", name)
- }
- if err := pkiutil.WriteCSR(outdir, name, csr); err != nil {
- return errors.Wrapf(err, "failure while saving %s CSR", name)
- }
- return nil
- }
- // CertificateExists returns true if a certificate exists.
- func (rm *Manager) CertificateExists(name string) (bool, error) {
- handler, ok := rm.certificates[name]
- if !ok {
- return false, errors.Errorf("%s is not a known certificate", name)
- }
- return handler.readwriter.Exists(), nil
- }
- // GetCertificateExpirationInfo returns certificate expiration info.
- // For PKI certificates, use the name defined in the certsphase package, while for certificates
- // embedded in the kubeConfig files, use the kubeConfig file name defined in the kubeadm constants package.
- // If you use the CertificateRenewHandler returned by Certificates func, handler.Name already contains the right value.
- func (rm *Manager) GetCertificateExpirationInfo(name string) (*ExpirationInfo, error) {
- handler, ok := rm.certificates[name]
- if !ok {
- return nil, errors.Errorf("%s is not a known certificate", name)
- }
- // checks if the certificate is externally managed (CA certificate provided without the certificate key)
- externallyManaged, err := rm.IsExternallyManaged(handler.CABaseName)
- if err != nil {
- return nil, err
- }
- // reads the current certificate
- cert, err := handler.readwriter.Read()
- if err != nil {
- return nil, err
- }
- // returns the certificate expiration info
- return newExpirationInfo(name, cert, externallyManaged), nil
- }
- // CAExists returns true if a certificate authority exists.
- func (rm *Manager) CAExists(name string) (bool, error) {
- handler, ok := rm.cas[name]
- if !ok {
- return false, errors.Errorf("%s is not a known certificate", name)
- }
- return handler.readwriter.Exists(), nil
- }
- // GetCAExpirationInfo returns CA expiration info.
- func (rm *Manager) GetCAExpirationInfo(name string) (*ExpirationInfo, error) {
- handler, ok := rm.cas[name]
- if !ok {
- return nil, errors.Errorf("%s is not a known CA", name)
- }
- // checks if the CA is externally managed (CA certificate provided without the certificate key)
- externallyManaged, err := rm.IsExternallyManaged(handler.FileName)
- if err != nil {
- return nil, err
- }
- // reads the current CA
- ca, err := handler.readwriter.Read()
- if err != nil {
- return nil, err
- }
- // returns the CA expiration info
- return newExpirationInfo(name, ca, externallyManaged), nil
- }
- // IsExternallyManaged checks if we are in the external CA case (CA certificate provided without the certificate key)
- func (rm *Manager) IsExternallyManaged(caBaseName string) (bool, error) {
- switch caBaseName {
- case kubeadmconstants.CACertAndKeyBaseName:
- externallyManaged, err := certsphase.UsingExternalCA(rm.cfg)
- if err != nil {
- return false, errors.Wrapf(err, "Error checking external CA condition for %s certificate authority", caBaseName)
- }
- return externallyManaged, nil
- case kubeadmconstants.FrontProxyCACertAndKeyBaseName:
- externallyManaged, err := certsphase.UsingExternalFrontProxyCA(rm.cfg)
- if err != nil {
- return false, errors.Wrapf(err, "Error checking external CA condition for %s certificate authority", caBaseName)
- }
- return externallyManaged, nil
- case kubeadmconstants.EtcdCACertAndKeyBaseName:
- return false, nil
- default:
- return false, errors.Errorf("unknown certificate authority %s", caBaseName)
- }
- }
- func certToConfig(cert *x509.Certificate) certutil.Config {
- return certutil.Config{
- CommonName: cert.Subject.CommonName,
- Organization: cert.Subject.Organization,
- AltNames: certutil.AltNames{
- IPs: cert.IPAddresses,
- DNSNames: cert.DNSNames,
- },
- Usages: cert.ExtKeyUsage,
- }
- }
|