Skip to content

Commit c88b7cd

Browse files
authored
Merge pull request kubernetes#76862 from fabriziopandini/fix-upgrade-certs-renew
kubeadm: fix certs renewal during upgrade
2 parents a0b8d1c + 137137c commit c88b7cd

File tree

7 files changed

+135
-196
lines changed

7 files changed

+135
-196
lines changed

cmd/kubeadm/app/cmd/upgrade/apply.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ type applyFlags struct {
5252
force bool
5353
dryRun bool
5454
etcdUpgrade bool
55+
renewCerts bool
5556
criSocket string
5657
imagePullTimeout time.Duration
5758
}
@@ -67,6 +68,7 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
6768
applyPlanFlags: apf,
6869
imagePullTimeout: defaultImagePullTimeout,
6970
etcdUpgrade: true,
71+
renewCerts: true,
7072
// Don't set criSocket to a default value here, as this will override the setting in the stored config in RunApply below.
7173
}
7274

@@ -90,6 +92,7 @@ func NewCmdApply(apf *applyPlanFlags) *cobra.Command {
9092
cmd.Flags().BoolVarP(&flags.force, "force", "f", flags.force, "Force upgrading although some requirements might not be met. This also implies non-interactive mode.")
9193
cmd.Flags().BoolVar(&flags.dryRun, options.DryRun, flags.dryRun, "Do not change any state, just output what actions would be performed.")
9294
cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.")
95+
cmd.Flags().BoolVar(&flags.renewCerts, "certificate-renewal", flags.renewCerts, "Perform the renewal of certificates used by component changed during upgrades.")
9396
cmd.Flags().DurationVar(&flags.imagePullTimeout, "image-pull-timeout", flags.imagePullTimeout, "The maximum amount of time to wait for the control plane pods to be downloaded.")
9497

9598
// The CRI socket flag is deprecated here, since it should be taken from the NodeRegistrationOptions for the current
@@ -231,7 +234,7 @@ func PerformControlPlaneUpgrade(flags *applyFlags, client clientset.Interface, w
231234
}
232235

233236
// Don't save etcd backup directory if etcd is HA, as this could cause corruption
234-
return PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade)
237+
return PerformStaticPodUpgrade(client, waiter, internalcfg, flags.etcdUpgrade, flags.renewCerts)
235238
}
236239

237240
// GetPathManagerForUpgrade returns a path manager properly configured for the given InitConfiguration.
@@ -241,14 +244,14 @@ func GetPathManagerForUpgrade(internalcfg *kubeadmapi.InitConfiguration, etcdUpg
241244
}
242245

243246
// PerformStaticPodUpgrade performs the upgrade of the control plane components for a static pod hosted cluster
244-
func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade bool) error {
247+
func PerformStaticPodUpgrade(client clientset.Interface, waiter apiclient.Waiter, internalcfg *kubeadmapi.InitConfiguration, etcdUpgrade, renewCerts bool) error {
245248
pathManager, err := GetPathManagerForUpgrade(internalcfg, etcdUpgrade)
246249
if err != nil {
247250
return err
248251
}
249252

250253
// The arguments oldEtcdClient and newEtdClient, are uninitialized because passing in the clients allow for mocking the client during testing
251-
return upgrade.StaticPodControlPlane(client, waiter, pathManager, internalcfg, etcdUpgrade, nil, nil)
254+
return upgrade.StaticPodControlPlane(client, waiter, pathManager, internalcfg, etcdUpgrade, renewCerts, nil, nil)
252255
}
253256

254257
// DryRunStaticPodUpgrade fakes an upgrade of the control plane

cmd/kubeadm/app/cmd/upgrade/node.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ type controlplaneUpgradeFlags struct {
6666
advertiseAddress string
6767
nodeName string
6868
etcdUpgrade bool
69+
renewCerts bool
6970
dryRun bool
7071
}
7172

@@ -114,6 +115,7 @@ func NewCmdUpgradeControlPlane() *cobra.Command {
114115
kubeConfigPath: constants.GetKubeletKubeConfigPath(),
115116
advertiseAddress: "",
116117
etcdUpgrade: true,
118+
renewCerts: true,
117119
dryRun: false,
118120
}
119121

@@ -151,6 +153,7 @@ func NewCmdUpgradeControlPlane() *cobra.Command {
151153
options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeConfigPath)
152154
cmd.Flags().BoolVar(&flags.dryRun, options.DryRun, flags.dryRun, "Do not change any state, just output the actions that would be performed.")
153155
cmd.Flags().BoolVar(&flags.etcdUpgrade, "etcd-upgrade", flags.etcdUpgrade, "Perform the upgrade of etcd.")
156+
cmd.Flags().BoolVar(&flags.renewCerts, "certificate-renewal", flags.renewCerts, "Perform the renewal of certificates used by component changed during upgrades.")
154157
return cmd
155158
}
156159

