package approver import ( "context" "errors" "net" "strings" pve "git.giftfish.de/ston1th/pve-go" "github.com/go-logr/logr" certificatesv1beta1 "k8s.io/api/certificates/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/sets" typedcertificatesv1beta1 "k8s.io/client-go/kubernetes/typed/certificates/v1beta1" "k8s.io/kubernetes/pkg/apis/certificates" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" ) var ErrNoKubeletServingCSR = errors.New("CSR is no kubelet serving certificate") type CSRReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme PVEClient *pve.Client CSRClient *typedcertificatesv1beta1.CertificatesV1beta1Client } func (r *CSRReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). WithOptions(options). For(&certificatesv1beta1.CertificateSigningRequest{}). Complete(r) } func (r *CSRReconciler) Reconcile(req ctrl.Request) (res ctrl.Result, err error) { ctx := context.Background() log := r.Log csr := &certificatesv1beta1.CertificateSigningRequest{} if err = r.Get(ctx, req.NamespacedName, csr); err != nil { if apierrors.IsNotFound(err) { err = nil return } return } if !csr.DeletionTimestamp.IsZero() { return } err = r.approve(ctx, log, csr) return } var conditionv1beta1 = certificatesv1beta1.CertificateSigningRequestCondition{ Type: certificatesv1beta1.CertificateApproved, Reason: "AutoApproved", Message: "Auto approving kubelet client certificate in PVE Cluster", } func (r *CSRReconciler) approve(ctx context.Context, log logr.Logger, request *certificatesv1beta1.CertificateSigningRequest) error { if len(request.Status.Conditions) > 0 { return nil } client := r.CSRClient.CertificateSigningRequests() err := checkCSR(ctx, r.PVEClient, request) if err == ErrNoKubeletServingCSR { return nil } if err != nil { return err } for { if len(request.Status.Conditions) > 0 { return nil } request.Status.Conditions = append(request.Status.Conditions, conditionv1beta1) // Submit the updated CSR. if _, err := client.UpdateApproval(ctx, request, metav1.UpdateOptions{}); err != nil { if strings.Contains(err.Error(), "the object has been modified") { // The CSR might have been updated by a third-party, retry until we // succeed. request, err = client.Get(ctx, request.ObjectMeta.Name, metav1.GetOptions{}) if err != nil { return err } continue } return err } log.Info("csr approval successful", "name", request.ObjectMeta.Name) return nil } } func checkCSR(ctx context.Context, pveClient *pve.Client, request *certificatesv1beta1.CertificateSigningRequest) error { req, err := certificates.ParseCSR(request.Spec.Request) if err != nil { return err } usages := make([]string, len(request.Spec.Usages)) for i, v := range request.Spec.Usages { usages[i] = string(v) } if !certificates.IsKubeletServingCSR(req, sets.NewString(usages...)) { return ErrNoKubeletServingCSR } hostname := strings.TrimPrefix(req.Subject.CommonName, "system:node:") ref, err := pveClient.Pool.FindServerByName(ctx, "", hostname) if err != nil { return err } if ref.Name != hostname { return errors.New("CSR hostname and PVE server name do not match") } ok := false for _, dns := range req.DNSNames { if dns == hostname { ok = true } } if len(req.DNSNames) == 0 { ok = true } if !ok { return errors.New("req.DNSNames does not contain hostname") } srv, err := pveClient.Server.GetByRef(ctx, ref) if err != nil { return err } ok = false for _, ip := range req.IPAddresses { for _, ipc := range srv.IPConfig { ipv4, _, err := net.ParseCIDR(ipc.IPv4CIDR) if err == nil { if ip.String() == ipv4.String() { ok = true break } } ipv6, _, err := net.ParseCIDR(ipc.IPv6CIDR) if err == nil { if ip.String() == ipv6.String() { ok = true break } } } if ok { break } } if len(req.IPAddresses) == 0 { ok = true } if !ok { return errors.New("req.IPAddresses does not contain a valid IP") } return nil }