Merge pull request #90143 from neolit123/1.19-remove-cert-renew-api

kubeadm: remove usage of the "certificates" API for cert renewal
This commit is contained in:
Kubernetes Prow Robot 2020-06-05 11:35:43 -07:00 committed by GitHub
commit b8b4186a14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 9 additions and 341 deletions

View File

@ -122,7 +122,6 @@ type renewFlags struct {
cfgPath string
kubeconfigPath string
cfg kubeadmapiv1beta2.ClusterConfiguration
useAPI bool
csrOnly bool
csrPath string
}
@ -210,12 +209,6 @@ func addRenewFlags(cmd *cobra.Command, flags *renewFlags) {
options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeconfigPath)
options.AddCSRFlag(cmd.Flags(), &flags.csrOnly)
options.AddCSRDirFlag(cmd.Flags(), &flags.csrPath)
// TODO: remove the flag and related logic once legacy signers are removed,
// potentially with the release of certificates.k8s.io/v1:
// https://github.com/kubernetes/kubeadm/issues/2047
cmd.Flags().BoolVar(&flags.useAPI, "use-api", flags.useAPI, "Use the Kubernetes certificate API to renew certificates")
cmd.Flags().MarkDeprecated("use-api", "certificate renewal from kubeadm using the Kubernetes API "+
"is deprecated and will be removed when 'certificates.k8s.io/v1' releases.")
}
func renewCert(flags *renewFlags, kdir string, internalcfg *kubeadmapi.InitConfiguration, handler *renewal.CertificateRenewHandler) error {
@ -241,29 +234,15 @@ func renewCert(flags *renewFlags, kdir string, internalcfg *kubeadmapi.InitConfi
// otherwise, the renewal operation has to actually renew a certificate
// renew the certificate using the requested renew method
if flags.useAPI {
// renew using K8s certificate API
kubeConfigPath := cmdutil.GetKubeConfigPath(flags.kubeconfigPath)
client, err := kubeconfigutil.ClientSetFromFile(kubeConfigPath)
if err != nil {
return err
}
if err := rm.RenewUsingCSRAPI(handler.Name, client); err != nil {
return err
}
} else {
// renew using local certificate authorities.
// this operation can't complete in case the certificate key is not provided (external CA)
renewed, err := rm.RenewUsingLocalCA(handler.Name)
if err != nil {
return err
}
if !renewed {
fmt.Printf("Detected external %s, %s can't be renewed\n", handler.CABaseName, handler.LongName)
return nil
}
// renew using local certificate authorities.
// this operation can't complete in case the certificate key is not provided (external CA)
renewed, err := rm.RenewUsingLocalCA(handler.Name)
if err != nil {
return err
}
if !renewed {
fmt.Printf("Detected external %s, %s can't be renewed\n", handler.CABaseName, handler.LongName)
return nil
}
fmt.Printf("%s renewed\n", handler.LongName)
return nil

View File

@ -39,7 +39,6 @@ func TestCommandsGenerated(t *testing.T) {
expectedFlags := []string{
"cert-dir",
"config",
"use-api",
}
expectedCommands := []string{

View File

@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"apirenewer.go",
"expiration.go",
"filerenewer.go",
"manager.go",
@ -16,14 +15,9 @@ go_library(
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/phases/certs:go_default_library",
"//cmd/kubeadm/app/util/pkiutil:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//staging/src/k8s.io/client-go/util/certificate/csr:go_default_library",
"//staging/src/k8s.io/client-go/util/keyutil:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
@ -32,7 +26,6 @@ go_library(
go_test(
name = "go_default_test",
srcs = [
"apirenewer_test.go",
"expiration_test.go",
"filerenewer_test.go",
"manager_test.go",
@ -46,12 +39,6 @@ go_test(
"//cmd/kubeadm/app/util/kubeconfig:go_default_library",
"//cmd/kubeadm/app/util/pkiutil:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//staging/src/k8s.io/api/certificates/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//staging/src/k8s.io/client-go/util/keyutil:go_default_library",

View File

@ -1,134 +0,0 @@
/*
Copyright 2018 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 (
"context"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"time"
"github.com/pkg/errors"
certsapi "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
certstype "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
certutil "k8s.io/client-go/util/cert"
csrutil "k8s.io/client-go/util/certificate/csr"
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
)
const certAPIPrefixName = "kubeadm-cert"
var watchTimeout = 5 * time.Minute
// APIRenewer define a certificate renewer implementation that uses the K8s certificate API
type APIRenewer struct {
client certstype.CertificatesV1beta1Interface
}
// NewAPIRenewer a new certificate renewer implementation that uses the K8s certificate API
func NewAPIRenewer(client clientset.Interface) *APIRenewer {
return &APIRenewer{
client: client.CertificatesV1beta1(),
}
}
// Renew a certificate using the K8s certificate API
func (r *APIRenewer) Renew(cfg *pkiutil.CertConfig) (*x509.Certificate, crypto.Signer, error) {
reqTmp := &x509.CertificateRequest{
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
}
key, err := pkiutil.NewPrivateKey(cfg.PublicKeyAlgorithm)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't create new private key")
}
csr, err := certutil.MakeCSRFromTemplate(key, reqTmp)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't create certificate signing request")
}
usages := make([]certsapi.KeyUsage, len(cfg.Usages))
for i, usage := range cfg.Usages {
certsAPIUsage, ok := usageMap[usage]
if !ok {
return nil, nil, errors.Errorf("unknown key usage: %v", usage)
}
usages[i] = certsAPIUsage
}
k8sCSR := &certsapi.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", certAPIPrefixName, cfg.CommonName),
},
Spec: certsapi.CertificateSigningRequestSpec{
Request: csr,
Usages: usages,
},
}
req, err := r.client.CertificateSigningRequests().Create(context.TODO(), k8sCSR, metav1.CreateOptions{})
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't create certificate signing request")
}
fmt.Printf("[certs] Certificate request %q created\n", req.Name)
ctx, cancel := context.WithTimeout(context.Background(), watchTimeout)
defer cancel()
certData, err := csrutil.WaitForCertificate(ctx, r.client.CertificateSigningRequests(), req)
if err != nil {
return nil, nil, errors.Wrap(err, "certificate failed to appear")
}
cert, err := certutil.ParseCertsPEM(certData)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't parse issued certificate")
}
if len(cert) != 1 {
return nil, nil, errors.Errorf("certificate request %q has %d certificates, wanted exactly 1", req.Name, len(cert))
}
return cert[0], key, nil
}
var usageMap = map[x509.ExtKeyUsage]certsapi.KeyUsage{
x509.ExtKeyUsageAny: certsapi.UsageAny,
x509.ExtKeyUsageServerAuth: certsapi.UsageServerAuth,
x509.ExtKeyUsageClientAuth: certsapi.UsageClientAuth,
x509.ExtKeyUsageCodeSigning: certsapi.UsageCodeSigning,
x509.ExtKeyUsageEmailProtection: certsapi.UsageEmailProtection,
x509.ExtKeyUsageIPSECEndSystem: certsapi.UsageIPsecEndSystem,
x509.ExtKeyUsageIPSECTunnel: certsapi.UsageIPsecTunnel,
x509.ExtKeyUsageIPSECUser: certsapi.UsageIPsecUser,
x509.ExtKeyUsageTimeStamping: certsapi.UsageTimestamping,
x509.ExtKeyUsageOCSPSigning: certsapi.UsageOCSPSigning,
x509.ExtKeyUsageMicrosoftServerGatedCrypto: certsapi.UsageMicrosoftSGC,
x509.ExtKeyUsageNetscapeServerGatedCrypto: certsapi.UsageNetscapeSGC,
}

View File

@ -1,125 +0,0 @@
/*
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"
"crypto/x509"
"testing"
"time"
certsapi "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
fakecerts "k8s.io/client-go/kubernetes/typed/certificates/v1beta1/fake"
k8stesting "k8s.io/client-go/testing"
certutil "k8s.io/client-go/util/cert"
pkiutil "k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
)
func TestAPIRenewer(t *testing.T) {
caCertCfg := &pkiutil.CertConfig{
Config: certutil.Config{CommonName: "kubernetes"},
}
caCert, caKey, err := pkiutil.NewCertificateAuthority(caCertCfg)
if err != nil {
t.Fatalf("couldn't create CA: %v", err)
}
client := &fakecerts.FakeCertificatesV1beta1{
Fake: &k8stesting.Fake{},
}
certReq := getCertReq(t, caCert, caKey)
certReqNoCert := certReq.DeepCopy()
certReqNoCert.Status.Certificate = nil
client.AddReactor("get", "certificatesigningrequests", defaultReactionFunc(certReq))
watcher := watch.NewFakeWithChanSize(3, false)
watcher.Add(certReqNoCert)
watcher.Modify(certReqNoCert)
watcher.Modify(certReq)
client.AddWatchReactor("certificatesigningrequests", k8stesting.DefaultWatchReactor(watcher, nil))
// override the timeout so tests are faster
watchTimeout = time.Second
certCfg := &pkiutil.CertConfig{
Config: certutil.Config{
CommonName: "test-certs",
AltNames: certutil.AltNames{
DNSNames: []string{"test-domain.space"},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
}
renewer := &APIRenewer{
client: client,
}
cert, _, err := renewer.Renew(certCfg)
if err != nil {
t.Fatalf("unexpected error renewing cert: %v", err)
}
pool := x509.NewCertPool()
pool.AddCert(caCert)
_, err = cert.Verify(x509.VerifyOptions{
DNSName: "test-domain.space",
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
})
if err != nil {
t.Errorf("couldn't verify new cert: %v", err)
}
}
func defaultReactionFunc(obj runtime.Object) k8stesting.ReactionFunc {
return func(act k8stesting.Action) (bool, runtime.Object, error) {
return true, obj, nil
}
}
func getCertReq(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer) *certsapi.CertificateSigningRequest {
cert, _, err := pkiutil.NewCertAndKey(caCert, caKey, &pkiutil.CertConfig{
Config: certutil.Config{
CommonName: "testcert",
AltNames: certutil.AltNames{
DNSNames: []string{"test-domain.space"},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
})
if err != nil {
t.Fatalf("couldn't generate cert: %v", err)
}
return &certsapi.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "testcert",
},
Status: certsapi.CertificateSigningRequestStatus{
Conditions: []certsapi.CertificateSigningRequestCondition{
{
Type: certsapi.CertificateApproved,
},
},
Certificate: pkiutil.EncodeCertPEM(cert),
},
}
}

View File

@ -21,7 +21,6 @@ import (
"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"
@ -250,43 +249,6 @@ func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) {
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.