@@ -221,18 +224,13 @@ func RunUpgradeControlPlane(flags *controlplaneUpgradeFlags) error {
221224
return errors.Wrap(err, "unable to fetch the kubeadm-config ConfigMap")
222225
}
223226

224-
// Rotate API server certificate if needed
225-
if err := upgrade.BackupAPIServerCertIfNeeded(cfg, flags.dryRun); err != nil {
226-
return errors.Wrap(err, "unable to rotate API server certificate")
227-
}
228-
229227
// Upgrade the control plane and etcd if installed on this node
230228
fmt.Printf("[upgrade] Upgrading your Static Pod-hosted control plane instance to version %q...\n", cfg.KubernetesVersion)
231229
if flags.dryRun {
232230
return DryRunStaticPodUpgrade(cfg)
233231
}
234232

235-
if err := PerformStaticPodUpgrade(client, waiter, cfg, flags.etcdUpgrade); err != nil {
233+
if err := PerformStaticPodUpgrade(client, waiter, cfg, flags.etcdUpgrade, flags.renewCerts); err != nil {
236234
return errors.Wrap(err, "couldn't complete the static pod upgrade")
237235
}
238236

cmd/kubeadm/app/phases/upgrade/BUILD

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ go_library(
1515
visibility = ["//visibility:public"],
1616
deps = [
1717
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
18-
"//cmd/kubeadm/app/apis/kubeadm/v1beta2:go_default_library",
1918
"//cmd/kubeadm/app/constants:go_default_library",
2019
"//cmd/kubeadm/app/images:go_default_library",
2120
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
@@ -45,7 +44,6 @@ go_library(
4544
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
4645
"//staging/src/k8s.io/apimachinery/pkg/util/version:go_default_library",
4746
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
48-
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
4947
"//vendor/github.com/pkg/errors:go_default_library",
5048
],
5149
)

cmd/kubeadm/app/phases/upgrade/postupgrade.go

Lines changed: 0 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ limitations under the License.
1717
package upgrade
1818

1919
import (
20-
"fmt"
2120
"os"
2221
"path/filepath"
2322
"time"
@@ -29,15 +28,12 @@ import (
2928
errorsutil "k8s.io/apimachinery/pkg/util/errors"
3029
"k8s.io/apimachinery/pkg/util/version"
3130
clientset "k8s.io/client-go/kubernetes"
32-
certutil "k8s.io/client-go/util/cert"
3331
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
34-
kubeadmapiv1beta2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta2"
3532
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
3633
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
3734
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/proxy"
3835
"k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo"
3936
nodebootstraptoken "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
40-
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
4137
kubeletphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubelet"
4238
patchnodephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/patchnode"
4339
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
@@ -101,11 +97,6 @@ func PerformPostUpgradeTasks(client clientset.Interface, cfg *kubeadmapi.InitCon
10197
errs = append(errs, err)
10298
}
10399

104-
// Rotate the kube-apiserver cert and key if needed
105-
if err := BackupAPIServerCertIfNeeded(cfg, dryRun); err != nil {
106-
errs = append(errs, err)
107-
}
108-
109100
// Upgrade kube-dns/CoreDNS and kube-proxy
110101
if err := dns.EnsureDNSAddon(&cfg.ClusterConfiguration, client); err != nil {
111102
errs = append(errs, err)
@@ -152,37 +143,6 @@ func removeOldDNSDeploymentIfAnotherDNSIsUsed(cfg *kubeadmapi.ClusterConfigurati
152143
}, 10)
153144
}
154145

155-
// BackupAPIServerCertIfNeeded rotates the kube-apiserver certificate if older than 180 days
156-
func BackupAPIServerCertIfNeeded(cfg *kubeadmapi.InitConfiguration, dryRun bool) error {
157-
certAndKeyDir := kubeadmapiv1beta2.DefaultCertificatesDir
158-
shouldBackup, err := shouldBackupAPIServerCertAndKey(certAndKeyDir)
159-
if err != nil {
160-
// Don't fail the upgrade phase if failing to determine to backup kube-apiserver cert and key.
161-
return errors.Wrap(err, "[postupgrade] WARNING: failed to determine to backup kube-apiserver cert and key")
162-
}
163-
164-
if !shouldBackup {
165-
return nil
166-
}
167-
168-
// If dry-running, just say that this would happen to the user and exit
169-
if dryRun {
170-
fmt.Println("[postupgrade] Would rotate the API server certificate and key.")
171-
return nil
172-
}
173-
174-
// Don't fail the upgrade phase if failing to backup kube-apiserver cert and key, just continue rotating the cert
175-
// TODO: We might want to reconsider this choice.
176-
if err := backupAPIServerCertAndKey(certAndKeyDir); err != nil {
177-
fmt.Printf("[postupgrade] WARNING: failed to backup kube-apiserver cert and key: %v\n", err)
178-
}
179-
return certsphase.CreateCertAndKeyFilesWithCA(
180-
&certsphase.KubeadmCertAPIServer,
181-
&certsphase.KubeadmCertRootCA,
182-
cfg,
183-
)
184-
}
185-
186146
func writeKubeletConfigFiles(client clientset.Interface, cfg *kubeadmapi.InitConfiguration, newK8sVer *version.Version, dryRun bool) error {
187147
kubeletDir, err := GetKubeletDir(dryRun)
188148
if err != nil {
@@ -228,20 +188,6 @@ func GetKubeletDir(dryRun bool) (string, error) {
228188
return kubeadmconstants.KubeletRunDirectory, nil
229189
}
230190

231-
// backupAPIServerCertAndKey backups the old cert and key of kube-apiserver to a specified directory.
232-
func backupAPIServerCertAndKey(certAndKeyDir string) error {
233-
subDir := filepath.Join(certAndKeyDir, "expired")
234-
if err := os.Mkdir(subDir, 0700); err != nil {
235-
return errors.Wrapf(err, "failed to created backup directory %s", subDir)
236-
}
237-
238-
filesToMove := map[string]string{
239-
filepath.Join(certAndKeyDir, kubeadmconstants.APIServerCertName): filepath.Join(subDir, kubeadmconstants.APIServerCertName),
240-
filepath.Join(certAndKeyDir, kubeadmconstants.APIServerKeyName): filepath.Join(subDir, kubeadmconstants.APIServerKeyName),
241-
}
242-
return moveFiles(filesToMove)
243-
}
244-
245191
// moveFiles moves files from one directory to another.
246192
func moveFiles(files map[string]string) error {
247193
filesToRecover := map[string]string{}
@@ -264,21 +210,3 @@ func rollbackFiles(files map[string]string, originalErr error) error {
264210
}
265211
return errors.Errorf("couldn't move these files: %v. Got errors: %v", files, errorsutil.NewAggregate(errs))
266212
}
267-
268-
// shouldBackupAPIServerCertAndKey checks if the cert of kube-apiserver will be expired in 180 days.
269-
func shouldBackupAPIServerCertAndKey(certAndKeyDir string) (bool, error) {
270-
apiServerCert := filepath.Join(certAndKeyDir, kubeadmconstants.APIServerCertName)
271-
certs, err := certutil.CertsFromFile(apiServerCert)
272-
if err != nil {
273-
return false, errors.Wrapf(err, "couldn't load the certificate file %s", apiServerCert)
274-
}
275-
if len(certs) == 0 {
276-
return false, errors.New("no certificate data found")
277-
}
278-
279-
if time.Since(certs[0].NotBefore) > expiry {
280-
return true, nil
281-
}
282-
283-
return false, nil
284-
}

cmd/kubeadm/app/phases/upgrade/postupgrade_test.go

Lines changed: 0 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -21,40 +21,13 @@ import (
2121
"path/filepath"
2222
"strings"
2323
"testing"
24-
"time"
2524

2625
"github.com/pkg/errors"
2726

28-
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
2927
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
30-
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
3128
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
3229
)
3330

34-
func TestBackupAPIServerCertAndKey(t *testing.T) {
35-
tmpdir := testutil.SetupTempDir(t)
36-
defer os.RemoveAll(tmpdir)
37-
os.Chmod(tmpdir, 0766)
38-
39-
certPath := filepath.Join(tmpdir, constants.APIServerCertName)
40-
certFile, err := os.OpenFile(certPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
41-
if err != nil {
42-
t.Fatalf("Failed to create cert file %s: %v", certPath, err)
43-
}
44-
defer certFile.Close()
45-
46-
keyPath := filepath.Join(tmpdir, constants.APIServerKeyName)
47-
keyFile, err := os.OpenFile(keyPath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
48-
if err != nil {
49-
t.Fatalf("Failed to create key file %s: %v", keyPath, err)
50-
}
51-
defer keyFile.Close()
52-
53-
if err := backupAPIServerCertAndKey(tmpdir); err != nil {
54-
t.Fatalf("Failed to backup cert and key in dir %s: %v", tmpdir, err)
55-
}
56-
}
57-
5831
func TestMoveFiles(t *testing.T) {
5932
tmpdir := testutil.SetupTempDir(t)
6033
defer os.RemoveAll(tmpdir)
@@ -128,59 +101,3 @@ func TestRollbackFiles(t *testing.T) {
128101
t.Fatalf("Expected error contains %q, got %v", errString, err)
129102
}
130103
}
131-
132-
func TestShouldBackupAPIServerCertAndKey(t *testing.T) {
133-
cfg := &kubeadmapi.InitConfiguration{
134-
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4"},
135-
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
136-
Networking: kubeadmapi.Networking{ServiceSubnet: "10.96.0.0/12", DNSDomain: "cluster.local"},
137-
},
138-
NodeRegistration: kubeadmapi.NodeRegistrationOptions{Name: "test-node"},
139-
}
140-
141-
for desc, test := range map[string]struct {
142-
adjustedExpiry time.Duration
143-
expected bool
144-
}{
145-
"default: cert not older than 180 days doesn't needs to backup": {
146-
expected: false,
147-
},
148-
"cert older than 180 days need to backup": {
149-
adjustedExpiry: expiry + 100*time.Hour,
150-
expected: true,
151-
},
152-
} {
153-
t.Run(desc, func(t *testing.T) {
154-
tmpdir := testutil.SetupTempDir(t)
155-
defer os.RemoveAll(tmpdir)
156-
cfg.CertificatesDir = tmpdir
157-
158-
caCert, caKey, err := certsphase.KubeadmCertRootCA.CreateAsCA(cfg)
159-
if err != nil {
160-
t.Fatalf("failed creation of ca cert and key: %v", err)
161-
}
162-
caCert.NotBefore = caCert.NotBefore.Add(-test.adjustedExpiry).UTC()
163-
164-
err = certsphase.KubeadmCertAPIServer.CreateFromCA(cfg, caCert, caKey)
165-
if err != nil {
166-
t.Fatalf("Test %s: failed creation of cert and key: %v", desc, err)
167-
}
168-
169-
certAndKey := []string{filepath.Join(tmpdir, constants.APIServerCertName), filepath.Join(tmpdir, constants.APIServerKeyName)}
170-
for _, path := range certAndKey {
171-
if _, err := os.Stat(path); os.IsNotExist(err) {
172-
t.Fatalf("Test %s: %s not exist: %v", desc, path, err)
173-
}
174-
}
175-
176-
shouldBackup, err := shouldBackupAPIServerCertAndKey(tmpdir)
177-
if err != nil {
178-
t.Fatalf("Test %s: failed to check shouldBackupAPIServerCertAndKey: %v", desc, err)
179-
}
180-
181-
if shouldBackup != test.expected {
182-
t.Fatalf("Test %s: shouldBackupAPIServerCertAndKey expected %v, got %v", desc, test.expected, shouldBackup)
183-
}
184-
})
185-
}
186-
}

0 commit comments

Comments
 (0)