mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 07:20:13 +00:00
certs-expiration
This commit is contained in:
parent
8ae998ceb6
commit
919826531c
@ -30,7 +30,7 @@ func NewCmdAlpha(in io.Reader, out io.Writer) *cobra.Command {
|
||||
Short: "Kubeadm experimental sub-commands",
|
||||
}
|
||||
|
||||
cmd.AddCommand(newCmdCertsUtility())
|
||||
cmd.AddCommand(newCmdCertsUtility(out))
|
||||
cmd.AddCommand(newCmdKubeletUtility())
|
||||
cmd.AddCommand(newCmdKubeConfigUtility(out))
|
||||
cmd.AddCommand(NewCmdSelfhosting(in))
|
||||
|
@ -18,10 +18,13 @@ package alpha
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
|
||||
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
|
||||
@ -54,10 +57,14 @@ var (
|
||||
Renew all known certificates necessary to run the control plane. Renewals are run unconditionally, regardless
|
||||
of expiration date. Renewals can also be run individually for more control.
|
||||
`)
|
||||
|
||||
expirationLongDesc = normalizer.LongDesc(`
|
||||
Checks expiration for the certificates in the local PKI managed by kubeadm.
|
||||
`)
|
||||
)
|
||||
|
||||
// newCmdCertsUtility returns main command for certs phase
|
||||
func newCmdCertsUtility() *cobra.Command {
|
||||
func newCmdCertsUtility(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "certs",
|
||||
Aliases: []string{"certificates"},
|
||||
@ -65,6 +72,7 @@ func newCmdCertsUtility() *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.AddCommand(newCmdCertsRenewal())
|
||||
cmd.AddCommand(newCmdCertsExpiration(out, kubeadmconstants.KubernetesDir))
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -118,7 +126,7 @@ func getRenewSubCommands(kdir string) []*cobra.Command {
|
||||
Short: fmt.Sprintf("Renew the %s", handler.LongName),
|
||||
Long: fmt.Sprintf(genericCertRenewLongDesc, handler.LongName),
|
||||
}
|
||||
addFlags(cmd, flags)
|
||||
addRenewFlags(cmd, flags)
|
||||
// get the implementation of renewing this certificate
|
||||
renewalFunc := func(handler *renewal.CertificateRenewHandler) func() {
|
||||
return func() { renewCert(flags, kdir, handler) }
|
||||
@ -140,13 +148,13 @@ func getRenewSubCommands(kdir string) []*cobra.Command {
|
||||
}
|
||||
},
|
||||
}
|
||||
addFlags(allCmd, flags)
|
||||
addRenewFlags(allCmd, flags)
|
||||
|
||||
cmdList = append(cmdList, allCmd)
|
||||
return cmdList
|
||||
}
|
||||
|
||||
func addFlags(cmd *cobra.Command, flags *renewFlags) {
|
||||
func addRenewFlags(cmd *cobra.Command, flags *renewFlags) {
|
||||
options.AddConfigFlag(cmd.Flags(), &flags.cfgPath)
|
||||
options.AddCertificateDirFlag(cmd.Flags(), &flags.cfg.CertificatesDir)
|
||||
options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeconfigPath)
|
||||
@ -197,3 +205,70 @@ func renewCert(flags *renewFlags, kdir string, handler *renewal.CertificateRenew
|
||||
}
|
||||
fmt.Printf("%s renewed\n", handler.LongName)
|
||||
}
|
||||
|
||||
// newCmdCertsExpiration creates a new `cert check-expiration` command.
|
||||
func newCmdCertsExpiration(out io.Writer, kdir string) *cobra.Command {
|
||||
flags := &expirationFlags{
|
||||
cfg: kubeadmapiv1beta2.InitConfiguration{
|
||||
ClusterConfiguration: kubeadmapiv1beta2.ClusterConfiguration{
|
||||
// Setting kubernetes version to a default value in order to allow a not necessary internet lookup
|
||||
KubernetesVersion: constants.CurrentKubernetesVersion.String(),
|
||||
},
|
||||
},
|
||||
}
|
||||
// Default values for the cobra help text
|
||||
kubeadmscheme.Scheme.Default(&flags.cfg)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "check-expiration",
|
||||
Short: "Check certificates expiration for a Kubernetes cluster",
|
||||
Long: expirationLongDesc,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internalcfg, err := configutil.LoadOrDefaultInitConfiguration(flags.cfgPath, &flags.cfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Get a renewal manager for the given cluster configuration
|
||||
rm, err := renewal.NewManager(&internalcfg.ClusterConfiguration, kdir)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Get all the certificate expiration info
|
||||
yesNo := func(b bool) string {
|
||||
if b {
|
||||
return "yes"
|
||||
}
|
||||
return "no"
|
||||
}
|
||||
w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0)
|
||||
fmt.Fprintln(w, "CERTIFICATE\tEXPIRES\tRESIDUAL TIME\tEXTERNALLY MANAGED")
|
||||
for _, handler := range rm.Certificates() {
|
||||
e, err := rm.GetExpirationInfo(handler.Name)
|
||||
if err != nil {
|
||||
kubeadmutil.CheckErr(err)
|
||||
}
|
||||
|
||||
s := fmt.Sprintf("%s\t%s\t%s\t%-8v",
|
||||
e.Name,
|
||||
e.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
|
||||
duration.ShortHumanDuration(e.ResidualTime()),
|
||||
yesNo(e.ExternallyManaged),
|
||||
)
|
||||
|
||||
fmt.Fprintln(w, s)
|
||||
}
|
||||
w.Flush()
|
||||
},
|
||||
}
|
||||
addExpirationFlags(cmd, flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
type expirationFlags struct {
|
||||
cfgPath string
|
||||
cfg kubeadmapiv1beta2.InitConfiguration
|
||||
}
|
||||
|
||||
func addExpirationFlags(cmd *cobra.Command, flags *expirationFlags) {
|
||||
options.AddConfigFlag(cmd.Flags(), &flags.cfgPath)
|
||||
options.AddCertificateDirFlag(cmd.Flags(), &flags.cfg.CertificatesDir)
|
||||
}
|
||||
|
@ -42,6 +42,9 @@ const (
|
||||
// should be joined with KubernetesDir.
|
||||
TempDirForKubeadm = "tmp"
|
||||
|
||||
// CertificateValidity defines the validity for all the signed certificates generated by kubeadm
|
||||
CertificateValidity = time.Hour * 24 * 365
|
||||
|
||||
// CACertAndKeyBaseName defines certificate authority base name
|
||||
CACertAndKeyBaseName = "ca"
|
||||
// CACertName defines certificate name
|
||||
|
52
cmd/kubeadm/app/phases/certs/renewal/expiration.go
Normal file
52
cmd/kubeadm/app/phases/certs/renewal/expiration.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
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"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ExpirationInfo defines expiration info for a certificate
|
||||
type ExpirationInfo struct {
|
||||
// Name of the certificate
|
||||
// For PKI certificates, it is the name defined in the certsphase package, while for certificates
|
||||
// embedded in the kubeConfig files, it is 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.
|
||||
Name string
|
||||
|
||||
// ExpirationDate defines certificate expiration date
|
||||
ExpirationDate time.Time
|
||||
|
||||
// ExternallyManaged defines if the certificate is externally managed, that is when
|
||||
// the signing CA certificate is provided without the certificate key (In this case kubeadm can't renew the certificate)
|
||||
ExternallyManaged bool
|
||||
}
|
||||
|
||||
// newExpirationInfo returns a new ExpirationInfo
|
||||
func newExpirationInfo(name string, cert *x509.Certificate, externallyManaged bool) *ExpirationInfo {
|
||||
return &ExpirationInfo{
|
||||
Name: name,
|
||||
ExpirationDate: cert.NotAfter,
|
||||
ExternallyManaged: externallyManaged,
|
||||
}
|
||||
}
|
||||
|
||||
// ResidualTime returns the time missing to expiration
|
||||
func (e *ExpirationInfo) ResidualTime() time.Duration {
|
||||
return e.ExpirationDate.Sub(time.Now())
|
||||
}
|
37
cmd/kubeadm/app/phases/certs/renewal/expiration_test.go
Normal file
37
cmd/kubeadm/app/phases/certs/renewal/expiration_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
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"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExpirationInfo(t *testing.T) {
|
||||
validity := 365 * 24 * time.Hour
|
||||
cert := &x509.Certificate{
|
||||
NotAfter: time.Now().Add(validity),
|
||||
}
|
||||
|
||||
e := newExpirationInfo("x", cert, false)
|
||||
|
||||
if math.Abs(float64(validity-e.ResidualTime())) > float64(5*time.Second) { // using 5s of tolerance becase the function is not determinstic (it uses time.Now()) and we want to avoid flakes
|
||||
t.Errorf("expected IsInRenewalWindow equal to %v, saw %v", validity, e.ResidualTime())
|
||||
}
|
||||
}
|
@ -161,21 +161,14 @@ func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) {
|
||||
return false, errors.Errorf("%s is not a valid certificate for this cluster", name)
|
||||
}
|
||||
|
||||
// checks if the we are in the external CA case (CA certificate provided without the certificate key)
|
||||
var externalCA bool
|
||||
switch handler.CABaseName {
|
||||
case kubeadmconstants.CACertAndKeyBaseName:
|
||||
externalCA, _ = certsphase.UsingExternalCA(rm.cfg)
|
||||
case kubeadmconstants.FrontProxyCACertAndKeyBaseName:
|
||||
externalCA, _ = certsphase.UsingExternalFrontProxyCA(rm.cfg)
|
||||
case kubeadmconstants.EtcdCACertAndKeyBaseName:
|
||||
externalCA = false
|
||||
default:
|
||||
return false, errors.Errorf("unknown certificate authority %s", handler.CABaseName)
|
||||
// checks if the certificate is externally managed (CA certificate provided without the certificate key)
|
||||
externallyManaged, err := rm.IsExternallyManaged(handler)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// in case of external CA it is not possible to renew certificates, then return early
|
||||
if externalCA {
|
||||
if externallyManaged {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -275,6 +268,54 @@ func (rm *Manager) CreateRenewCSR(name, outdir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExpirationInfo 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) GetExpirationInfo(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)
|
||||
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
|
||||
}
|
||||
|
||||
// IsExternallyManaged checks if we are in the external CA case (CA certificate provided without the certificate key)
|
||||
func (rm *Manager) IsExternallyManaged(h *CertificateRenewHandler) (bool, error) {
|
||||
switch h.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", h.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", h.CABaseName)
|
||||
}
|
||||
return externallyManaged, nil
|
||||
case kubeadmconstants.EtcdCACertAndKeyBaseName:
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.Errorf("unknown certificate authority %s", h.CABaseName)
|
||||
}
|
||||
}
|
||||
|
||||
func certToConfig(cert *x509.Certificate) *certutil.Config {
|
||||
return &certutil.Config{
|
||||
CommonName: cert.Subject.CommonName,
|
||||
|
@ -697,6 +697,7 @@ func TestRenewCertsByComponent(t *testing.T) {
|
||||
skipCreateEtcdCA bool
|
||||
shouldErrorOnRenew bool
|
||||
certsShouldExist []*certsphase.KubeadmCert
|
||||
certsShouldBeRenewed []*certsphase.KubeadmCert // NB. If empty, it will assume certsShouldBeRenewed == certsShouldExist
|
||||
kubeConfigShouldExist []string
|
||||
}{
|
||||
{
|
||||
@ -724,6 +725,12 @@ func TestRenewCertsByComponent(t *testing.T) {
|
||||
certsShouldExist: []*certsphase.KubeadmCert{
|
||||
&certsphase.KubeadmCertEtcdAPIClient,
|
||||
&certsphase.KubeadmCertFrontProxyClient,
|
||||
&certsphase.KubeadmCertAPIServer,
|
||||
&certsphase.KubeadmCertKubeletClient,
|
||||
},
|
||||
certsShouldBeRenewed: []*certsphase.KubeadmCert{
|
||||
&certsphase.KubeadmCertEtcdAPIClient,
|
||||
&certsphase.KubeadmCertFrontProxyClient,
|
||||
},
|
||||
externalCA: true,
|
||||
},
|
||||
@ -731,6 +738,12 @@ func TestRenewCertsByComponent(t *testing.T) {
|
||||
name: "external front-proxy-CA, renew only certificates not signed by front-proxy-CA for apiserver",
|
||||
component: constants.KubeAPIServer,
|
||||
certsShouldExist: []*certsphase.KubeadmCert{
|
||||
&certsphase.KubeadmCertEtcdAPIClient,
|
||||
&certsphase.KubeadmCertFrontProxyClient,
|
||||
&certsphase.KubeadmCertAPIServer,
|
||||
&certsphase.KubeadmCertKubeletClient,
|
||||
},
|
||||
certsShouldBeRenewed: []*certsphase.KubeadmCert{
|
||||
&certsphase.KubeadmCertEtcdAPIClient,
|
||||
&certsphase.KubeadmCertAPIServer,
|
||||
&certsphase.KubeadmCertKubeletClient,
|
||||
@ -849,8 +862,22 @@ func TestRenewCertsByComponent(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
oldSerial, _ := certMaps[kubeCert.Name]
|
||||
if oldSerial.Cmp(newCert.SerialNumber) == 0 {
|
||||
t.Errorf("certifitate %v was not reissued", kubeCert.Name)
|
||||
|
||||
shouldBeRenewed := true
|
||||
if test.certsShouldBeRenewed != nil {
|
||||
shouldBeRenewed = false
|
||||
for _, x := range test.certsShouldBeRenewed {
|
||||
if x.Name == kubeCert.Name {
|
||||
shouldBeRenewed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if shouldBeRenewed && oldSerial.Cmp(newCert.SerialNumber) == 0 {
|
||||
t.Errorf("certifitate %v was not reissued when expected", kubeCert.Name)
|
||||
}
|
||||
if !shouldBeRenewed && oldSerial.Cmp(newCert.SerialNumber) != 0 {
|
||||
t.Errorf("certifitate %v was reissued when not expected", kubeCert.Name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,6 @@ const (
|
||||
// RSAPrivateKeyBlockType is a possible value for pem.Block.Type.
|
||||
RSAPrivateKeyBlockType = "RSA PRIVATE KEY"
|
||||
rsaKeySize = 2048
|
||||
duration365d = time.Hour * 24 * 365
|
||||
)
|
||||
|
||||
// NewCertificateAuthority creates new certificate and private key for the certificate authority
|
||||
@ -572,7 +571,7 @@ func NewSignedCert(cfg *certutil.Config, key crypto.Signer, caCert *x509.Certifi
|
||||
IPAddresses: cfg.AltNames.IPs,
|
||||
SerialNumber: serial,
|
||||
NotBefore: caCert.NotBefore,
|
||||
NotAfter: time.Now().Add(duration365d).UTC(),
|
||||
NotAfter: time.Now().Add(kubeadmconstants.CertificateValidity).UTC(),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: cfg.Usages,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user