mirror of
https://github.com/kubernetes/client-go.git
synced 2025-09-22 19:47:08 +00:00
Switch cert manager to v1 CSR API by default, falling back to v1beta1
Kubernetes-commit: a298c14f18d4973a9ceaf21f1e0dc4e39b4c5bfb
This commit is contained in:
committed by
Kubernetes Publisher
parent
0adb702ae4
commit
3ab7d09ea9
@@ -27,14 +27,17 @@ import (
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
certificates "k8s.io/api/certificates/v1beta1"
|
||||
certificatesv1 "k8s.io/api/certificates/v1"
|
||||
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
watchtools "k8s.io/client-go/tools/watch"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
@@ -42,95 +45,239 @@ import (
|
||||
|
||||
// RequestCertificate will either use an existing (if this process has run
|
||||
// before but not to completion) or create a certificate signing request using the
|
||||
// PEM encoded CSR and send it to API server, then it will watch the object's
|
||||
// status, once approved by API server, it will return the API server's issued
|
||||
// certificate (pem-encoded). If there is any errors, or the watch timeouts, it
|
||||
// will return an error.
|
||||
func RequestCertificate(client certificatesclient.CertificateSigningRequestInterface, csrData []byte, name string, signerName string, usages []certificates.KeyUsage, privateKey interface{}) (req *certificates.CertificateSigningRequest, err error) {
|
||||
csr := &certificates.CertificateSigningRequest{
|
||||
// PEM encoded CSR and send it to API server.
|
||||
func RequestCertificate(client clientset.Interface, csrData []byte, name string, signerName string, usages []certificatesv1.KeyUsage, privateKey interface{}) (reqName string, reqUID types.UID, err error) {
|
||||
csr := &certificatesv1.CertificateSigningRequest{
|
||||
// Username, UID, Groups will be injected by API server.
|
||||
TypeMeta: metav1.TypeMeta{Kind: "CertificateSigningRequest"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
Spec: certificates.CertificateSigningRequestSpec{
|
||||
Spec: certificatesv1.CertificateSigningRequestSpec{
|
||||
Request: csrData,
|
||||
Usages: usages,
|
||||
SignerName: &signerName,
|
||||
SignerName: signerName,
|
||||
},
|
||||
}
|
||||
if len(csr.Name) == 0 {
|
||||
csr.GenerateName = "csr-"
|
||||
}
|
||||
|
||||
req, err = client.Create(context.TODO(), csr, metav1.CreateOptions{})
|
||||
reqName, reqUID, err = create(client, csr)
|
||||
switch {
|
||||
case err == nil:
|
||||
return reqName, reqUID, err
|
||||
|
||||
case errors.IsAlreadyExists(err) && len(name) > 0:
|
||||
klog.Infof("csr for this node already exists, reusing")
|
||||
req, err = client.Get(context.TODO(), name, metav1.GetOptions{})
|
||||
req, err := get(client, name)
|
||||
if err != nil {
|
||||
return nil, formatError("cannot retrieve certificate signing request: %v", err)
|
||||
return "", "", formatError("cannot retrieve certificate signing request: %v", err)
|
||||
}
|
||||
if err := ensureCompatible(req, csr, privateKey); err != nil {
|
||||
return nil, fmt.Errorf("retrieved csr is not compatible: %v", err)
|
||||
return "", "", fmt.Errorf("retrieved csr is not compatible: %v", err)
|
||||
}
|
||||
klog.Infof("csr for this node is still valid")
|
||||
return req.Name, req.UID, nil
|
||||
|
||||
default:
|
||||
return nil, formatError("cannot create certificate signing request: %v", err)
|
||||
return "", "", formatError("cannot create certificate signing request: %v", err)
|
||||
}
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func get(client clientset.Interface, name string) (*certificatesv1.CertificateSigningRequest, error) {
|
||||
v1req, v1err := client.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
if v1err == nil || !apierrors.IsNotFound(v1err) {
|
||||
return v1req, v1err
|
||||
}
|
||||
|
||||
v1beta1req, v1beta1err := client.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{})
|
||||
if v1beta1err != nil {
|
||||
return nil, v1beta1err
|
||||
}
|
||||
|
||||
v1req = &certificatesv1.CertificateSigningRequest{
|
||||
ObjectMeta: v1beta1req.ObjectMeta,
|
||||
Spec: certificatesv1.CertificateSigningRequestSpec{
|
||||
Request: v1beta1req.Spec.Request,
|
||||
},
|
||||
}
|
||||
if v1beta1req.Spec.SignerName != nil {
|
||||
v1req.Spec.SignerName = *v1beta1req.Spec.SignerName
|
||||
}
|
||||
for _, usage := range v1beta1req.Spec.Usages {
|
||||
v1req.Spec.Usages = append(v1req.Spec.Usages, certificatesv1.KeyUsage(usage))
|
||||
}
|
||||
return v1req, nil
|
||||
}
|
||||
|
||||
func create(client clientset.Interface, csr *certificatesv1.CertificateSigningRequest) (reqName string, reqUID types.UID, err error) {
|
||||
// only attempt a create via v1 if we specified signerName and usages and are not using the legacy unknown signerName
|
||||
if len(csr.Spec.Usages) > 0 && len(csr.Spec.SignerName) > 0 && csr.Spec.SignerName != "kubernetes.io/legacy-unknown" {
|
||||
v1req, v1err := client.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), csr, metav1.CreateOptions{})
|
||||
switch {
|
||||
case v1err != nil && apierrors.IsNotFound(v1err):
|
||||
// v1 CSR API was not found, continue to try v1beta1
|
||||
|
||||
case v1err != nil:
|
||||
// other creation error
|
||||
return "", "", v1err
|
||||
|
||||
default:
|
||||
// success
|
||||
return v1req.Name, v1req.UID, v1err
|
||||
}
|
||||
}
|
||||
|
||||
// convert relevant bits to v1beta1
|
||||
v1beta1csr := &certificatesv1beta1.CertificateSigningRequest{
|
||||
ObjectMeta: csr.ObjectMeta,
|
||||
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
|
||||
SignerName: &csr.Spec.SignerName,
|
||||
Request: csr.Spec.Request,
|
||||
},
|
||||
}
|
||||
for _, usage := range csr.Spec.Usages {
|
||||
v1beta1csr.Spec.Usages = append(v1beta1csr.Spec.Usages, certificatesv1beta1.KeyUsage(usage))
|
||||
}
|
||||
|
||||
// create v1beta1
|
||||
v1beta1req, v1beta1err := client.CertificatesV1beta1().CertificateSigningRequests().Create(context.TODO(), v1beta1csr, metav1.CreateOptions{})
|
||||
if v1beta1err != nil {
|
||||
return "", "", v1beta1err
|
||||
}
|
||||
return v1beta1req.Name, v1beta1req.UID, nil
|
||||
}
|
||||
|
||||
// WaitForCertificate waits for a certificate to be issued until timeout, or returns an error.
|
||||
func WaitForCertificate(ctx context.Context, client certificatesclient.CertificateSigningRequestInterface, req *certificates.CertificateSigningRequest) (certData []byte, err error) {
|
||||
fieldSelector := fields.OneTermEqualSelector("metadata.name", req.Name).String()
|
||||
lw := &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.List(context.TODO(), options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.Watch(context.TODO(), options)
|
||||
},
|
||||
func WaitForCertificate(ctx context.Context, client clientset.Interface, reqName string, reqUID types.UID) (certData []byte, err error) {
|
||||
fieldSelector := fields.OneTermEqualSelector("metadata.name", reqName).String()
|
||||
|
||||
var lw *cache.ListWatch
|
||||
var obj runtime.Object
|
||||
for {
|
||||
// see if the v1 API is available
|
||||
if _, err := client.CertificatesV1().CertificateSigningRequests().List(ctx, metav1.ListOptions{FieldSelector: fieldSelector}); err == nil {
|
||||
// watch v1 objects
|
||||
obj = &certificatesv1.CertificateSigningRequest{}
|
||||
lw = &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.CertificatesV1().CertificateSigningRequests().List(ctx, options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.CertificatesV1().CertificateSigningRequests().Watch(ctx, options)
|
||||
},
|
||||
}
|
||||
break
|
||||
} else {
|
||||
klog.V(2).Infof("error fetching v1 certificate signing request: %v", err)
|
||||
}
|
||||
|
||||
// return if we've timed out
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, wait.ErrWaitTimeout
|
||||
}
|
||||
|
||||
// see if the v1beta1 API is available
|
||||
if _, err := client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, metav1.ListOptions{FieldSelector: fieldSelector}); err == nil {
|
||||
// watch v1beta1 objects
|
||||
obj = &certificatesv1beta1.CertificateSigningRequest{}
|
||||
lw = &cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.CertificatesV1beta1().CertificateSigningRequests().List(ctx, options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
options.FieldSelector = fieldSelector
|
||||
return client.CertificatesV1beta1().CertificateSigningRequests().Watch(ctx, options)
|
||||
},
|
||||
}
|
||||
break
|
||||
} else {
|
||||
klog.V(2).Infof("error fetching v1beta1 certificate signing request: %v", err)
|
||||
}
|
||||
|
||||
// return if we've timed out
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, wait.ErrWaitTimeout
|
||||
}
|
||||
|
||||
// wait and try again
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
event, err := watchtools.UntilWithSync(
|
||||
|
||||
var issuedCertificate []byte
|
||||
_, err = watchtools.UntilWithSync(
|
||||
ctx,
|
||||
lw,
|
||||
&certificates.CertificateSigningRequest{},
|
||||
obj,
|
||||
nil,
|
||||
func(event watch.Event) (bool, error) {
|
||||
switch event.Type {
|
||||
case watch.Modified, watch.Added:
|
||||
case watch.Deleted:
|
||||
return false, fmt.Errorf("csr %q was deleted", req.Name)
|
||||
return false, fmt.Errorf("csr %q was deleted", reqName)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
csr := event.Object.(*certificates.CertificateSigningRequest)
|
||||
if csr.UID != req.UID {
|
||||
return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
|
||||
}
|
||||
approved := false
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if c.Type == certificates.CertificateDenied {
|
||||
return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificates.CertificateFailed {
|
||||
return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificates.CertificateApproved {
|
||||
approved = true
|
||||
}
|
||||
}
|
||||
if approved {
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
|
||||
return true, nil
|
||||
}
|
||||
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
|
||||
|
||||
switch csr := event.Object.(type) {
|
||||
case *certificatesv1.CertificateSigningRequest:
|
||||
if csr.UID != reqUID {
|
||||
return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
|
||||
}
|
||||
approved := false
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if c.Type == certificatesv1.CertificateDenied {
|
||||
return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificatesv1.CertificateFailed {
|
||||
return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificatesv1.CertificateApproved {
|
||||
approved = true
|
||||
}
|
||||
}
|
||||
if approved {
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
|
||||
issuedCertificate = csr.Status.Certificate
|
||||
return true, nil
|
||||
}
|
||||
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
|
||||
}
|
||||
|
||||
case *certificatesv1beta1.CertificateSigningRequest:
|
||||
if csr.UID != reqUID {
|
||||
return false, fmt.Errorf("csr %q changed UIDs", csr.Name)
|
||||
}
|
||||
approved := false
|
||||
for _, c := range csr.Status.Conditions {
|
||||
if c.Type == certificatesv1beta1.CertificateDenied {
|
||||
return false, fmt.Errorf("certificate signing request is denied, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificatesv1beta1.CertificateFailed {
|
||||
return false, fmt.Errorf("certificate signing request failed, reason: %v, message: %v", c.Reason, c.Message)
|
||||
}
|
||||
if c.Type == certificatesv1beta1.CertificateApproved {
|
||||
approved = true
|
||||
}
|
||||
}
|
||||
if approved {
|
||||
if len(csr.Status.Certificate) > 0 {
|
||||
klog.V(2).Infof("certificate signing request %s is issued", csr.Name)
|
||||
issuedCertificate = csr.Status.Certificate
|
||||
return true, nil
|
||||
}
|
||||
klog.V(2).Infof("certificate signing request %s is approved, waiting to be issued", csr.Name)
|
||||
}
|
||||
|
||||
default:
|
||||
return false, fmt.Errorf("unexpected type received: %T", event.Object)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
},
|
||||
)
|
||||
@@ -141,24 +288,24 @@ func WaitForCertificate(ctx context.Context, client certificatesclient.Certifica
|
||||
return nil, formatError("cannot watch on the certificate signing request: %v", err)
|
||||
}
|
||||
|
||||
return event.Object.(*certificates.CertificateSigningRequest).Status.Certificate, nil
|
||||
return issuedCertificate, nil
|
||||
}
|
||||
|
||||
// ensureCompatible ensures that a CSR object is compatible with an original CSR
|
||||
func ensureCompatible(new, orig *certificates.CertificateSigningRequest, privateKey interface{}) error {
|
||||
newCSR, err := parseCSR(new)
|
||||
func ensureCompatible(new, orig *certificatesv1.CertificateSigningRequest, privateKey interface{}) error {
|
||||
newCSR, err := parseCSR(new.Spec.Request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse new csr: %v", err)
|
||||
}
|
||||
origCSR, err := parseCSR(orig)
|
||||
origCSR, err := parseCSR(orig.Spec.Request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse original csr: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(newCSR.Subject, origCSR.Subject) {
|
||||
return fmt.Errorf("csr subjects differ: new: %#v, orig: %#v", newCSR.Subject, origCSR.Subject)
|
||||
}
|
||||
if new.Spec.SignerName != nil && orig.Spec.SignerName != nil && *new.Spec.SignerName != *orig.Spec.SignerName {
|
||||
return fmt.Errorf("csr signerNames differ: new %q, orig: %q", *new.Spec.SignerName, *orig.Spec.SignerName)
|
||||
if len(new.Spec.SignerName) > 0 && len(orig.Spec.SignerName) > 0 && new.Spec.SignerName != orig.Spec.SignerName {
|
||||
return fmt.Errorf("csr signerNames differ: new %q, orig: %q", new.Spec.SignerName, orig.Spec.SignerName)
|
||||
}
|
||||
signer, ok := privateKey.(crypto.Signer)
|
||||
if !ok {
|
||||
@@ -195,9 +342,9 @@ func formatError(format string, err error) error {
|
||||
}
|
||||
|
||||
// parseCSR extracts the CSR from the API object and decodes it.
|
||||
func parseCSR(obj *certificates.CertificateSigningRequest) (*x509.CertificateRequest, error) {
|
||||
func parseCSR(pemData []byte) (*x509.CertificateRequest, error) {
|
||||
// extract PEM from request object
|
||||
block, _ := pem.Decode(obj.Spec.Request)
|
||||
block, _ := pem.Decode(pemData)
|
||||
if block == nil || block.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, fmt.Errorf("PEM block type must be CERTIFICATE REQUEST")
|
||||
}
|
||||
|
Reference in New Issue
Block a user